SLOPSHOPPER

redraw-probe

Measures ways to redraw a docked pane when the transcript scroll position changes

newpanerowscommandtimer
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · redraw-probe
│ ┃ RP off ✕ › fix the failing auth test and add an audit log call │ ┃ cp=T-1 pane=1 inv=0 set=0 │ ┃ [ T000 你 · 用户问题:请检查第 0 个模块 ] ⏺ Read(src/auth.ts) │ ┃ [ T001 你 · 用户问题:请检查第 1 个模块 ] ⎿ Read 6 lines │ ┃ [ T002 你 · 用户问题:请检查第 2 个模块 ] ⏺ Update(src/auth.ts) │ ┃ [ T003 你 · 用户问题:请检查第 3 个模块 ] ⎿ Added 2 lines, removed 1 line │ ┃ [ T004 你 · 用户问题:请检查第 4 个模块 ] ⏺ Bash(bun test) │ ┃ [ T005 你 · 用户问题:请检查第 5 个模块 ] ⎿ 3 pass, 1 fail │ ┃ [ T006 你 · 用户问题:请检查第 6 个模块 ] │ ┃ [ T007 你 · 用户问题:请检查第 7 个模块 ] ● Done. refresh now rejects expired claims and logs an audit event. │ ┃ [ T008 你 · 用户问题:请检查第 8 个模块 ] │ ┃ [ T009 你 · 用户问题:请检查第 9 个模块 ] ✻ Worked for 42s · done 4:20 PM │ ┃ [ T010 你 · 用户问题:请检查第 10 个模块 ] │ ┃ [ T011 你 · 用户问题:请检查第 11 个模块 ] › /rpd │ ┃ [ T012 你 · 用户问题:请检查第 12 个模块 ] ⎿ redraw-probe: /plugins/redraw-probe/../logs/x-off-1760000000020. │ ┃ [ T013 你 · 用户问题:请检查第 13 个模块 ] │ ┃ [ T014 你 · 用户问题:请检查第 14 个模块 ] │ ┃ [ T015 你 · 用户问题:请检查第 15 个模块 ] │ ┃ [ T016 你 · 用户问题:请检查第 16 个模块 ] │ ┃ [ T017 你 · 用户问题:请检查第 17 个模块 ] │ ┃ [ T018 你 · 用户问题:请检查第 18 个模块 ] │ ┃ [ T019 你 · 用户问题:请检查第 19 个模块 ] │ ┃ [ T020 你 · 用户问题:请检查第 20 个模块 ] │ ┃ [ T021 你 · 用户问题:请检查第 21 个模块 ] │ ┃ [ T022 你 · 用户问题:请检查第 22 个模块 ] │ ┃ [ T023 你 · 用户问题:请检查第 23 个模块 ] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · RP off
cp=T-1 pane=1 inv=0 set=0 [ T000 你 · 用户问题:请检查第 0 个模块 ] [ T001 你 · 用户问题:请检查第 1 个模块 ] [ T002 你 · 用户问题:请检查第 2 个模块 ] [ T003 你 · 用户问题:请检查第 3 个模块 ] [ T004 你 · 用户问题:请检查第 4 个模块 ] [ T005 你 · 用户问题:请检查第 5 个模块 ] [ T006 你 · 用户问题:请检查第 6 个模块 ] [ T007 你 · 用户问题:请检查第 7 个模块 ] [ T008 你 · 用户问题:请检查第 8 个模块 ] [ T009 你 · 用户问题:请检查第 9 个模块 ] [ T010 你 · 用户问题:请检查第 10 个模块 ] [ T011 你 · 用户问题:请检查第 11 个模块 ] [ T012 你 · 用户问题:请检查第 12 个模块 ] [ T013 你 · 用户问题:请检查第 13 个模块 ] [ T014 你 · 用户问题:请检查第 14 个模块 ] [ T015 你 · 用户问题:请检查第 15 个模块 ] [ T016 你 · 用户问题:请检查第 16 个模块 ] [ T017 你 · 用户问题:请检查第 17 个模块 ] [ T018 你 · 用户问题:请检查第 18 个模块 ] [ T019 你 · 用户问题:请检查第 19 个模块 ] [ T020 你 · 用户问题:请检查第 20 个模块 ] [ T021 你 · 用户问题:请检查第 21 个模块 ] [ T022 你 · 用户问题:请检查第 22 个模块 ] [ T023 你 · 用户问题:请检查第 23 个模块 ] [ T024 你 · 用户问题:请检查第 24 个模块 ] [ T025 你 · 用户问题:请检查第 25 个模块 ] [ T026 你 · 用户问题:请检查第 26 个模块 ] [ T027 你 · 用户问题:请检查第 27 个模块 ] [ T028 你 · 用户问题:请检查第 28 个模块 ] [ T029 你 · 用户问题:请检查第 29 个模块 ] [ T030 你 · 用户问题:请检查第 30 个模块 ] [ T031 你 · 用户问题:请检查第 31 个模块 ] [ T032 你 · 用户问题:请检查第 32 个模块 ] [ T033 你 · 用户问题:请检查第 33 个模块 ] [ T034 你 · 用户问题:请检查第 34 个模块 ] [ T035 你 · 用户问题:请检查第 35 个模块 ] [ T036 你 · 用户问题:请检查第 36 个模块 ] [ T037 你 · 用户问题:请检查第 37 个模块 ] [ T038 你 · 用户问题:请检查第 38 个模块 ] [ T039 你 · 用户问题:请检查第 39 个模块 ] [ T040 你 · 用户问题:请检查第 40 个模块 ] [ T041 你 · 用户问题:请检查第 41 个模块 ] [ T042 你 · 用户问题:请检查第 42 个模块 ] [ T043 你 · 用户问题:请检查第 43 个模块 ] [ T044 你 · 用户问题:请检查第 44 个模块 ] [ T045 你 · 用户问题:请检查第 45 个模块 ] [ T046 你 · 用户问题:请检查第 46 个模块 ] [ T047 你 · 用户问题:请检查第 47 个模块 ] [ T048 你 · 用户问题:请检查第 48 个模块 ] [ T049 你 · 用户问题:请检查第 49 个模块 ] [ T050 你 · 用户问题:请检查第 50 个模块 ] [ T051 你 · 用户问题:请检查第 51 个模块 ] [ T052 你 · 用户问题:请检查第 52 个模块 ] [ T053 你 · 用户问题:请检查第 53 个模块 ] [ T054 你 · 用户问题:请检查第 54 个模块 ] [ T055 你 · 用户问题:请检查第 55 个模块 ] [ T056 你 · 用户问题:请检查第 56 个模块 ] [ T057 你 · 用户问题:请检查第 57 个模块 ] [ T058 你 · 用户问题:请检查第 58 个模块 ] [ T059 你 · 用户问题:请检查第 59 个模块 ] [ T060 你 · 用户问题:请检查第 60 个模块 ] [ T061 你 · 用户问题:请检查第 61 个模块 ] [ T062 你 · 用户问题:请检查第 62 个模块 ] [ T063 你 · 用户问题:请检查第 63 个模块 ] [ T064 你 · 用户问题:请检查第 64 个模块 ] [ T065 你 · 用户问题:请检查第 65 个模块 ] [ T066 你 · 用户问题:请检查第 66 个模块 ] [ T067 你 · 用户问题:请检查第 67 个模块 ] [ T068 你 · 用户问题:请检查第 68 个模块 ] [ T069 你 · 用户问题:请检查第 69 个模块 ] [ T070 你 · 用户问题:请检查第 70 个模块 ] [ T071 你 · 用户问题:请检查第 71 个模块 ] [ T072 你 · 用户问题:请检查第 72 个模块 ] [ T073 你 · 用户问题:请检查第 73 个模块 ] [ T074 你 · 用户问题:请检查第 74 个模块 ] [ T075 你 · 用户问题:请检查第 75 个模块 ] [ T076 你 · 用户问题:请检查第 76 个模块 ] [ T077 你 · 用户问题:请检查第 77 个模块 ] [ T078 你 · 用户问题:请检查第 78 个模块 ] [ T079 你 · 用户问题:请检查第 79 个模块 ] [ T080 你 · 用户问题:请检查第 80 个模块 ] [ T081 你 · 用户问题:请检查第 81 个模块 ] [ T082 你 · 用户问题:请检查第 82 个模块 ] [ T083 你 · 用户问题:请检查第 83 个模块 ] [ T084 你 · 用户问题:请检查第 84 个模块 ] [ T085 你 · 用户问题:请检查第 85 个模块 ] [ T086 你 · 用户问题:请检查第 86 个模块 ] [ T087 你 · 用户问题:请检查第 87 个模块 ] [ T088 你 · 用户问题:请检查第 88 个模块 ] [ T089 你 · 用户问题:请检查第 89 个模块 ] [ T090 你 · 用户问题:请检查第 90 个模块 ] [ T091 你 · 用户问题:请检查第 91 个模块 ] [ T092 你 · 用户问题:请检查第 92 个模块 ] [ T093 你 · 用户问题:请检查第 93 个模块 ] [ T094 你 · 用户问题:请检查第 94 个模块 ] [ T095 你 · 用户问题:请检查第 95 个模块 ] [ T096 你 · 用户问题:请检查第 96 个模块 ] [ T097 你 · 用户问题:请检查第 97 个模块 ] [ T098 你 · 用户问题:请检查第 98 个模块 ] [ T099 你 · 用户问题:请检查第 99 个模块 ] [ T100 你 · 用户问题:请检查第 100 个模块 ] [ T101 你 · 用户问题:请检查第 101 个模块 ] [ T102 你 · 用户问题:请检查第 102 个模块 ] [ T103 你 · 用户问题:请检查第 103 个模块 ] [ T104 你 · 用户问题:请检查第 104 个模块 ] [ T105 你 · 用户问题:请检查第 105 个模块 ] [ T106 你 · 用户问题:请检查第 106 个模块 ] [ T107 你 · 用户问题:请检查第 107 个模块 ] [ T108 你 · 用户问题:请检查第 108 个模块 ] [ T109 你 · 用户问题:请检查第 109 个模块 ] [ T110 你 · 用户问题:请检查第 110 个模块 ] [ T111 你 · 用户问题:请检查第 111 个模块 ] [ T112 你 · 用户问题:请检查第 112 个模块 ] [ T113 你 · 用户问题:请检查第 113 个模块 ] [ T114 你 · 用户问题:请检查第 114 个模块 ] [ T115 你 · 用户问题:请检查第 115 个模块 ] [ T116 你 · 用户问题:请检查第 116 个模块 ] [ T117 你 · 用户问题:请检查第 117 个模块 ] [ T118 你 · 用户问题:请检查第 118 个模块 ] [ T119 你 · 用户问题:请检查第 119 个模块 ] [ T120 你 · 用户问题:请检查第 120 个模块 ] [ T121 你 · 用户问题:请检查第 121 个模块 ] [ T122 你 · 用户问题:请检查第 122 个模块 ] [ T123 你 · 用户问题:请检查第 123 个模块 ] [ T124 你 · 用户问题:请检查第 124 个模块 ] [ T125 你 · 用户问题:请检查第 125 个模块 ] [ T126 你 · 用户问题:请检查第 126 个模块 ] [ T127 你 · 用户问题:请检查第 127 个模块 ] [ T128 你 · 用户问题:请检查第 128 个模块 ] [ T129 你 · 用户问题:请检查第 129 个模块 ] [ T130 你 · 用户问题:请检查第 130 个模块 ] [ T131 你 · 用户问题:请检查第 131 个模块 ] [ T132 你 · 用户问题:请检查第 132 个模块 ] [ T133 你 · 用户问题:请检查第 133 个模块 ] [ T134 你 · 用户问题:请检查第 134 个模块 ] [ T135 你 · 用户问题:请检查第 135 个模块 ] [ T136 你 · 用户问题:请检查第 136 个模块 ] [ T137 你 · 用户问题:请检查第 137 个模块 ] [ T138 你 · 用户问题:请检查第 138 个模块 ] [ T139 你 · 用户问题:请检查第 139 个模块 ] [ T140 你 · 用户问题:请检查第 140 个模块 ] [ T141 你 · 用户问题:请检查第 141 个模块 ] [ T142 你 · 用户问题:请检查第 142 个模块 ] [ T143 你 · 用户问题:请检查第 143 个模块 ] [ T144 你 · 用户问题:请检查第 144 个模块 ] [ T145 你 · 用户问题:请检查第 145 个模块 ] [ T146 你 · 用户问题:请检查第 146 个模块 ] [ T147 你 · 用户问题:请检查第 147 个模块 ] [ T148 你 · 用户问题:请检查第 148 个模块 ] [ T149 你 · 用户问题:请检查第 149 个模块 ] [ T150 你 · 用户问题:请检查第 150 个模块 ] [ T151 你 · 用户问题:请检查第 151 个模块 ] [ T152 你 · 用户问题:请检查第 152 个模块 ] [ T153 你 · 用户问题:请检查第 153 个模块 ] [ T154 你 · 用户问题:请检查第 154 个模块 ] [ T155 你 · 用户问题:请检查第 155 个模块 ] [ T156 你 · 用户问题:请检查第 156 个模块 ] [ T157 你 · 用户问题:请检查第 157 个模块 ] [ T158 你 · 用户问题:请检查第 158 个模块 ] [ T159 你 · 用户问题:请检查第 159 个模块 ] [ T160 你 · 用户问题:请检查第 160 个模块 ] [ T161 你 · 用户问题:请检查第 161 个模块 ] [ T162 你 · 用户问题:请检查第 162 个模块 ] [ T163 你 · 用户问题:请检查第 163 个模块 ] [ T164 你 · 用户问题:请检查第 164 个模块 ] [ T165 你 · 用户问题:请检查第 165 个模块 ] [ T166 你 · 用户问题:请检查第 166 个模块 ] [ T167 你 · 用户问题:请检查第 167 个模块 ] [ T168 你 · 用户问题:请检查第 168 个模块 ] [ T169 你 · 用户问题:请检查第 169 个模块 ] [ T170 你 · 用户问题:请检查第 170 个模块 ] [ T171 你 · 用户问题:请检查第 171 个模块 ] [ T172 你 · 用户问题:请检查第 172 个模块 ] [ T173 你 · 用户问题:请检查第 173 个模块 ] [ T174 你 · 用户问题:请检查第 174 个模块 ] [ T175 你 · 用户问题:请检查第 175 个模块 ] [ T176 你 · 用户问题:请检查第 176 个模块 ] [ T177 你 · 用户问题:请检查第 177 个模块 ] [ T178 你 · 用户问题:请检查第 178 个模块 ] [ T179 你 · 用户问题:请检查第 179 个模块 ] [ T180 你 · 用户问题:请检查第 180 个模块 ] [ T181 你 · 用户问题:请检查第 181 个模块 ] [ T182 你 · 用户问题:请检查第 182 个模块 ] [ T183 你 · 用户问题:请检查第 183 个模块 ] [ T184 你 · 用户问题:请检查第 184 个模块 ] [ T185 你 · 用户问题:请检查第 185 个模块 ] [ T186 你 · 用户问题:请检查第 186 个模块 ] [ T187 你 · 用户问题:请检查第 187 个模块 ] [ T188 你 · 用户问题:请检查第 188 个模块 ] [ T189 你 · 用户问题:请检查第 189 个模块 ] [ T190 你 · 用户问题:请检查第 190 个模块 ] [ T191 你 · 用户问题:请检查第 191 个模块 ] [ T192 你 · 用户问题:请检查第 192 个模块 ] [ T193 你 · 用户问题:请检查第 193 个模块 ] [ T194 你 · 用户问题:请检查第 194 个模块 ] [ T195 你 · 用户问题:请检查第 195 个模块 ] [ T196 你 · 用户问题:请检查第 196 个模块 ] [ T197 你 · 用户问题:请检查第 197 个模块 ] [ T198 你 · 用户问题:请检查第 198 个模块 ] [ T199 你 · 用户问题:请检查第 199 个模块 ] [ T200 你 · 用户问题:请检查第 200 个模块 ] [ T201 你 · 用户问题:请检查第 201 个模块 ] [ T202 你 · 用户问题:请检查第 202 个模块 ] [ T203 你 · 用户问题:请检查第 203 个模块 ] [ T204 你 · 用户问题:请检查第 204 个模块 ] [ T205 你 · 用户问题:请检查第 205 个模块 ] [ T206 你 · 用户问题:请检查第 206 个模块 ] [ T207 你 · 用户问题:请检查第 207 个模块 ] [ T208 你 · 用户问题:请检查第 208 个模块 ] [ T209 你 · 用户问题:请检查第 209 个模块 ] [ T210 你 · 用户问题:请检查第 210 个模块 ] [ T211 你 · 用户问题:请检查第 211 个模块 ] [ T212 你 · 用户问题:请检查第 212 个模块 ] [ T213 你 · 用户问题:请检查第 213 个模块 ] [ T214 你 · 用户问题:请检查第 214 个模块 ] [ T215 你 · 用户问题:请检查第 215 个模块 ] [ T216 你 · 用户问题:请检查第 216 个模块 ] [ T217 你 · 用户问题:请检查第 217 个模块 ] [ T218 你 · 用户问题:请检查第 218 个模块 ] [ T219 你 · 用户问题:请检查第 219 个模块 ] [ T220 你 · 用户问题:请检查第 220 个模块 ] [ T221 你 · 用户问题:请检查第 221 个模块 ] [ T222 你 · 用户问题:请检查第 222 个模块 ] [ T223 你 · 用户问题:请检查第 223 个模块 ] [ T224 你 · 用户问题:请检查第 224 个模块 ] [ T225 你 · 用户问题:请检查第 225 个模块 ] [ T226 你 · 用户问题:请检查第 226 个模块 ] [ T227 你 · 用户问题:请检查第 227 个模块 ] [ T228 你 · 用户问题:请检查第 228 个模块 ] [ T229 你 · 用户问题:请检查第 229 个模块 ] [ T230 你 · 用户问题:请检查第 230 个模块 ] [ T231 你 · 用户问题:请检查第 231 个模块 ] [ T232 你 · 用户问题:请检查第 232 个模块 ] [ T233 你 · 用户问题:请检查第 233 个模块 ] [ T234 你 · 用户问题:请检查第 234 个模块 ] [ T235 你 · 用户问题:请检查第 235 个模块 ] [ T236 你 · 用户问题:请检查第 236 个模块 ] [ T237 你 · 用户问题:请检查第 237 个模块 ] [ T238 你 · 用户问题:请检查第 238 个模块 ] [ T239 你 · 用户问题:请检查第 239 个模块 ] [ T240 你 · 用户问题:请检查第 240 个模块 ] [ T241 你 · 用户问题:请检查第 241 个模块 ] [ T242 你 · 用户问题:请检查第 242 个模块 ] [ T243 你 · 用户问题:请检查第 243 个模块 ] [ T244 你 · 用户问题:请检查第 244 个模块 ] [ T245 你 · 用户问题:请检查第 245 个模块 ] [ T246 你 · 用户问题:请检查第 246 个模块 ] [ T247 你 · 用户问题:请检查第 247 个模块 ] [ T248 你 · 用户问题:请检查第 248 个模块 ] [ T249 你 · 用户问题:请检查第 249 个模块 ] [ T250 你 · 用户问题:请检查第 250 个模块 ] [ T251 你 · 用户问题:请检查第 251 个模块 ] [ T252 你 · 用户问题:请检查第 252 个模块 ] [ T253 你 · 用户问题:请检查第 253 个模块 ] [ T254 你 · 用户问题:请检查第 254 个模块 ] [ T255 你 · 用户问题:请检查第 255 个模块 ] [ T256 你 · 用户问题:请检查第 256 个模块 ] [ T257 你 · 用户问题:请检查第 257 个模块 ] [ T258 你 · 用户问题:请检查第 258 个模块 ] [ T259 你 · 用户问题:请检查第 259 个模块 ] [ T260 你 · 用户问题:请检查第 260 个模块 ] [ T261 你 · 用户问题:请检查第 261 个模块 ] [ T262 你 · 用户问题:请检查第 262 个模块 ] [ T263 你 · 用户问题:请检查第 263 个模块 ] [ T264 你 · 用户问题:请检查第 264 个模块 ] [ T265 你 · 用户问题:请检查第 265 个模块 ] [ T266 你 · 用户问题:请检查第 266 个模块 ] [ T267 你 · 用户问题:请检查第 267 个模块 ] [ T268 你 · 用户问题:请检查第 268 个模块 ] [ T269 你 · 用户问题:请检查第 269 个模块 ] [ T270 你 · 用户问题:请检查第 270 个模块 ] [ T271 你 · 用户问题:请检查第 271 个模块 ] [ T272 你 · 用户问题:请检查第 272 个模块 ] [ T273 你 · 用户问题:请检查第 273 个模块 ] [ T274 你 · 用户问题:请检查第 274 个模块 ] [ T275 你 · 用户问题:请检查第 275 个模块 ] [ T276 你 · 用户问题:请检查第 276 个模块 ] [ T277 你 · 用户问题:请检查第 277 个模块 ] [ T278 你 · 用户问题:请检查第 278 个模块 ] [ T279 你 · 用户问题:请检查第 279 个模块 ] [ T280 你 · 用户问题:请检查第 280 个模块 ] [ T281 你 · 用户问题:请检查第 281 个模块 ] [ T282 你 · 用户问题:请检查第 282 个模块 ] [ T283 你 · 用户问题:请检查第 283 个模块 ] [ T284 你 · 用户问题:请检查第 284 个模块 ] [ T285 你 · 用户问题:请检查第 285 个模块 ] [ T286 你 · 用户问题:请检查第 286 个模块 ] [ T287 你 · 用户问题:请检查第 287 个模块 ] [ T288 你 · 用户问题:请检查第 288 个模块 ] [ T289 你 · 用户问题:请检查第 289 个模块 ] [ T290 你 · 用户问题:请检查第 290 个模块 ] [ T291 你 · 用户问题:请检查第 291 个模块 ] [ T292 你 · 用户问题:请检查第 292 个模块 ] [ T293 你 · 用户问题:请检查第 293 个模块 ] [ T294 你 · 用户问题:请检查第 294 个模块 ] [ T295 你 · 用户问题:请检查第 295 个模块 ] [ T296 你 · 用户问题:请检查第 296 个模块 ] [ T297 你 · 用户问题:请检查第 297 个模块 ] [ T298 你 · 用户问题:请检查第 298 个模块 ] [ T299 你 · 用户问题:请检查第 299 个模块 ]
README

English | 简体中文

cc-mods

A collection of Claude Code mods. Each mod is its own plugin under mods/<mod name>/: none depends on another or shares code or data, they can be enabled together, and each has its own install and usage notes.

Mods

ModVersionWhat it does
chat-toc0.2.0Docks the current session's contents beside the conversation; a click jumps back to any turn

For a new mod, add a row to the table above and a section at the same level as chat-toc's below.


chat-toc

chat-toc in a fullscreen Claude Code terminal: clicking the minimap and an entry jumps the transcript back to that turn

In Claude Code's fullscreen terminal, chat-toc docks the current session's contents to the right of the transcript: one entry per turn of the conversation, and a click jumps back to that turn.

Requirements

  • Claude Code >= 2.1.287 (see claude --version)
  • An interactive terminal. In Claude Code Desktop (and VS Code and mobile) the contents show, but a click cannot jump: the Claude app does not let plugins scroll the conversation yet (see Known limits). JetBrains is not supported
  • The fullscreen layout: run /tui fullscreen to switch
  • A wide enough terminal: whether the pane can dock is Claude Code's call. Measured on 2.1.287 and 2.1.295: it docks by itself from 144 columns, and when opened with /chat-toc from 110 columns
  • CLAUDE_CODE_ENABLE_FUNCTION_HOOKS is not needed
  • Any operating system, but tested on macOS only (see Known limits)

While the contents are not on screen, a line above the prompt stands in for them and opens them (see Getting started).

Install

Pick one of the two, never both:

Marketplace (recommended):

claude plugin marketplace add lkangd/cc-mods
claude plugin install chat-toc@cc-mods

After installing, claude plugin list shows chat-toc@cc-mods as enabled.

Local load (for development):

claude --plugin-dir /path/to/cc-mods/mods/chat-toc

Getting started

  1. Start Claude Code in the fullscreen layout. The contents dock on the right by themselves, titled "ChatTOC" (2.1.287 draws no title row).
  2. If the contents do not show, the terminal is narrower than Claude Code docks them by itself, and a line above the prompt stands in for them: ChatTOC ‹ › │ 12 user turns · 80 agent steps Open contents ❯ ✕. Where the line is too narrow for all of it, the counts shorten from the end, then the name goes; Claude Code Desktop draws no ‹ ›. Widen the terminal and they dock by themselves, and the line goes; or click Open contents ❯ (or run /chat-toc): Claude Code then docks them in a narrower terminal too, and below that places them above the prompt. Outside the fullscreen layout they open above the prompt by themselves; run /tui fullscreen to dock them on the right.
  1. Click any entry to jump back to that turn in the conversation. After closing the pane with ✕, /chat-toc opens it again at any time, with the focus in it.

Controls

ControlWhat it does
Click your input in an entry (from the ● line)Jumps to that input, at the top of the screen, and selects the entry; it opens no preview. A slash command or ! command jumps to the row right after it and keeps the input row in sight
Click the Agent's reply in an entry (from the ├╴ line)Jumps to the turn's last text reply (without one, to the last thing the turn drew), and selects the entry
Click a block in the row of blocks under the filtersJumps to that turn, as clicking its entry does, but selects nothing. One block per turn, as tall as the steps the Agent took; with more turns than the pane is wide, a block stands for several and jumps to the first of them that can be jumped to. The block of the turn you are on is the bright one. Hidden with fewer than two turns or in a very short pane
Wheel over the contentsScrolls the contents by entries and pauses following
Ctrl+X then Tab; or /chat-toc with an empty promptMoves the focus into the contents
Any click in the contents, with an empty promptAlso moves the focus into the contents, so the keys below work at once. Not after r: Reuse (the focus goes to the prompt) or ‹ ›. With text in the prompt the focus stays there. A key that is none of the contents' keys goes back to the prompt and is typed there, but one that is (c, p, r, f, …) acts on the contents
↑ ↓ (focus in the contents)Moves the selection one at a time (your input and the Agent's reply count one each); the selected line is inverse. Moving it opens no preview and leaves an open one where it is. Entries that cannot be jumped to can be selected too
Enter (focus in the contents)Same as clicking the selected side: jumps and keeps the selection
p, or click p: Preview on the row under the selected entryOpens the preview on that turn. The row shows while the preview is closed: p: Preview · r: Reuse · c: Copy, each button only when it can act (as on the preview's lines, below); r reuses the turn's input, c copies the selected side (your input, or the Agent's last text reply). ↑ ↓ pass over the row
1 2 3, or click 1: All 2: User 3: Agent at the topShows all / only your input / only the Agent's replies. A thick line in the highlight colour marks the chosen one. Remembered across sessions
l, or click the "Layout: Card" button at the footer's right endCycles the layout Card → Compact → Timeline; the button names the one in use. Remembered across sessions
p, or click p: ✕ at the right end of the preview's top lineCloses the preview, from the region or expanded; the list takes its rows back, and the selected entry's row of buttons comes back. Not remembered: the contents always open with the preview closed
e, or click e: expand on the preview's last lineExpands the preview over the list and the row of blocks: the turn whole, scrolled with the wheel, PgUp PgDn or ↑ ↓. e again, or e: ← Back at its top, returns to the list with the preview below it; so does Esc. A preview opened expanded because the contents are too short closes on e: ← Back
r, or click r: Reuse on the preview's User linePuts the input the preview shows back into the prompt as a draft, as you typed it: into an empty prompt, or after a blank line below what you are typing, which is never lost. Never sent: edit it or press Enter yourself. Works while the Agent is working too (Enter then queues it, as usual). The focus moves to the prompt. A ! command has no r: Reuse (filled in, it would be sent to the model, not run)
c, or click c: Copy on the preview's User lineCopies that input, whole, as you typed it (a ! command as !command); the button reads Copied for about 2 seconds. The focus stays in the contents
a, or click a: Copy on the preview's Agent lineCopies the Agent's last text reply, whole, as Markdown; the button reads Copied for about 2 seconds. Not offered while the turn is running
Click the search field under the filters, or f (f: Find)Moves the focus into the field. Type words or part of a file path: the list narrows to the turns holding every word
Enter in the search fieldJumps to the matching turn nearest the one you are viewing and ends the search (the list is whole again, the turn jumped to highlighted). With no match it only clears the field
↓ or Tab from the search fieldMoves the selection on to the matching entries; Enter on one jumps there, selects it and keeps the search
Esc in the search fieldMoves the focus back to the prompt and keeps the search
Ctrl+U or Backspace in the search field, or click the ✕ at its right endClears the search
Click ‹ or › at the search row's right end, or on the line above the prompt while the contents are not on screen; or a key you bind to them (see Keys for the previous and next turn)Jumps the transcript to the turn before or after the one you are viewing (the highlighted one, not the ↑ ↓ selection), among the turns the list shows: the view choice applies, and while searching only the matches, without ending the search. It jumps to the side the view shows (your input under All and User, the reply under Agent) and the highlight stays on the turn jumped to, so each press goes one turn further. At either end nothing happens. Turns that cannot be jumped to are passed over; when no turn that way can be, the note "This entry is not in the conversation view now; cannot jump to it" shows under the list, or at the top right of the screen while the contents are not on screen
Esc (focus in the contents)Moves the focus back to the prompt; the contents stay open
Ctrl+X then ← / →Widens / narrows the contents (a Claude Code feature). A width you set wins
Click ✕Closes the contents; they do not open by themselves again this session. A new session (including after /clear or /resume) opens them again
Click ChatTOC on the line above the promptOpens the contents, as Open contents ❯ does. Claude Code Desktop, and the terminal before Claude Code 2.1.303, draw it in the plain text colour
Click ✕ at the right end of the line above the prompt (right after Open contents ❯ before Claude Code 2.1.295)Hides that line, in this session and every later one, and says how to bring it back. /chat-toc still opens the contents
/chat-toc-bandShows the line above the prompt again
/chat-tocOpens the contents when closed, brings them to the front when open (also when another pane pushed them back); either way moves the focus into them (with an empty prompt). It never closes them
Claude Code Desktop: click an entryOpens the preview right under that turn's entries, in a box with a rounded border; clicking another entry moves it there. Opening it does not scroll the contents: when it falls below the edge, scroll to it. The click also asks to jump, which the Claude app refuses without a word (see Known limits)
Claude Code Desktop: type in the search field under the view choicesNarrows the list as on the terminal, at each change; the row of bars draws the matching turns in the accent colour and the rest dim. There is no f: Find: click the field
Claude Code Desktop: Enter in the search field, or its show buttonKeeps the search and opens the preview on the newest match (the app cannot jump, nor say which turn you are viewing)
Claude Code Desktop: click ✕ on the preview's top line, ↓ 12 more lines · expand on its last line, ← BackAs p, e and e: ← Back on the terminal. Desktop has no hotkeys, so none of the buttons names a key
Claude Code Desktop: scroll the contents; click ↑ 12 earlier turns at the list's top, ↓ 12 later turns at its bottom, ↑ Back to top after the last entryThe app scrolls the whole contents, the view choices and the search field with them. A long conversation lists its newest turns (as many as fit in what the app takes in one drawing, about 45 in the Card layout); ↑ lists the turns before them and ↓ goes back toward the newest, and either closes an open preview. A search or a view choice narrows the list first. ↑ Back to top scrolls the contents back to the view choices
Claude Code Desktop: click Copy on the preview's User or Agent lineCopies as c and a do; a copy the app cannot take says "Too long to copy here". There is no Reuse in Desktop

The hotkeys 1 2 3 l p e f r c a work only with the focus in the contents, and not while it is in the search field, which takes every letter typed; p r c only while the selected entry's row or the open preview shows their button; e a only while the preview is open, e only while it is cut or expanded (a not while a short preview cuts the Agent line, until e). ‹ › have no hotkey, and ↑ ↓ pass over them.

Keys for the previous and next turn

chat-toc binds no key of its own. ‹ and › answer two Claude Code keybinding actions that are free in a normal session, so you can bind keys to them in ~/.claude/keybindings.json and step through the conversation from the prompt, while Claude works too:

{
  "bindings": [
    {
      "context": "Global",
      "bindings": {
        "meta+k": "app:toggleDiffNoiseFilter",
        "meta+j": "app:toggleDiffPreSession"
      }
    }
  ]
}
  • meta+k is the previous turn and meta+j the next. The action names do not match because a plugin cannot add an action: chat-toc borrows two that Claude Code names for its diff view and does not use in a normal session.
  • On macOS the terminal must send Option as Meta (iTerm2: Profiles → Keys → Left Option key: Esc+; Terminal: Settings → Profiles → Keyboard → Use Option as Meta key).
  • Use a modified key such as meta+k, not a chord such as ctrl+x p, and not a bare key: when nothing answers a chord (the contents and the line above the prompt both away), its second key is typed into the prompt; a bare key never reaches the buttons.
  • The keys press the ‹ › the contents draw, or, while the contents are not on screen (closed, waiting to dock, or a tab behind another pane), the ones on the line above the prompt. See Known limits for when nothing answers.

What it shows

  • Entries: each entry is one turn of the conversation: one input of yours, plus what the Agent did from that input to your next one. In conversation order. A slash command and a ! command are each an entry of their own; the same prompt submitted several times is several entries; a prompt queued while the Agent worked is an entry of its own too. Background task notifications and work started by /loop join the entry before them. Nothing from inside a subagent shows.
  • Time rail: entries hang on a vertical line on the left. Your input starts at a node ●, the Agent's reply at a branch ├╴; the lines after them and the gaps between groups on the same day sit against the line │.
  • Your input: its first 2 lines, with … at the end when it does not fit. A slash command shows as /command args, a ! command as !command; code blocks, Markdown marks and paste markers are left out, and an input of only images shows "(image only)". When a line folds, commas and full stops never start a line, and English words and file paths up to 14 characters move down whole.
  • The Agent's reply: dim text, first the state and the steps, then the last text reply, at most 2 lines:
  • N steps · reply…: the steps are how many tools the main Agent called this turn; parallel calls count one each, a subagent counts 1, thinking and text in between do not count. With 0 steps there is no count, only the reply.
  • N steps · No text reply: the turn ended without a text reply.
  • Interrupted · N steps: you interrupted the turn.
  • Error · N steps · error message: the turn ended in an API error. A single failed tool call is not an error.
  • Running · N steps: the Agent is still working on the last turn; an orange ◐ sits by the branch and the steps count up live.
  • A local slash command, or a ! command that set no Agent work going, has only your input. Under "Agent" those entries are hidden.
  • Time and day: times come from the transcript, in this machine's time zone. The card layout writes the time at the right end of your input's first line (HH:MM); Compact and Timeline write it in a column left of the line, on each turn's first row. Where the day changes (including at the top of the list) there is a line ─ Oct 9 ───; today reads Today · Oct 13, and another year's day carries the year. A turn whose time cannot be read gets no time and no day line; nothing is guessed.
  • Compactions: where the conversation was compacted, a dashed line crosses the list, ╌ Compacted 14:32 ╌╌╌ for /compact and ╌ Auto-compacted 14:32 ╌╌╌ for an automatic compaction, with the time it happened (no token figures). It never cuts a turn: after /compact it goes below the /compact entry; an automatic compaction goes before the turn it happened at the submit of, and after the turn it happened in the middle of (below a running turn, at the end of the list, until the next turn comes) or between two turns. It breaks the time rail as the day line does, in every layout, and shows under every view choice, between the turns shown; several between two turns shown fold into one line with a count and the last one's time, ╌ Compacted ×3 · 14:32 ╌. Where a day line meets it, the day line comes first. Entries above it are not dimmed.
  • Earlier turns elsewhere: when this session's file starts at a compaction whose earlier turns it does not hold (a session forked with --fork-session, or a resumed file compacted before any turn of its own), a dim line at the top of the list says "Earlier turns are not in this session's file". The earlier turns are not read from anywhere else.
  • Three layouts: Card (the default) draws up to 2 lines a side, the time at the right end; Compact draws one line a side, the time in the left column; Timeline draws up to 2 lines a side, the time in the left column. The layout changes only how entries are drawn, not which show. On a switch, the selected entry (or, with no selection, the turn being viewed) stays in view.
  • Highlight: marks the turn you are viewing in the transcript (the turn holding the top line of the screen; the last turn when the conversation's end shows whole): the node becomes ◉, the branch ┣╸, the turn's line thickens to ┃, all in the highlight colour along with the time. The highlight and the dim text follow Claude Code's theme (/theme); the highlight is the colour of a selected item in Claude Code's menus, and the contents follow a theme change at once. After a click jump, the highlight stays on the turn jumped to until you scroll the transcript again. A ! command's turn (and, in a session opened with --resume, a local slash command's turn) has no row in the transcript to locate: when only such a row shows at the top of the screen, the highlight falls on the next turn. The highlight and the ↑ ↓ selection (inverse) are independent and can sit on different entries. With the pointer over an entry, that side's text turns the highlight colour and the line right under the pointer is inverse.
  • Search: the row under the view choices holds f: Find and the field (words or a file path while empty). A search narrows the list to the turns that hold every word typed (words split on spaces), in any of: your input as you typed it (code blocks and pasted text included), the Agent's last text reply in full, and the full paths of the files the turn changed or read. Case does not matter, full-width and half-width letters match alike, and Chinese and other text match as plain substrings; no regular expressions, fuzzy matches or quoted phrases. It follows the view choice: All searches every field, User only your input, Agent only the reply and the paths. The row's right end says how many turns match of all (4 of 38) and holds ✕, then ‹ › (always there, see Controls); with no match the list says "No turns match". A new turn that matches joins the list, and a running turn is matched as it writes. While searching, the list keeps the match nearest the turn you are viewing in sight (the nearest at or before it, else the first after it), the highlight shows only when that turn itself matches, the compaction lines stay between the turns shown, and the row of blocks lights the blocks holding a match instead of the one you are on (all dim with no match). The search stays through a view or layout switch, new turns, another pane in front and closing and reopening the contents, until Claude Code quits; it is never stored.
  • Top and footer: the top holds the three view choices, a line under them marking the chosen one, the search row, then the row of blocks and the list. The footer sits at the bottom of the contents, under the preview when it is open: a thin line, under it on the left the number of turns ("12 turns", the same under every view choice) and on the right the layout button.
  • Context use: how full the context window was at the last response, as Claude Code reports it, follows the turn count in the footer: "38 turns · context 62% used"; when the contents are narrow it shortens to "38 turns · 62% used" and then leaves it out. The line above the prompt that stands in for contents not on screen adds "· context 62% used" after its counts. It is read when the session starts and after every response, always dim, never coloured, with nothing said near the limit (Claude Code warns before compacting by itself). Until the first response, and from a compaction until the next response, Claude Code has no figure, so none shows.
  • Preview: a region at the bottom, above the footer, shows one turn whole. It is closed until you ask for it: p, or p: Preview on the row under the selected entry, opens it on that turn, and it stays on it while you scroll the transcript, move the selection with ↑ ↓, switch the view or layout, new turns arrive or a search leaves that turn out of the list. Opening it from another entry moves it there. A click or Enter on an entry, Enter in the search field, a block in the row of blocks, ‹ › and the line above the prompt open nothing. It closes with p: ✕ at the right end of its top line, when its turn leaves the conversation (a rewind, /clear, another session), and whenever the contents open again; it is never stored. While searching, if the turn it shows matches, its line of figures ends with what matched, matched: User · Agent · files, and a matching file goes first on the files line (a matching file that was only read is named there). Its top line reads ─ Preview ─── p: ✕; under it:
  • a dim line of what the transcript states about the turn: Oct 16 14:32 · 12 steps · 3m 20s · 3.5k out. The date always shows, today too; a state (Running, Interrupted, Error) only when the turn did not finish. The duration is how long the model worked (45s, 3m 20s, 1h 5m, <1s), summed over the turn's model turns, and shows only when the transcript records it; the tokens are what the main Agent wrote (out), each response counted once, and show only when the transcript records them. Nothing is estimated: a figure the transcript does not state is left out. When the line is too wide for the contents, its last figures are left out whole;
  • User with r: Reuse · c: Copy at the right, then your input as you typed it: code blocks and pasted text kept, a slash command as /command args, a ! command as !command. An input of only images has neither button; an input with text and images fills or copies its text only, and a note says "Images not included";
  • Agent with a: Copy at the right, then the Agent's last text reply drawn as Markdown; a running turn shows the latest text written so far, without a: Copy;
  • the files the turn touched: changed register.tsx, pane.tsx +1 · read 5. Changed are files edited or written by the Agent and files a shell command changed as Claude Code recorded it; read are files the Agent read; a file both read and changed counts as changed. Paths are relative to the session's folder when inside it, else full with ~ for your home folder. The preview's height depends on the contents' height, not on the turn (about 40% of the rows below the top, 4 to 14 rows), so the list does not jump as you press another entry. What does not fit is cut, the files line first, then the Agent's reply, then your input; the last line then reads ↓ 12 more lines · e: expand. C
Source 2 files
hooks/register.js 114 lines
1import { atom, read, update } from 'claude-code'
2
3// Current position = smallest turn index among on-screen transcript rows (probe texts start with T###).
4const cpAtom = atom({ plugin: 'redraw-probe', key: 'cp' }, -1)
5const PANE = 'rp'
6let mode = 'off'
7let tag = ''
8const rows = new Map()
9const userIds = new Map()
10let cp = -1
11let pushed = -1
12let ev = []
13let cnt = {}
14let stamp = 0
15
16function bump(k) { cnt[k] = (cnt[k] || 0) + 1 }
17function computeCp() {
18  let m = Infinity
19  for (const r of rows.values()) if (r.os) m = Math.min(m, r.turn)
20  return m === Infinity ? -1 : m
21}
22function fail(where, err) { bump('err:' + where); ev.push([Date.now(), 'err', where, String(err && err.message || err).slice(0, 200)]) }
23
24function onChange($) {
25  if (mode === 'sync') {
26    bump('inv')
27    try { $.ui.invalidate('ui.render') } catch (err) { fail('sync', err) }
28  } else if (mode === 'micro') {
29    Promise.resolve().then(() => { bump('inv'); $.ui.invalidate('ui.render') }).catch(err => fail('micro', err))
30  } else if (mode === 'after-inv') {
31    $.clock.after(0, () => { bump('inv'); try { $.ui.invalidate('ui.render') } catch (err) { fail('after-inv', err) } })
32  } else if (mode === 'micro-state') {
33    Promise.resolve().then(() => { bump('set'); return update($, cpAtom, () => cp) }).catch(err => fail('micro-state', err))
34  } else if (mode === 'after-state') {
35    $.clock.after(0, () => { bump('set'); update($, cpAtom, () => cp).catch(err => fail('after-state', err)) })
36  }
37}
38
39async function dump($) {
40  const name = `${$.plugin.root}/../logs/${tag}-${mode}-${stamp}.json`
41  await $.fs.write(name, JSON.stringify({ tag, mode, cnt, ev }))
42  const n = ev.length
43  ev = []
44  cnt = {}
45  return name + ' events=' + n
46}
47
48export const register = on => {
49  on('session.start', async ($, e, next) => {
50    mode = (await $.env.get('RP_MODE')) || 'off'
51    tag = (await $.env.get('RP_TAG')) || 'x'
52    stamp = await $.clock.now()
53    await $.command.register({ name: 'rpd', description: 'Dump redraw-probe counters', immediate: true })
54    await $.command.register({ name: 'rp', description: 'Open redraw-probe pane', immediate: true })
55    const r = await $.ui.open({ id: PANE, title: 'RP ' + mode, columns: 44 })
56    ev.push([Date.now(), 'open', JSON.stringify(r)])
57    if (mode === 'poll-state' || mode === 'poll-inv') {
58      $.clock.every(50, () => {
59        bump('tick')
60        if (cp === pushed) return
61        pushed = cp
62        if (mode === 'poll-state') { bump('set'); update($, cpAtom, () => cp).catch(err => fail('poll-state', err)) }
63        else { bump('inv'); try { $.ui.invalidate('ui.render') } catch (err) { fail('poll-inv', err) } }
64      })
65    }
66    return next(e)
67  })
68  for (const c of ['UserMessage', 'AssistantMessage']) {
69    on('ui.render', { component: c }, async ($, e, next) => {
70      bump('t:' + c)
71      const p = e.props
72      const m = /^T(\d{3})/.exec(p.text || '')
73      if (m && e.requestId !== 'placeholder') {
74        const turn = Number(m[1])
75        if (c === 'UserMessage') userIds.set(turn, e.requestId)
76        rows.set(e.requestId, { turn, os: p.onScreen || null })
77        const n = computeCp()
78        if (n !== cp) {
79          cp = n
80          ev.push([Date.now(), 'cp', n])
81          onChange($)
82        }
83      }
84      if (mode === 'loop') {
85        bump('inv')
86        try { $.ui.invalidate('ui.render') } catch (err) { fail('loop', err) }
87      }
88      return next(e)
89    })
90  }
91  on('command.run', { command: 'rpd' }, async ($) => ({ text: await dump($) }))
92  on('command.run', { command: 'rp' }, async ($) => ({ text: JSON.stringify(await $.ui.open({ id: PANE, title: 'RP ' + mode, columns: 44 })) }))
93  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
94    bump('pane')
95    const { Box, Text, Button } = $.ui.resolve(e)
96    const viaState = mode.endsWith('state')
97    const shown = viaState ? await read($, cpAtom) : cp
98    ev.push([Date.now(), 'pane', shown])
99    const items = []
100    for (let i = 0; i < 300; i++) {
101      const id = userIds.get(i)
102      items.push(Button({
103        key: 'b' + i,
104        label: (i === shown ? '▶ ' : '  ') + 'T' + String(i).padStart(3, '0') + ' 你 · 用户问题:请检查第 ' + i + ' 个模块',
105        onPress: () => { if (id) $.ui.scroll({ to: { requestId: id }, block: 'start' }) },
106      }))
107    }
108    return Box({
109      flexDirection: 'column',
110      children: [Text({ children: ['cp=T' + shown + ' pane=' + (cnt.pane || 0) + ' inv=' + (cnt.inv || 0) + ' set=' + (cnt.set || 0)] }), ...items],
111    })
112  })
113}
114
types/index.d.ts 8 lines
1export type Cp = number
2
3declare module 'claude-code' {
4  interface PluginState {
5    'redraw-probe': { cp: Cp }
6  }
7}
8