v0.11.0 dc50343

deduplicator.go RawHistoryFolder

1package messaging
2
3import (
4	"crypto/sha256"
5	"fmt"
6	"log"
7	"sync"
8	"time"
9)
10
11type RequestFingerprint struct {
12	URL       string
13	WebViewID string
14	Timestamp int64
15	Hash      string
16}
17
18type PaneRequestDeduplicator struct {
19	mu              sync.RWMutex
20	recentRequests  map[string]*RequestFingerprint
21	requestIDs      map[string]bool
22	debounceWindow  time.Duration
23	cleanupInterval time.Duration
24	lastCleanup     time.Time
25}
26
27func NewPaneRequestDeduplicator() *PaneRequestDeduplicator {
28	return &PaneRequestDeduplicator{
29		recentRequests:  make(map[string]*RequestFingerprint),
30		requestIDs:      make(map[string]bool),
31		debounceWindow:  200 * time.Millisecond,
32		cleanupInterval: 5 * time.Second,
33		lastCleanup:     time.Now(),
34	}
35}
36
37func (d *PaneRequestDeduplicator) generateFingerprint(intent *WindowIntent, webViewID string) string {
38	normalizedTime := intent.Timestamp / 100
39	data := fmt.Sprintf("%s:%s:%d", intent.URL, webViewID, normalizedTime)
40	hash := sha256.Sum256([]byte(data))
41	return fmt.Sprintf("%x", hash[:8])
42}
43
44func (d *PaneRequestDeduplicator) IsDuplicate(intent *WindowIntent, webViewID string) (bool, string) {
45	d.mu.Lock()
46	defer d.mu.Unlock()
47
48	if time.Since(d.lastCleanup) > d.cleanupInterval {
49		d.cleanup()
50	}
51
52	if intent.RequestID != "" {
53		if d.requestIDs[intent.RequestID] {
54			return true, fmt.Sprintf("duplicate request ID: %s", intent.RequestID)
55		}
56	}
57
58	fingerprint := d.generateFingerprint(intent, webViewID)
59
60	if existing, exists := d.recentRequests[fingerprint]; exists {
61		timeDiff := time.Duration(intent.Timestamp-existing.Timestamp) * time.Millisecond
62		if timeDiff < d.debounceWindow {
63			return true, fmt.Sprintf("duplicate content fingerprint: %s (within %v)", fingerprint, timeDiff)
64		}
65	}
66
67	d.recentRequests[fingerprint] = &RequestFingerprint{
68		URL:       intent.URL,
69		WebViewID: webViewID,
70		Timestamp: intent.Timestamp,
71		Hash:      fingerprint,
72	}
73
74	if intent.RequestID != "" {
75		d.requestIDs[intent.RequestID] = true
76	}
77
78	return false, ""
79}
80
81func (d *PaneRequestDeduplicator) cleanup() {
82	now := time.Now().UnixMilli()
83	cutoff := now - int64(d.debounceWindow*3/time.Millisecond)
84
85	for key, req := range d.recentRequests {
86		if now-req.Timestamp > cutoff {
87			delete(d.recentRequests, key)
88		}
89	}
90
91	if len(d.requestIDs) > 100 {
92		d.requestIDs = make(map[string]bool)
93	}
94
95	d.lastCleanup = time.Now()
96	log.Printf("[deduplicator] Cleanup completed: %d fingerprints, %d request IDs", len(d.recentRequests), len(d.requestIDs))
97}
98
99// ClearRequestID removes a specific request ID from the deduplication cache
100// This allows the same RequestID to be used again (e.g., for OAuth verification -> real popup flow)
101func (d *PaneRequestDeduplicator) ClearRequestID(requestID string) {
102	if requestID == "" {
103		return
104	}
105
106	d.mu.Lock()
107	defer d.mu.Unlock()
108
109	if d.requestIDs[requestID] {
110		delete(d.requestIDs, requestID)
111		log.Printf("[deduplicator] Cleared request ID: %s", requestID)
112	}
113}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close