fix(palette): report malformed JRS ranks distinctly and drop write-only feedback state
Parse failures now say "rank must be one positive decimal" instead of reusing the stale-target message, and the write-only paletteFeedback field is gone in favor of the already-rendered paletteHints.Feedback.
- commit
- 9d575ec9a946dacaf23b6aa39eec881ab7652a26
- author
- brice · · 2026-07-11 14:04 UTC
- parent
- 7a1586d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +37 -29
M internal/usecase/daemon/overlay_runtime.go +0 -3
| @@ -24,7 +24,6 @@ type overlayRuntime struct { | |||
| 24 | 24 | paletteRecent []recentSession // immutable for this palette interaction | |
| 25 | 25 | paletteGeneration uint64 | |
| 26 | 26 | paletteHints palette.ContextualHints | |
| 27 | - | paletteFeedback string | |
| 28 | 27 | palettePending []byte | |
| 29 | 28 | ||
| 30 | 29 | promptMu sync.Mutex | |
| @@ -221,8 +220,6 @@ func (rt *overlayRuntime) SnapshotForRender() *overlayRenderSnapshot { | |||
| 221 | 220 | hints.Recent = append([]palette.RecentSessionHint(nil), hints.Recent...) | |
| 222 | 221 | snap.paletteHints = &hints | |
| 223 | 222 | snap.paletteRecent = append([]recentSession(nil), rt.paletteRecent...) | |
| 224 | - | } | |
| 225 | - | if snap.paletteActive { | |
| 226 | 223 | snap.paletteLocked = true | |
| 227 | 224 | } else { | |
| 228 | 225 | rt.paletteMu.Unlock() | |
| @@ -24,7 +24,6 @@ type overlayRuntime struct { | |||||
| 24 | paletteRecent []recentSession // immutable for this palette interaction | 24 | paletteRecent []recentSession // immutable for this palette interaction | ||
| 25 | paletteGeneration uint64 | 25 | paletteGeneration uint64 | ||
| 26 | paletteHints palette.ContextualHints | 26 | paletteHints palette.ContextualHints | ||
| 27 | - | paletteFeedback string | |||
| 28 | palettePending []byte | 27 | palettePending []byte | ||
| 29 | 28 | ||||
| 30 | promptMu sync.Mutex | 29 | promptMu sync.Mutex | ||
| @@ -221,8 +220,6 @@ func (rt *overlayRuntime) SnapshotForRender() *overlayRenderSnapshot { | |||||
| 221 | hints.Recent = append([]palette.RecentSessionHint(nil), hints.Recent...) | 220 | hints.Recent = append([]palette.RecentSessionHint(nil), hints.Recent...) | ||
| 222 | snap.paletteHints = &hints | 221 | snap.paletteHints = &hints | ||
| 223 | snap.paletteRecent = append([]recentSession(nil), rt.paletteRecent...) | 222 | snap.paletteRecent = append([]recentSession(nil), rt.paletteRecent...) | ||
| 224 | - | } | |||
| 225 | - | if snap.paletteActive { | |||
| 226 | snap.paletteLocked = true | 223 | snap.paletteLocked = true | ||
| 227 | } else { | 224 | } else { | ||
| 228 | rt.paletteMu.Unlock() | 225 | rt.paletteMu.Unlock() | ||
M internal/usecase/daemon/palette.go +14 -20
| @@ -40,7 +40,6 @@ func (d *Daemon) enterPalette(sess *session, ac *attachedClient) { | |||
| 40 | 40 | ac.overlays.palette = palette.New(commands) | |
| 41 | 41 | ac.overlays.paletteRecent = recent | |
| 42 | 42 | ac.overlays.paletteHints = palette.ContextualHints{} | |
| 43 | - | ac.overlays.paletteFeedback = "" | |
| 44 | 43 | ac.overlays.palettePending = nil | |
| 45 | 44 | ac.overlays.paletteMu.Unlock() | |
| 46 | 45 | d.paint(sess, ac, true) | |
| @@ -120,12 +119,10 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 120 | 119 | routeOverlayBytes(data, &ac.overlays.palettePending, overlayEvents{ | |
| 121 | 120 | rune: func(r rune) { | |
| 122 | 121 | ac.overlays.palette.Insert(r) | |
| 123 | - | ac.overlays.paletteFeedback = "" | |
| 124 | 122 | changed = true | |
| 125 | 123 | }, | |
| 126 | 124 | backspace: func() { | |
| 127 | 125 | ac.overlays.palette.Backspace() | |
| 128 | - | ac.overlays.paletteFeedback = "" | |
| 129 | 126 | changed = true | |
| 130 | 127 | }, | |
| 131 | 128 | up: func() { ac.overlays.palette.Up(); changed = true }, | |
| @@ -135,7 +132,6 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 135 | 132 | var selected bool | |
| 136 | 133 | cmd, selected = ac.overlays.palette.Selected() | |
| 137 | 134 | if !selected { | |
| 138 | - | ac.overlays.paletteFeedback = "invalid command arguments" | |
| 139 | 135 | changed = true | |
| 140 | 136 | return | |
| 141 | 137 | } | |
| @@ -147,13 +143,12 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 147 | 143 | if cmd.Slug == "jump-recent-session" { | |
| 148 | 144 | action, valid := palette.ParseAction([]command.Command{cmd}, rawQuery) | |
| 149 | 145 | if !valid { | |
| 150 | - | ac.overlays.paletteFeedback = "invalid command arguments" | |
| 151 | 146 | changed = true | |
| 152 | 147 | return | |
| 153 | 148 | } | |
| 154 | 149 | args = action.Args | |
| 150 | + | recent = append([]recentSession(nil), ac.overlays.paletteRecent...) | |
| 155 | 151 | } | |
| 156 | - | recent = append([]recentSession(nil), ac.overlays.paletteRecent...) | |
| 157 | 152 | generation = ac.overlays.paletteGeneration | |
| 158 | 153 | execute = true | |
| 159 | 154 | }, | |
| @@ -168,7 +163,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 168 | 163 | } | |
| 169 | 164 | } | |
| 170 | 165 | if cancel { | |
| 171 | - | d.clearPaletteLocked(ac) | |
| 166 | + | ac.clearPaletteLocked() | |
| 172 | 167 | } | |
| 173 | 168 | ac.overlays.paletteMu.Unlock() | |
| 174 | 169 | if cancel { | |
| @@ -187,7 +182,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 187 | 182 | if cmd.Slug == "jump-recent-session" { | |
| 188 | 183 | rank, err := command.ParsePositiveDecimal(args) | |
| 189 | 184 | if err != nil { | |
| 190 | - | d.paletteFailure(ac, generation, rawQuery, "requested recent session is unavailable") | |
| 185 | + | ac.paletteFailure(generation, rawQuery, "rank must be one positive decimal") | |
| 191 | 186 | d.paint(sess, ac, true) | |
| 192 | 187 | return | |
| 193 | 188 | } | |
| @@ -195,22 +190,22 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 195 | 190 | // the validation above, so closing is contingent on the committed switch. | |
| 196 | 191 | exec := paletteExec{d: d, sess: sess, ac: ac, recent: recent} | |
| 197 | 192 | if err := exec.JumpRecentSession(rank); err != nil { | |
| 198 | - | d.paletteFailure(ac, generation, rawQuery, "requested recent session is unavailable") | |
| 193 | + | ac.paletteFailure(generation, rawQuery, "requested recent session is unavailable") | |
| 199 | 194 | d.paint(sess, ac, true) | |
| 200 | 195 | return | |
| 201 | 196 | } | |
| 202 | 197 | // Publication remains generation-safe; do not let stale work close a | |
| 203 | 198 | // newer palette interaction. | |
| 204 | - | if d.closeExecutedPalette(ac, generation, rawQuery) { | |
| 199 | + | if ac.closeExecutedPalette(generation, rawQuery) { | |
| 205 | 200 | d.recordPaletteUse(cmd.Code) | |
| 206 | 201 | d.paint(ac.currentSession(), ac, true) | |
| 207 | 202 | } | |
| 208 | 203 | return | |
| 209 | 204 | } | |
| 210 | - | if !d.closeExecutedPalette(ac, generation, rawQuery) { | |
| 205 | + | if !ac.closeExecutedPalette(generation, rawQuery) { | |
| 211 | 206 | return | |
| 212 | 207 | } | |
| 213 | - | if err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac, recent: recent}, args); err != nil { | |
| 208 | + | if err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac}, args); err != nil { | |
| 214 | 209 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | |
| 215 | 210 | } else { | |
| 216 | 211 | d.recordPaletteUse(cmd.Code) | |
| @@ -225,35 +220,34 @@ func paletteArgs(query string, cmd command.Command) []string { | |||
| 225 | 220 | return action.Args | |
| 226 | 221 | } | |
| 227 | 222 | ||
| 228 | - | func (d *Daemon) clearPaletteLocked(ac *attachedClient) { | |
| 223 | + | func (ac *attachedClient) clearPaletteLocked() { | |
| 229 | 224 | ac.overlays.paletteGeneration++ | |
| 230 | 225 | ac.overlays.palette = nil | |
| 231 | 226 | ac.overlays.paletteRecent = nil | |
| 232 | 227 | ac.overlays.paletteHints = palette.ContextualHints{} | |
| 233 | - | ac.overlays.paletteFeedback = "" | |
| 234 | 228 | ac.overlays.palettePending = nil | |
| 235 | 229 | } | |
| 236 | 230 | ||
| 237 | - | func (d *Daemon) paletteFailure(ac *attachedClient, generation uint64, rawQuery, feedback string) { | |
| 231 | + | func (ac *attachedClient) paletteFailure(generation uint64, rawQuery, feedback string) { | |
| 238 | 232 | ac.overlays.paletteMu.Lock() | |
| 239 | 233 | defer ac.overlays.paletteMu.Unlock() | |
| 240 | 234 | if ac.overlays.palette == nil || ac.overlays.paletteGeneration != generation || ac.overlays.palette.Query() != rawQuery { | |
| 241 | 235 | return | |
| 242 | 236 | } | |
| 243 | - | ac.overlays.paletteFeedback = feedback | |
| 244 | - | // Rendering consumes the immutable contextual hint snapshot, not Model's | |
| 245 | - | // feedback state. Preserve ranks while making stale-target feedback visible. | |
| 237 | + | // Rendering consumes the immutable contextual hint snapshot; preserve ranks | |
| 238 | + | // while making stale-target feedback visible. | |
| 246 | 239 | if ac.overlays.paletteHints.Kind == command.ContextHintRecentSessions { | |
| 247 | 240 | ac.overlays.paletteHints.Feedback = feedback | |
| 248 | 241 | } | |
| 249 | 242 | } | |
| 250 | - | func (d *Daemon) closeExecutedPalette(ac *attachedClient, generation uint64, rawQuery string) bool { | |
| 243 | + | ||
| 244 | + | func (ac *attachedClient) closeExecutedPalette(generation uint64, rawQuery string) bool { | |
| 251 | 245 | ac.overlays.paletteMu.Lock() | |
| 252 | 246 | defer ac.overlays.paletteMu.Unlock() | |
| 253 | 247 | if ac.overlays.palette == nil || ac.overlays.paletteGeneration != generation || ac.overlays.palette.Query() != rawQuery { | |
| 254 | 248 | return false | |
| 255 | 249 | } | |
| 256 | - | d.clearPaletteLocked(ac) | |
| 250 | + | ac.clearPaletteLocked() | |
| 257 | 251 | return true | |
| 258 | 252 | } | |
| 259 | 253 | ||
| @@ -40,7 +40,6 @@ func (d *Daemon) enterPalette(sess *session, ac *attachedClient) { | |||||
| 40 | ac.overlays.palette = palette.New(commands) | 40 | ac.overlays.palette = palette.New(commands) | ||
| 41 | ac.overlays.paletteRecent = recent | 41 | ac.overlays.paletteRecent = recent | ||
| 42 | ac.overlays.paletteHints = palette.ContextualHints{} | 42 | ac.overlays.paletteHints = palette.ContextualHints{} | ||
| 43 | - | ac.overlays.paletteFeedback = "" | |||
| 44 | ac.overlays.palettePending = nil | 43 | ac.overlays.palettePending = nil | ||
| 45 | ac.overlays.paletteMu.Unlock() | 44 | ac.overlays.paletteMu.Unlock() | ||
| 46 | d.paint(sess, ac, true) | 45 | d.paint(sess, ac, true) | ||
| @@ -120,12 +119,10 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 120 | routeOverlayBytes(data, &ac.overlays.palettePending, overlayEvents{ | 119 | routeOverlayBytes(data, &ac.overlays.palettePending, overlayEvents{ | ||
| 121 | rune: func(r rune) { | 120 | rune: func(r rune) { | ||
| 122 | ac.overlays.palette.Insert(r) | 121 | ac.overlays.palette.Insert(r) | ||
| 123 | - | ac.overlays.paletteFeedback = "" | |||
| 124 | changed = true | 122 | changed = true | ||
| 125 | }, | 123 | }, | ||
| 126 | backspace: func() { | 124 | backspace: func() { | ||
| 127 | ac.overlays.palette.Backspace() | 125 | ac.overlays.palette.Backspace() | ||
| 128 | - | ac.overlays.paletteFeedback = "" | |||
| 129 | changed = true | 126 | changed = true | ||
| 130 | }, | 127 | }, | ||
| 131 | up: func() { ac.overlays.palette.Up(); changed = true }, | 128 | up: func() { ac.overlays.palette.Up(); changed = true }, | ||
| @@ -135,7 +132,6 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 135 | var selected bool | 132 | var selected bool | ||
| 136 | cmd, selected = ac.overlays.palette.Selected() | 133 | cmd, selected = ac.overlays.palette.Selected() | ||
| 137 | if !selected { | 134 | if !selected { | ||
| 138 | - | ac.overlays.paletteFeedback = "invalid command arguments" | |||
| 139 | changed = true | 135 | changed = true | ||
| 140 | return | 136 | return | ||
| 141 | } | 137 | } | ||
| @@ -147,13 +143,12 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 147 | if cmd.Slug == "jump-recent-session" { | 143 | if cmd.Slug == "jump-recent-session" { | ||
| 148 | action, valid := palette.ParseAction([]command.Command{cmd}, rawQuery) | 144 | action, valid := palette.ParseAction([]command.Command{cmd}, rawQuery) | ||
| 149 | if !valid { | 145 | if !valid { | ||
| 150 | - | ac.overlays.paletteFeedback = "invalid command arguments" | |||
| 151 | changed = true | 146 | changed = true | ||
| 152 | return | 147 | return | ||
| 153 | } | 148 | } | ||
| 154 | args = action.Args | 149 | args = action.Args | ||
| 150 | + | recent = append([]recentSession(nil), ac.overlays.paletteRecent...) | |||
| 155 | } | 151 | } | ||
| 156 | - | recent = append([]recentSession(nil), ac.overlays.paletteRecent...) | |||
| 157 | generation = ac.overlays.paletteGeneration | 152 | generation = ac.overlays.paletteGeneration | ||
| 158 | execute = true | 153 | execute = true | ||
| 159 | }, | 154 | }, | ||
| @@ -168,7 +163,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 168 | } | 163 | } | ||
| 169 | } | 164 | } | ||
| 170 | if cancel { | 165 | if cancel { | ||
| 171 | - | d.clearPaletteLocked(ac) | 166 | + | ac.clearPaletteLocked() |
| 172 | } | 167 | } | ||
| 173 | ac.overlays.paletteMu.Unlock() | 168 | ac.overlays.paletteMu.Unlock() | ||
| 174 | if cancel { | 169 | if cancel { | ||
| @@ -187,7 +182,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 187 | if cmd.Slug == "jump-recent-session" { | 182 | if cmd.Slug == "jump-recent-session" { | ||
| 188 | rank, err := command.ParsePositiveDecimal(args) | 183 | rank, err := command.ParsePositiveDecimal(args) | ||
| 189 | if err != nil { | 184 | if err != nil { | ||
| 190 | - | d.paletteFailure(ac, generation, rawQuery, "requested recent session is unavailable") | 185 | + | ac.paletteFailure(generation, rawQuery, "rank must be one positive decimal") |
| 191 | d.paint(sess, ac, true) | 186 | d.paint(sess, ac, true) | ||
| 192 | return | 187 | return | ||
| 193 | } | 188 | } | ||
| @@ -195,22 +190,22 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 195 | // the validation above, so closing is contingent on the committed switch. | 190 | // the validation above, so closing is contingent on the committed switch. | ||
| 196 | exec := paletteExec{d: d, sess: sess, ac: ac, recent: recent} | 191 | exec := paletteExec{d: d, sess: sess, ac: ac, recent: recent} | ||
| 197 | if err := exec.JumpRecentSession(rank); err != nil { | 192 | if err := exec.JumpRecentSession(rank); err != nil { | ||
| 198 | - | d.paletteFailure(ac, generation, rawQuery, "requested recent session is unavailable") | 193 | + | ac.paletteFailure(generation, rawQuery, "requested recent session is unavailable") |
| 199 | d.paint(sess, ac, true) | 194 | d.paint(sess, ac, true) | ||
| 200 | return | 195 | return | ||
| 201 | } | 196 | } | ||
| 202 | // Publication remains generation-safe; do not let stale work close a | 197 | // Publication remains generation-safe; do not let stale work close a | ||
| 203 | // newer palette interaction. | 198 | // newer palette interaction. | ||
| 204 | - | if d.closeExecutedPalette(ac, generation, rawQuery) { | 199 | + | if ac.closeExecutedPalette(generation, rawQuery) { |
| 205 | d.recordPaletteUse(cmd.Code) | 200 | d.recordPaletteUse(cmd.Code) | ||
| 206 | d.paint(ac.currentSession(), ac, true) | 201 | d.paint(ac.currentSession(), ac, true) | ||
| 207 | } | 202 | } | ||
| 208 | return | 203 | return | ||
| 209 | } | 204 | } | ||
| 210 | - | if !d.closeExecutedPalette(ac, generation, rawQuery) { | 205 | + | if !ac.closeExecutedPalette(generation, rawQuery) { |
| 211 | return | 206 | return | ||
| 212 | } | 207 | } | ||
| 213 | - | if err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac, recent: recent}, args); err != nil { | 208 | + | if err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac}, args); err != nil { |
| 214 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | 209 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | ||
| 215 | } else { | 210 | } else { | ||
| 216 | d.recordPaletteUse(cmd.Code) | 211 | d.recordPaletteUse(cmd.Code) | ||
| @@ -225,35 +220,34 @@ func paletteArgs(query string, cmd command.Command) []string { | |||||
| 225 | return action.Args | 220 | return action.Args | ||
| 226 | } | 221 | } | ||
| 227 | 222 | ||||
| 228 | - | func (d *Daemon) clearPaletteLocked(ac *attachedClient) { | 223 | + | func (ac *attachedClient) clearPaletteLocked() { |
| 229 | ac.overlays.paletteGeneration++ | 224 | ac.overlays.paletteGeneration++ | ||
| 230 | ac.overlays.palette = nil | 225 | ac.overlays.palette = nil | ||
| 231 | ac.overlays.paletteRecent = nil | 226 | ac.overlays.paletteRecent = nil | ||
| 232 | ac.overlays.paletteHints = palette.ContextualHints{} | 227 | ac.overlays.paletteHints = palette.ContextualHints{} | ||
| 233 | - | ac.overlays.paletteFeedback = "" | |||
| 234 | ac.overlays.palettePending = nil | 228 | ac.overlays.palettePending = nil | ||
| 235 | } | 229 | } | ||
| 236 | 230 | ||||
| 237 | - | func (d *Daemon) paletteFailure(ac *attachedClient, generation uint64, rawQuery, feedback string) { | 231 | + | func (ac *attachedClient) paletteFailure(generation uint64, rawQuery, feedback string) { |
| 238 | ac.overlays.paletteMu.Lock() | 232 | ac.overlays.paletteMu.Lock() | ||
| 239 | defer ac.overlays.paletteMu.Unlock() | 233 | defer ac.overlays.paletteMu.Unlock() | ||
| 240 | if ac.overlays.palette == nil || ac.overlays.paletteGeneration != generation || ac.overlays.palette.Query() != rawQuery { | 234 | if ac.overlays.palette == nil || ac.overlays.paletteGeneration != generation || ac.overlays.palette.Query() != rawQuery { | ||
| 241 | return | 235 | return | ||
| 242 | } | 236 | } | ||
| 243 | - | ac.overlays.paletteFeedback = feedback | 237 | + | // Rendering consumes the immutable contextual hint snapshot; preserve ranks |
| 244 | - | // Rendering consumes the immutable contextual hint snapshot, not Model's | 238 | + | // while making stale-target feedback visible. |
| 245 | - | // feedback state. Preserve ranks while making stale-target feedback visible. | |||
| 246 | if ac.overlays.paletteHints.Kind == command.ContextHintRecentSessions { | 239 | if ac.overlays.paletteHints.Kind == command.ContextHintRecentSessions { | ||
| 247 | ac.overlays.paletteHints.Feedback = feedback | 240 | ac.overlays.paletteHints.Feedback = feedback | ||
| 248 | } | 241 | } | ||
| 249 | } | 242 | } | ||
| 250 | - | func (d *Daemon) closeExecutedPalette(ac *attachedClient, generation uint64, rawQuery string) bool { | 243 | + | |
| 244 | + | func (ac *attachedClient) closeExecutedPalette(generation uint64, rawQuery string) bool { | |||
| 251 | ac.overlays.paletteMu.Lock() | 245 | ac.overlays.paletteMu.Lock() | ||
| 252 | defer ac.overlays.paletteMu.Unlock() | 246 | defer ac.overlays.paletteMu.Unlock() | ||
| 253 | if ac.overlays.palette == nil || ac.overlays.paletteGeneration != generation || ac.overlays.palette.Query() != rawQuery { | 247 | if ac.overlays.palette == nil || ac.overlays.paletteGeneration != generation || ac.overlays.palette.Query() != rawQuery { | ||
| 254 | return false | 248 | return false | ||
| 255 | } | 249 | } | ||
| 256 | - | d.clearPaletteLocked(ac) | 250 | + | ac.clearPaletteLocked() |
| 257 | return true | 251 | return true | ||
| 258 | } | 252 | } | ||
| 259 | 253 | ||||
M internal/usecase/daemon/palette_test.go +23 -6
| @@ -66,7 +66,7 @@ func TestPaletteJRSActivatesOnlyExactContextualHint(t *testing.T) { | |||
| 66 | 66 | d.handleInput(sess, ac, []byte("\b\b\bRNS")) | |
| 67 | 67 | awaitFrame(t, sends, ports.MsgOutput) | |
| 68 | 68 | ac.overlays.paletteMu.Lock() | |
| 69 | - | require.Equal(t, command.ContextHint(0), ac.overlays.paletteHints.Kind) | |
| 69 | + | require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind) | |
| 70 | 70 | ac.overlays.paletteMu.Unlock() | |
| 71 | 71 | } | |
| 72 | 72 | ||
| @@ -94,7 +94,7 @@ func TestPaletteJRSUsesEffectiveOverrideOnly(t *testing.T) { | |||
| 94 | 94 | d.handleInput(ac.currentSession(), ac, []byte("JRS")) | |
| 95 | 95 | awaitFrame(t, sends, ports.MsgOutput) | |
| 96 | 96 | ac.overlays.paletteMu.Lock() | |
| 97 | - | require.Equal(t, command.ContextHint(0), ac.overlays.paletteHints.Kind) | |
| 97 | + | require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind) | |
| 98 | 98 | ac.overlays.paletteMu.Unlock() | |
| 99 | 99 | require.Same(t, d.sessions[domain.SessionID("recent")], ac.currentSession()) | |
| 100 | 100 | d.handleInput(ac.currentSession(), ac, []byte("\r")) | |
| @@ -172,7 +172,7 @@ func TestPaletteJRSDisplacedTargetKeepsInteractionOpen(t *testing.T) { | |||
| 172 | 172 | require.True(t, ac.overlays.paletteActive()) | |
| 173 | 173 | ac.overlays.paletteMu.Lock() | |
| 174 | 174 | require.Equal(t, "JRS 1", ac.overlays.palette.Query()) | |
| 175 | - | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteFeedback) | |
| 175 | + | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteHints.Feedback) | |
| 176 | 176 | ac.overlays.paletteMu.Unlock() | |
| 177 | 177 | } | |
| 178 | 178 | ||
| @@ -188,7 +188,24 @@ func TestPaletteJRSOutOfRangeKeepsPaletteOpenWithoutClamping(t *testing.T) { | |||
| 188 | 188 | require.True(t, ac.overlays.paletteActive()) | |
| 189 | 189 | ac.overlays.paletteMu.Lock() | |
| 190 | 190 | require.Equal(t, "JRS 3", ac.overlays.palette.Query()) | |
| 191 | - | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteFeedback) | |
| 191 | + | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteHints.Feedback) | |
| 192 | + | ac.overlays.paletteMu.Unlock() | |
| 193 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | func TestPaletteJRSMalformedRankFeedback(t *testing.T) { | |
| 197 | + | d, current, ac, sends, releases := newRecentNavigationTestSessions(t) | |
| 198 | + | defer releaseAll(releases) | |
| 199 | + | ||
| 200 | + | d.handleInput(current, ac, []byte("\x1b ")) | |
| 201 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 202 | + | d.handleInput(current, ac, []byte("JRS 0\r")) | |
| 203 | + | ||
| 204 | + | require.Same(t, current, ac.currentSession()) | |
| 205 | + | require.True(t, ac.overlays.paletteActive()) | |
| 206 | + | ac.overlays.paletteMu.Lock() | |
| 207 | + | require.Equal(t, "JRS 0", ac.overlays.palette.Query()) | |
| 208 | + | require.Equal(t, "rank must be one positive decimal", ac.overlays.paletteHints.Feedback) | |
| 192 | 209 | ac.overlays.paletteMu.Unlock() | |
| 193 | 210 | awaitFrame(t, sends, ports.MsgOutput) | |
| 194 | 211 | } | |
| @@ -203,10 +220,10 @@ func TestPaletteFailureDoesNotOverwriteNewerInteraction(t *testing.T) { | |||
| 203 | 220 | staleGeneration := ac.overlays.paletteGeneration | |
| 204 | 221 | ac.overlays.paletteMu.Unlock() | |
| 205 | 222 | d.enterPalette(sess, ac) | |
| 206 | - | d.paletteFailure(ac, staleGeneration, "JRS 1", "stale failure") | |
| 223 | + | ac.paletteFailure(staleGeneration, "JRS 1", "stale failure") | |
| 207 | 224 | ||
| 208 | 225 | ac.overlays.paletteMu.Lock() | |
| 209 | - | require.Empty(t, ac.overlays.paletteFeedback) | |
| 226 | + | require.Empty(t, ac.overlays.paletteHints.Feedback) | |
| 210 | 227 | ac.overlays.paletteMu.Unlock() | |
| 211 | 228 | awaitFrame(t, sends, ports.MsgOutput) | |
| 212 | 229 | awaitFrame(t, sends, ports.MsgOutput) | |
| @@ -66,7 +66,7 @@ func TestPaletteJRSActivatesOnlyExactContextualHint(t *testing.T) { | |||||
| 66 | d.handleInput(sess, ac, []byte("\b\b\bRNS")) | 66 | d.handleInput(sess, ac, []byte("\b\b\bRNS")) | ||
| 67 | awaitFrame(t, sends, ports.MsgOutput) | 67 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 68 | ac.overlays.paletteMu.Lock() | 68 | ac.overlays.paletteMu.Lock() | ||
| 69 | - | require.Equal(t, command.ContextHint(0), ac.overlays.paletteHints.Kind) | 69 | + | require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind) |
| 70 | ac.overlays.paletteMu.Unlock() | 70 | ac.overlays.paletteMu.Unlock() | ||
| 71 | } | 71 | } | ||
| 72 | 72 | ||||
| @@ -94,7 +94,7 @@ func TestPaletteJRSUsesEffectiveOverrideOnly(t *testing.T) { | |||||
| 94 | d.handleInput(ac.currentSession(), ac, []byte("JRS")) | 94 | d.handleInput(ac.currentSession(), ac, []byte("JRS")) | ||
| 95 | awaitFrame(t, sends, ports.MsgOutput) | 95 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 96 | ac.overlays.paletteMu.Lock() | 96 | ac.overlays.paletteMu.Lock() | ||
| 97 | - | require.Equal(t, command.ContextHint(0), ac.overlays.paletteHints.Kind) | 97 | + | require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind) |
| 98 | ac.overlays.paletteMu.Unlock() | 98 | ac.overlays.paletteMu.Unlock() | ||
| 99 | require.Same(t, d.sessions[domain.SessionID("recent")], ac.currentSession()) | 99 | require.Same(t, d.sessions[domain.SessionID("recent")], ac.currentSession()) | ||
| 100 | d.handleInput(ac.currentSession(), ac, []byte("\r")) | 100 | d.handleInput(ac.currentSession(), ac, []byte("\r")) | ||
| @@ -172,7 +172,7 @@ func TestPaletteJRSDisplacedTargetKeepsInteractionOpen(t *testing.T) { | |||||
| 172 | require.True(t, ac.overlays.paletteActive()) | 172 | require.True(t, ac.overlays.paletteActive()) | ||
| 173 | ac.overlays.paletteMu.Lock() | 173 | ac.overlays.paletteMu.Lock() | ||
| 174 | require.Equal(t, "JRS 1", ac.overlays.palette.Query()) | 174 | require.Equal(t, "JRS 1", ac.overlays.palette.Query()) | ||
| 175 | - | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteFeedback) | 175 | + | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteHints.Feedback) |
| 176 | ac.overlays.paletteMu.Unlock() | 176 | ac.overlays.paletteMu.Unlock() | ||
| 177 | } | 177 | } | ||
| 178 | 178 | ||||
| @@ -188,7 +188,24 @@ func TestPaletteJRSOutOfRangeKeepsPaletteOpenWithoutClamping(t *testing.T) { | |||||
| 188 | require.True(t, ac.overlays.paletteActive()) | 188 | require.True(t, ac.overlays.paletteActive()) | ||
| 189 | ac.overlays.paletteMu.Lock() | 189 | ac.overlays.paletteMu.Lock() | ||
| 190 | require.Equal(t, "JRS 3", ac.overlays.palette.Query()) | 190 | require.Equal(t, "JRS 3", ac.overlays.palette.Query()) | ||
| 191 | - | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteFeedback) | 191 | + | require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteHints.Feedback) |
| 192 | + | ac.overlays.paletteMu.Unlock() | |||
| 193 | + | awaitFrame(t, sends, ports.MsgOutput) | |||
| 194 | + | } | |||
| 195 | + | ||||
| 196 | + | func TestPaletteJRSMalformedRankFeedback(t *testing.T) { | |||
| 197 | + | d, current, ac, sends, releases := newRecentNavigationTestSessions(t) | |||
| 198 | + | defer releaseAll(releases) | |||
| 199 | + | ||||
| 200 | + | d.handleInput(current, ac, []byte("\x1b ")) | |||
| 201 | + | awaitFrame(t, sends, ports.MsgOutput) | |||
| 202 | + | d.handleInput(current, ac, []byte("JRS 0\r")) | |||
| 203 | + | ||||
| 204 | + | require.Same(t, current, ac.currentSession()) | |||
| 205 | + | require.True(t, ac.overlays.paletteActive()) | |||
| 206 | + | ac.overlays.paletteMu.Lock() | |||
| 207 | + | require.Equal(t, "JRS 0", ac.overlays.palette.Query()) | |||
| 208 | + | require.Equal(t, "rank must be one positive decimal", ac.overlays.paletteHints.Feedback) | |||
| 192 | ac.overlays.paletteMu.Unlock() | 209 | ac.overlays.paletteMu.Unlock() | ||
| 193 | awaitFrame(t, sends, ports.MsgOutput) | 210 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 194 | } | 211 | } | ||
| @@ -203,10 +220,10 @@ func TestPaletteFailureDoesNotOverwriteNewerInteraction(t *testing.T) { | |||||
| 203 | staleGeneration := ac.overlays.paletteGeneration | 220 | staleGeneration := ac.overlays.paletteGeneration | ||
| 204 | ac.overlays.paletteMu.Unlock() | 221 | ac.overlays.paletteMu.Unlock() | ||
| 205 | d.enterPalette(sess, ac) | 222 | d.enterPalette(sess, ac) | ||
| 206 | - | d.paletteFailure(ac, staleGeneration, "JRS 1", "stale failure") | 223 | + | ac.paletteFailure(staleGeneration, "JRS 1", "stale failure") |
| 207 | 224 | ||||
| 208 | ac.overlays.paletteMu.Lock() | 225 | ac.overlays.paletteMu.Lock() | ||
| 209 | - | require.Empty(t, ac.overlays.paletteFeedback) | 226 | + | require.Empty(t, ac.overlays.paletteHints.Feedback) |
| 210 | ac.overlays.paletteMu.Unlock() | 227 | ac.overlays.paletteMu.Unlock() | ||
| 211 | awaitFrame(t, sends, ports.MsgOutput) | 228 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 212 | awaitFrame(t, sends, ports.MsgOutput) | 229 | awaitFrame(t, sends, ports.MsgOutput) | ||