3636 </button >
3737 </div >
3838
39+ <!-- Center: per-side line counts + net delta (b - a). "A" is the
40+ original (left) pane, "B" is the modified (right) pane. -->
41+ <div class =" noden-line-stats" aria-label =" Line counts" aria-live =" polite" >
42+ <span class =" noden-line-stat" >
43+ <span class =" noden-line-stat-key" >A</span >
44+ <span class =" noden-line-stat-val" >{{ lineStats.a }}</span >
45+ </span >
46+ <span class =" noden-line-stat" >
47+ <span class =" noden-line-stat-key" >B</span >
48+ <span class =" noden-line-stat-val" >{{ lineStats.b }}</span >
49+ </span >
50+ <span
51+ class =" noden-line-stat noden-line-stat-delta"
52+ :class =" {
53+ 'is-positive': lineStats.delta > 0,
54+ 'is-negative': lineStats.delta < 0,
55+ }"
56+ :title =" `Net change: ${formatDelta(lineStats.delta)} lines`"
57+ >
58+ <span class =" noden-line-stat-key" >Δ</span >
59+ <span class =" noden-line-stat-val" >{{ formatDelta(lineStats.delta) }}</span >
60+ </span >
61+ </div >
62+
3963 <!-- Right side: copy-link CTA. -->
4064 <CopyLink :click-handler =" copyUrlToClipboard " :copied =" copied " />
4165 </section >
@@ -55,6 +79,7 @@ import {
5579} from ' ~/helpers/encrypt'
5680import { DiffActionBarData } from ' ~/helpers/types'
5781import { getRandomDiffId } from ' ~/helpers/utils'
82+ import { computeLineStats , formatDelta , LineStats } from ' ~/helpers/lineStats'
5883export default Vue .extend ({
5984 components: { CopyLink , Up , Down },
6085 props: {
@@ -89,6 +114,16 @@ export default Vue.extend({
89114 updateDiffDisposer: null ,
90115 }
91116 },
117+ computed: {
118+ /* Per-side line counts + net delta, read reactively from the diff
119+ * payload in the store (set by diff.vue's unzipCommitData, so it
120+ * matches exactly what the editor renders). "a" is the original
121+ * (left) side, "b" is the modified (right) side. */
122+ lineStats(): LineStats {
123+ const data = (this .$store .state as any ).data || {}
124+ return computeLineStats (String (data .lhs || ' ' ), String (data .rhs || ' ' ))
125+ },
126+ },
92127 watch: {
93128 /* Subscribe to Monaco's onDidUpdateDiff so the counter refreshes
94129 * whenever the diff is recomputed (initial load, model swap, etc).
@@ -121,6 +156,10 @@ export default Vue.extend({
121156 }
122157 },
123158 methods: {
159+ /* Exposed so the template can sign-prefix the delta (+15 / -15 / 0). */
160+ formatDelta(delta : number ): string {
161+ return formatDelta (delta )
162+ },
124163 handleCtrlC(event : KeyboardEvent ) {
125164 const { metaKey, ctrlKey, key } = event
126165 if (
@@ -292,6 +331,62 @@ export default Vue.extend({
292331 gap : 8px ;
293332}
294333
334+ /* Center cluster: per-side line counts (A / B) and the net delta (Δ).
335+ * Reads as quiet metadata — tabular figures so the numbers don't jitter
336+ * as they change, muted key letters, and a colour-coded delta. */
337+ .noden-line-stats {
338+ display : inline-flex ;
339+ align-items : center ;
340+ gap : 14px ;
341+ font-size : 0.8rem ;
342+ font-weight : 500 ;
343+ font-variant-numeric : tabular-nums ;
344+ color : var (--noden-text-secondary , #64748b );
345+ user-select : none ;
346+ }
347+ .noden-line-stat {
348+ display : inline-flex ;
349+ align-items : baseline ;
350+ gap : 4px ;
351+ }
352+ .noden-line-stat-key {
353+ font-size : 0.7rem ;
354+ font-weight : 600 ;
355+ letter-spacing : 0.04em ;
356+ opacity : 0.7 ;
357+ }
358+ .noden-line-stat-val {
359+ color : var (--noden-text-primary , #1e3a5f );
360+ }
361+ /* Dark-theme overrides kept above the specificity-3 delta rules so the
362+ * cascade reads in ascending specificity (stylelint no-descending). */
363+ .dark .noden-line-stats {
364+ color : #9ca3af ;
365+ }
366+ .dark .noden-line-stat-val {
367+ color : #e5e7eb ;
368+ }
369+ .noden-line-stat-delta.is-positive .noden-line-stat-val {
370+ color : #16a34a ;
371+ }
372+ .noden-line-stat-delta.is-negative .noden-line-stat-val {
373+ color : #dc2626 ;
374+ }
375+ .dark .noden-line-stat-delta.is-positive .noden-line-stat-val {
376+ color : #4ade80 ;
377+ }
378+ .dark .noden-line-stat-delta.is-negative .noden-line-stat-val {
379+ color : #f87171 ;
380+ }
381+
382+ /* On narrow viewports the action bar gets crowded; drop the line stats
383+ * rather than let them wrap the bar onto a second row. */
384+ @media (max-width : 640px ) {
385+ .noden-line-stats {
386+ display : none ;
387+ }
388+ }
389+
295390/* Counter pill between Previous / Next. Reads as a soft label, not
296391 * an interactive control — no background hover, just sits between
297392 * the buttons showing "<idx>/<total> changes". Fixed min-width to
0 commit comments