diff --git a/lua/opencode/ui/renderer.lua b/lua/opencode/ui/renderer.lua index 6e16e010..15d28051 100644 --- a/lua/opencode/ui/renderer.lua +++ b/lua/opencode/ui/renderer.lua @@ -839,8 +839,31 @@ function M.on_session_updated(properties) if not properties or not properties.info or not state.active_session then return end - if not vim.deep_equal(state.active_session.revert, properties.info.revert) then - state.active_session.revert = properties.info.revert + + local updated_session = properties.info + if not updated_session.id or updated_session.id ~= state.active_session.id then + return + end + + local current_session = state.active_session + local revert_changed = not vim.deep_equal(current_session.revert, updated_session.revert) + local previous_title = current_session.title + + local merged_session = vim.tbl_deep_extend('force', vim.deepcopy(current_session), updated_session) + + if not vim.deep_equal(current_session, merged_session) then + -- mutate existing `state.active_session` table in place + -- reassigning would cause UI flickering on frequent `session.updated` events since it triggers a full rerender + for key, value in pairs(merged_session) do + current_session[key] = value + end + + if updated_session.title and updated_session.title ~= previous_title then + require('opencode.ui.topbar').render() + end + end + + if revert_changed then M._render_full_session_data(state.messages) end end diff --git a/tests/data/redo-all.expected.json b/tests/data/redo-all.expected.json index e8f3e3e5..d2f2fd75 100644 --- a/tests/data/redo-all.expected.json +++ b/tests/data/redo-all.expected.json @@ -1 +1 @@ -{"extmarks":[[1,1,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[["▌󰭻 ","OpencodeMessageRoleUser"],[" "],["USER","OpencodeMessageRoleUser"],["","OpencodeHint"],[" (2025-10-20 15:20:02)","OpencodeHint"],[" [msg_a0234c0b7001y2o9S1jMaNVZar]","OpencodeHint"]]}],[2,2,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[3,3,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[4,4,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[5,5,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[6,8,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:04)","OpencodeHint"],[" [msg_a0234c7960011LTxTvD94hfWCi]","OpencodeHint"]]}],[7,12,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[8,13,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[9,14,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[10,15,0,{"hl_eol":true,"end_right_gravity":false,"ns_id":3,"virt_text_pos":"overlay","virt_text_repeat_linebreak":false,"priority":5000,"virt_text_hide":false,"virt_text":[["-","OpencodeDiffDelete"]],"hl_group":"OpencodeDiffDelete","end_col":0,"right_gravity":true,"end_row":16}],[11,15,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[12,16,0,{"hl_eol":true,"end_right_gravity":false,"ns_id":3,"virt_text_pos":"overlay","virt_text_repeat_linebreak":false,"priority":5000,"virt_text_hide":false,"virt_text":[["+","OpencodeDiffAdd"]],"hl_group":"OpencodeDiffAdd","end_col":0,"right_gravity":true,"end_row":17}],[13,16,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[14,17,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[15,18,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[16,19,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[17,20,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[18,25,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:09)","OpencodeHint"],[" [msg_a0234d8fb001SXyngLjuKSuxOY]","OpencodeHint"]]}],[19,30,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[["▌󰭻 ","OpencodeMessageRoleUser"],[" "],["USER","OpencodeMessageRoleUser"],["","OpencodeHint"],[" (2025-10-20 15:20:11)","OpencodeHint"],[" [msg_a0234e308001SKl5bQUibp5gtI]","OpencodeHint"]]}],[20,31,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[21,32,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[22,35,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:11)","OpencodeHint"],[" [msg_a0234e31f001m4EsQdPmY3PTtS]","OpencodeHint"]]}],[23,42,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:16)","OpencodeHint"],[" [msg_a0234f482001PQbMjWc6W8s0eF]","OpencodeHint"]]}],[24,46,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[25,47,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[26,48,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[27,49,0,{"hl_eol":true,"end_right_gravity":false,"ns_id":3,"virt_text_pos":"overlay","virt_text_repeat_linebreak":false,"priority":5000,"virt_text_hide":false,"virt_text":[["-","OpencodeDiffDelete"]],"hl_group":"OpencodeDiffDelete","end_col":0,"right_gravity":true,"end_row":50}],[28,49,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[29,50,0,{"hl_eol":true,"end_right_gravity":false,"ns_id":3,"virt_text_pos":"overlay","virt_text_repeat_linebreak":false,"priority":5000,"virt_text_hide":false,"virt_text":[["+","OpencodeDiffAdd"]],"hl_group":"OpencodeDiffAdd","end_col":0,"right_gravity":true,"end_row":51}],[30,50,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[31,51,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[32,52,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[33,53,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[34,54,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[35,59,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:17)","OpencodeHint"],[" [msg_a0234f9c6001JCKYaca1HHwwx6]","OpencodeHint"]]}],[36,64,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[["▌󰭻 ","OpencodeMessageRoleUser"],[" "],["USER","OpencodeMessageRoleUser"],["","OpencodeHint"],[" (2025-10-20 15:22:29)","OpencodeHint"],[" [msg_a0236fd1c001TlwqL8fwvq529i]","OpencodeHint"]]}],[37,65,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[38,66,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeMessageRoleUser"]]}],[39,69,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:22:29)","OpencodeHint"],[" [msg_a0236fd57001pTnTjSBdFlleCb]","OpencodeHint"]]}],[40,76,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:22:34)","OpencodeHint"],[" [msg_a02371241001PBQAsr8Oc9hqNI]","OpencodeHint"]]}],[41,80,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[42,81,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[43,82,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[44,83,0,{"hl_eol":true,"end_right_gravity":false,"ns_id":3,"virt_text_pos":"overlay","virt_text_repeat_linebreak":false,"priority":5000,"virt_text_hide":false,"virt_text":[["-","OpencodeDiffDelete"]],"hl_group":"OpencodeDiffDelete","end_col":0,"right_gravity":true,"end_row":84}],[45,83,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[46,84,0,{"hl_eol":true,"end_right_gravity":false,"ns_id":3,"virt_text_pos":"overlay","virt_text_repeat_linebreak":false,"priority":5000,"virt_text_hide":false,"virt_text":[["+","OpencodeDiffAdd"]],"hl_group":"OpencodeDiffAdd","end_col":0,"right_gravity":true,"end_row":85}],[47,84,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[48,85,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[49,86,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[50,87,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[51,88,0,{"virt_text_hide":false,"virt_text_win_col":-1,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"right_gravity":true,"virt_text":[["▌","OpencodeToolBorder"]]}],[52,93,0,{"virt_text_hide":false,"virt_text_win_col":-3,"ns_id":3,"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"right_gravity":true,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:22:39)","OpencodeHint"],[" [msg_a023723d0001r87MaJThFssUw1]","OpencodeHint"]]}]],"timestamp":1769172213,"actions":[{"text":"[R]evert file","display_line":90,"args":["d988cc85565b99017d40ad8baea20225165be9d5"],"type":"diff_revert_selected_file","range":{"from":90,"to":90},"key":"R"},{"text":"Revert [A]ll","display_line":90,"args":["d988cc85565b99017d40ad8baea20225165be9d5"],"type":"diff_revert_all","range":{"from":90,"to":90},"key":"A"},{"text":"[D]iff","display_line":90,"args":["d988cc85565b99017d40ad8baea20225165be9d5"],"type":"diff_open","range":{"from":90,"to":90},"key":"D"},{"text":"[R]evert file","display_line":22,"args":["1b6ba655c6c0d899965adff278ac6320d5fc3b12"],"type":"diff_revert_selected_file","range":{"from":22,"to":22},"key":"R"},{"text":"Revert [A]ll","display_line":22,"args":["1b6ba655c6c0d899965adff278ac6320d5fc3b12"],"type":"diff_revert_all","range":{"from":22,"to":22},"key":"A"},{"text":"[D]iff","display_line":22,"args":["1b6ba655c6c0d899965adff278ac6320d5fc3b12"],"type":"diff_open","range":{"from":22,"to":22},"key":"D"},{"text":"[R]evert file","display_line":56,"args":["57d83f5596cb1f142fbc681d3d93b7184f7f73cd"],"type":"diff_revert_selected_file","range":{"from":56,"to":56},"key":"R"},{"text":"Revert [A]ll","display_line":56,"args":["57d83f5596cb1f142fbc681d3d93b7184f7f73cd"],"type":"diff_revert_all","range":{"from":56,"to":56},"key":"A"},{"text":"[D]iff","display_line":56,"args":["57d83f5596cb1f142fbc681d3d93b7184f7f73cd"],"type":"diff_open","range":{"from":56,"to":56},"key":"D"}],"lines":["----","","","add another word","","[`test.txt`](test.txt)","","----","","","I'll append a single word (\"again\") to the first line of `test.txt`. Applying a precise edit to the existing line now.","","** edit** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","`````txt"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again"," "," ","","`````","","**󰻛 Created Snapshot** `1b6ba655`","","----","","","**Done:** added the word `again` to `test.txt`.","","----","","","add another word","","----","","","I'll read `test.txt` to get the current first-line content, then append one word (\"again2\") to that line. Proceeding to read the file.","","** read** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","----","","","Now I'll append the word \"again2\" to the first line. I'll apply an exact in-place edit to update that line.","","** edit** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","`````txt"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again again2"," "," ","","`````","","**󰻛 Created Snapshot** `57d83f55`","","----","","","**Done:** appended the word `again2` to `test.txt`.","","----","","","add another word","","----","","","I'll read `test.txt` to get the current first-line content, then append the word `again3`. Proceeding to read the file.","","** read** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","----","","","I'll append the word \"again3\" to the first line of `test.txt` with an exact in-place edit. Applying the change now.","","** edit** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","`````txt"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again again2"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again again2 again3"," "," ","","`````","","**󰻛 Created Snapshot** `d988cc85`","","----","","","**Done:** appended the word `again3` to `test.txt`.","",""]} +{"timestamp":1770332852,"lines":["----","","","add another word","","[`test.txt`](test.txt)","","----","","","I'll append a single word (\"again\") to the first line of `test.txt`. Applying a precise edit to the existing line now.","","** edit** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","`````txt"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again"," "," ","","`````","","**󰻛 Created Snapshot** `1b6ba655`","","----","","","**Done:** added the word `again` to `test.txt`.","","----","","","add another word","","----","","","I'll read `test.txt` to get the current first-line content, then append one word (\"again2\") to that line. Proceeding to read the file.","","** read** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","----","","","Now I'll append the word \"again2\" to the first line. I'll apply an exact in-place edit to update that line.","","** edit** `/home/francis/Projects/_nvim/opencode.nvim/test.txt`","","`````txt"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again"," tangram quiver saffron nebula cobalt murmur plinth zephyr ember lattice cadenza another yet extra more again again2"," "," ","","`````","","**󰻛 Created Snapshot** `57d83f55`","","----","","","**Done:** appended the word `again2` to `test.txt`.","","----","","> 1 message reverted, 2 tool calls reverted",">","> type `/redo` to restore.",""," test.txt: +1 -1",""],"extmarks":[[1,1,0,{"virt_text":[["▌󰭻 ","OpencodeMessageRoleUser"],[" "],["USER","OpencodeMessageRoleUser"],["","OpencodeHint"],[" (2025-10-20 15:20:02)","OpencodeHint"],[" [msg_a0234c0b7001y2o9S1jMaNVZar]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[2,2,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[3,3,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[4,4,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[5,5,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[6,8,0,{"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:04)","OpencodeHint"],[" [msg_a0234c7960011LTxTvD94hfWCi]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[7,12,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[8,13,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[9,14,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[10,15,0,{"end_row":16,"hl_eol":true,"right_gravity":true,"ns_id":3,"virt_text_repeat_linebreak":false,"hl_group":"OpencodeDiffDelete","virt_text":[["-","OpencodeDiffDelete"]],"virt_text_pos":"overlay","priority":5000,"virt_text_hide":false,"end_right_gravity":false,"end_col":0}],[11,15,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[12,16,0,{"end_row":17,"hl_eol":true,"right_gravity":true,"ns_id":3,"virt_text_repeat_linebreak":false,"hl_group":"OpencodeDiffAdd","virt_text":[["+","OpencodeDiffAdd"]],"virt_text_pos":"overlay","priority":5000,"virt_text_hide":false,"end_right_gravity":false,"end_col":0}],[13,16,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[14,17,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[15,18,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[16,19,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[17,20,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[18,25,0,{"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:09)","OpencodeHint"],[" [msg_a0234d8fb001SXyngLjuKSuxOY]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[19,30,0,{"virt_text":[["▌󰭻 ","OpencodeMessageRoleUser"],[" "],["USER","OpencodeMessageRoleUser"],["","OpencodeHint"],[" (2025-10-20 15:20:11)","OpencodeHint"],[" [msg_a0234e308001SKl5bQUibp5gtI]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[20,31,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[21,32,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[22,35,0,{"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:11)","OpencodeHint"],[" [msg_a0234e31f001m4EsQdPmY3PTtS]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[23,42,0,{"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:16)","OpencodeHint"],[" [msg_a0234f482001PQbMjWc6W8s0eF]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[24,46,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[25,47,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[26,48,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[27,49,0,{"end_row":50,"hl_eol":true,"right_gravity":true,"ns_id":3,"virt_text_repeat_linebreak":false,"hl_group":"OpencodeDiffDelete","virt_text":[["-","OpencodeDiffDelete"]],"virt_text_pos":"overlay","priority":5000,"virt_text_hide":false,"end_right_gravity":false,"end_col":0}],[28,49,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[29,50,0,{"end_row":51,"hl_eol":true,"right_gravity":true,"ns_id":3,"virt_text_repeat_linebreak":false,"hl_group":"OpencodeDiffAdd","virt_text":[["+","OpencodeDiffAdd"]],"virt_text_pos":"overlay","priority":5000,"virt_text_hide":false,"end_right_gravity":false,"end_col":0}],[30,50,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[31,51,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[32,52,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[33,53,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[34,54,0,{"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_win_col":-1,"right_gravity":true,"ns_id":3,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_hide":false}],[35,59,0,{"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" gpt-5-mini","OpencodeHint"],[" (2025-10-20 15:20:17)","OpencodeHint"],[" [msg_a0234f9c6001JCKYaca1HHwwx6]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_win_col":-3,"right_gravity":true,"ns_id":3,"priority":10,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[36,69,0,{"virt_text":[["+1","OpencodeDiffAddText"]],"virt_text_pos":"win_col","virt_text_win_col":12,"right_gravity":true,"ns_id":3,"priority":1000,"virt_text_repeat_linebreak":false,"virt_text_hide":false}],[37,69,0,{"virt_text":[["-1","OpencodeDiffDeleteText"]],"virt_text_pos":"win_col","virt_text_win_col":15,"right_gravity":true,"ns_id":3,"priority":1000,"virt_text_repeat_linebreak":false,"virt_text_hide":false}]],"actions":[{"text":"[R]evert file","key":"R","type":"diff_revert_selected_file","range":{"to":56,"from":56},"args":["57d83f5596cb1f142fbc681d3d93b7184f7f73cd"],"display_line":56},{"text":"Revert [A]ll","key":"A","type":"diff_revert_all","range":{"to":56,"from":56},"args":["57d83f5596cb1f142fbc681d3d93b7184f7f73cd"],"display_line":56},{"text":"[D]iff","key":"D","type":"diff_open","range":{"to":56,"from":56},"args":["57d83f5596cb1f142fbc681d3d93b7184f7f73cd"],"display_line":56},{"text":"[R]evert file","key":"R","type":"diff_revert_selected_file","range":{"to":22,"from":22},"args":["1b6ba655c6c0d899965adff278ac6320d5fc3b12"],"display_line":22},{"text":"Revert [A]ll","key":"A","type":"diff_revert_all","range":{"to":22,"from":22},"args":["1b6ba655c6c0d899965adff278ac6320d5fc3b12"],"display_line":22},{"text":"[D]iff","key":"D","type":"diff_open","range":{"to":22,"from":22},"args":["1b6ba655c6c0d899965adff278ac6320d5fc3b12"],"display_line":22}]} \ No newline at end of file diff --git a/tests/helpers.lua b/tests/helpers.lua index 886f61c5..95f22ab1 100644 --- a/tests/helpers.lua +++ b/tests/helpers.lua @@ -224,15 +224,26 @@ end function M.get_session_from_events(events, with_session_updates) -- renderer needs a valid session id - -- find the last session.updated event + -- merge session.updated events and use the latest updated session if with_session_updates then - for i = #events, 1, -1 do - local event = events[i] - if event.type == 'session.updated' and event.properties.info and event.properties.info then - return event.properties.info + local sessions_by_id = {} + local last_session_id = nil + + for _, event in ipairs(events) do + if event.type == 'session.updated' and event.properties.info then + local info = event.properties.info + if info.id then + local existing = sessions_by_id[info.id] or {} + sessions_by_id[info.id] = vim.tbl_deep_extend('force', existing, vim.deepcopy(info)) + last_session_id = info.id + end end end + + if last_session_id then + return sessions_by_id[last_session_id] + end end for _, event in ipairs(events) do -- find the session id in a message or part event diff --git a/tests/replay/renderer_spec.lua b/tests/replay/renderer_spec.lua index 5764b1cd..2e58ed91 100644 --- a/tests/replay/renderer_spec.lua +++ b/tests/replay/renderer_spec.lua @@ -3,6 +3,7 @@ local ui = require('opencode.ui.ui') local helpers = require('tests.helpers') local output_window = require('opencode.ui.output_window') local assert = require('luassert') +local stub = require('luassert.stub') local config = require('opencode.config') local function assert_output_matches(expected, actual, name) @@ -144,6 +145,82 @@ describe('renderer unit tests', function() ) end end) + + it('updates active session title from session.updated event', function() + local renderer = require('opencode.ui.renderer') + local topbar = require('opencode.ui.topbar') + + state.active_session = { + id = 'ses_123', + title = 'New session - 2026-02-05T22:26:08.579Z', + time = { created = 1, updated = 1 }, + } + + local active_session_ref = state.active_session + local topbar_render_stub = stub(topbar, 'render') + + renderer.on_session_updated({ + info = { + id = 'ses_123', + title = 'Branch review request', + time = { created = 1, updated = 2 }, + }, + }) + + assert.is_true(state.active_session == active_session_ref) + assert.are.equal('Branch review request', state.active_session.title) + assert.stub(topbar_render_stub).was_called() + topbar_render_stub:revert() + end) + + it('rerenders full session when revert changes', function() + local renderer = require('opencode.ui.renderer') + + state.messages = {} + state.active_session = { + id = 'ses_123', + title = 'Session', + time = { created = 1, updated = 1 }, + revert = { messageID = 'msg_1', snapshot = 'a', diff = '' }, + } + + local render_stub = stub(renderer, '_render_full_session_data') + + renderer.on_session_updated({ + info = { + id = 'ses_123', + title = 'Session', + time = { created = 1, updated = 2 }, + revert = { messageID = 'msg_2', snapshot = 'b', diff = '' }, + }, + }) + + assert.stub(render_stub).was_called_with(state.messages) + render_stub:revert() + end) + + it('ignores session.updated for non-active session IDs', function() + local renderer = require('opencode.ui.renderer') + + state.active_session = { + id = 'ses_123', + title = 'Session', + time = { created = 1, updated = 1 }, + } + + local render_stub = stub(renderer, '_render_full_session_data') + + renderer.on_session_updated({ + info = { + id = 'ses_999', + title = 'Should not apply', + }, + }) + + assert.are.equal('Session', state.active_session.title) + assert.stub(render_stub).was_not_called() + render_stub:revert() + end) end) describe('renderer functional tests', function()