palette_inventory.go RawHistoryFolder

1package daemon
2
3import (
4	"github.com/bnema/vev/internal/protocol"
5	"github.com/bnema/vev/internal/usecase/palette"
6)
7
8// This file owns the serving-daemon side of navigation inventory: demand
9// emission, publication admission, and imported-row selection. All overlay
10// fields below are guarded by paletteMu alongside the palette model; no
11// lock is held across sends. Helpers never lock internally: callers hold
12// paletteMu for state transitions and release it before any sendControl.
13
14// inventoryDemandEnabled reports whether this attachment may receive
15// inventory demand. The serving daemon emits demand only for the advertised
16// capability; the client independently checks its concrete local source.
17func inventoryDemandEnabled(ac *attachedClient) bool {
18	return ac != nil && ac.navigationCapabilities&protocol.NavigationCapabilityInventory != 0
19}
20
21// openPaletteInventory starts one palette interaction namespace. Admitted
22// keys and retirements never cross interactions.
23func openPaletteInventory(rt *overlayRuntime) uint64 {
24	rt.paletteInventoryOpen = true
25	rt.paletteInventoryInteraction++
26	rt.paletteInventoryPublication = 0
27	rt.paletteInventoryGroups = nil
28	rt.paletteInventorySelected = nil
29	rt.paletteInventoryDemandSent = false
30	return rt.paletteInventoryInteraction
31}
32
33// closePaletteInventory ends the interaction and drops relayed rows.
34// Imported rows vanish with the palette; a reopen starts a new namespace.
35// The pending selection survives the close so a late resolve failure still
36// correlates to the interaction that chose it; the next open clears it.
37func closePaletteInventory(rt *overlayRuntime) {
38	rt.paletteInventoryOpen = false
39	rt.paletteInventoryGroups = nil
40	rt.paletteInventoryDemandSent = false
41}
42
43// takePaletteInventoryClose snapshots a pending close-demand: the overlay
44// must be open for the interaction. Callers send Demand{open:false} after
45// releasing paletteMu, then close through clearPaletteLocked.
46func takePaletteInventoryClose(rt *overlayRuntime) (uint64, bool) {
47	if !rt.paletteInventoryOpen {
48		return 0, false
49	}
50	return rt.paletteInventoryInteraction, true
51}
52
53// sendPaletteInventoryDemand emits an open or close demand on the guarded
54// serving connection. It locks paletteMu only for the emission check, never
55// across the send. A nil effect skips the send while the overlay state still
56// transitions; those paths coincide with attachment teardown or replacement
57// where the client-side relay is cancelled independently. Stale close
58// demands (superseded by a reopen) skip so generations stay ordered.
59func (d *Daemon) sendPaletteInventoryDemand(ac *attachedClient, effect *attachmentEffect, open bool, interaction uint64) {
60	if !inventoryDemandEnabled(ac) || effect == nil || interaction == 0 {
61		return
62	}
63	ac.overlays.paletteMu.Lock()
64	if open {
65		if !ac.overlays.paletteInventoryOpen || ac.overlays.paletteInventoryInteraction != interaction || ac.overlays.paletteInventoryDemandSent {
66			ac.overlays.paletteMu.Unlock()
67			return
68		}
69		ac.overlays.paletteInventoryDemandSent = true
70	} else if ac.overlays.paletteInventoryInteraction != interaction {
71		ac.overlays.paletteMu.Unlock()
72		return
73	}
74	ac.overlays.paletteMu.Unlock()
75	demand := protocol.NavigationInventoryDemand{InteractionGeneration: interaction, Open: open}
76	if protocol.ValidateNavigationInventoryDemand(demand) != nil {
77		return
78	}
79	_ = effect.sendControl(demand)
80}
81
82// admitPaletteInventoryPublication validates and stores one client
83// publication. Older or duplicate publication generations discard: the
84// daemon never steps its display backward. Stale interactions (palette
85// closed or reopened since) drop silently.
86func admitPaletteInventoryPublication(rt *overlayRuntime, message protocol.NavigationInventoryPublication) bool {
87	if protocol.ValidateNavigationInventoryPublication(message) != nil {
88		return false
89	}
90	if !rt.paletteInventoryOpen || message.InteractionGeneration != rt.paletteInventoryInteraction {
91		return false
92	}
93	if message.PublicationGeneration <= rt.paletteInventoryPublication {
94		return false
95	}
96	rt.paletteInventoryPublication = message.PublicationGeneration
97	rt.paletteInventoryGroups = append([]protocol.NavigationInventorySourceGroup(nil), message.Groups...)
98	return true
99}
100
101// copyPaletteInventoryGroups snapshots the admitted relayed groups for
102// projection. It copies under paletteMu and releases it before any capture:
103// callers must never nest it inside registry locks. A closed palette
104// yields nil: imported rows vanish with the overlay.
105func copyPaletteInventoryGroups(ac *attachedClient) []protocol.NavigationInventorySourceGroup {
106	if ac == nil || ac.overlays == nil {
107		return nil
108	}
109	ac.overlays.paletteMu.Lock()
110	defer ac.overlays.paletteMu.Unlock()
111	if !ac.overlays.paletteInventoryOpen {
112		return nil
113	}
114	return append([]protocol.NavigationInventorySourceGroup(nil), ac.overlays.paletteInventoryGroups...)
115}
116
117// importedPaletteResults projects admitted sanitized groups as palette rows
118// with explicit origin qualification. Homonyms across sources stay distinct
119// rows; selection resolves through opaque keys, never labels.
120func importedPaletteResults(groups []protocol.NavigationInventorySourceGroup) []palette.Result {
121	var results []palette.Result
122	for _, group := range groups {
123		if group.Status != protocol.NavigationInventorySourceOK {
124			continue
125		}
126		for _, entry := range group.Entries {
127			origin := entry.DisplayOrigin
128			if origin == "" {
129				origin = group.SourceKey
130			}
131			results = append(results, palette.NewImportedSessionResult(group.SourceKey, entry.EntryKey, entry.Name, origin, entry.State, entry.Reason))
132		}
133	}
134	return results
135}
136
137// appendImportedResults projects admitted relayed rows after native
138// results. Enter starts native-only (fresh namespace); refreshes pick up
139// streamed publications. Stable opaque keys preserve cursor and query
140// across ReplaceResults.
141func appendImportedResults(results []palette.Result, ac *attachedClient) []palette.Result {
142	groups := copyPaletteInventoryGroups(ac)
143	if ac == nil || ac.overlays == nil {
144		return results
145	}
146	localKeys := make(map[string]bool)
147	for _, group := range groups {
148		if group.SourceKey == protocol.NavigationInventoryLocalSourceKey && group.Status == protocol.NavigationInventorySourceOK {
149			for _, entry := range group.Entries {
150				localKeys[entry.EntryKey] = true
151			}
152		}
153	}
154	ac.overlays.paletteMu.Lock()
155	snapshot := ac.overlays.paletteRouteSnapshot
156	ac.overlays.paletteMu.Unlock()
157	filtered := results[:0]
158	for _, result := range results {
159		if action, ok := result.RouteNavigationAction(); ok {
160			entry, found := routeEntryForRef(snapshot, protocol.RouteRef{Key: action.Key, Generation: action.Generation})
161			if found && entry.Kind == protocol.RouteKindLocal && localKeys[navigationInventoryEntryKey(entry.Target.LifecycleID, entry.Target.SessionName)] {
162				continue
163			}
164		}
165		filtered = append(filtered, result)
166	}
167	return append(filtered, importedPaletteResults(groups)...)
168}
169
170// inventoryFailureNotice maps a bounded relay failure code to palette
171// feedback through existing conventions. Native commands stay available:
172// only the contextual feedback line changes.
173func inventoryFailureNotice(code protocol.NavigationInventoryFailureCode) string {
174	switch code {
175	case protocol.NavigationInventoryStaleIdentity, protocol.NavigationInventoryInvalidTarget:
176		return "that destination is no longer available"
177	case protocol.NavigationInventorySourceGone:
178		return "local inventory source unavailable"
179	case protocol.NavigationInventoryIncompatible:
180		return "incompatible session version"
181	case protocol.NavigationInventoryNavigationFailed:
182		return "couldn't switch sessions"
183	case protocol.NavigationInventoryRestoreFailed:
184		return "couldn't restore the original session"
185	default:
186		return "inventory selection unavailable"
187	}
188}
189
190// freezePaletteInventorySelection records the chosen imported row before its
191// Selection crosses the wire. Closing after selection must not cancel the
192// client's pending navigation operation.
193func freezePaletteInventorySelection(rt *overlayRuntime, selection protocol.NavigationInventorySelection) {
194	rt.paletteInventorySelected = &selection
195}

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 12

Tags 11

↑↓ moveenter openesc close