@@ -58,6 +58,22 @@ function render(ui: ReactElement) {
5858 flushSync ( ( ) => root ! . render ( < ThemeProvider > { ui } </ ThemeProvider > ) ) ;
5959}
6060
61+ function fakeScrollGeometry (
62+ element : HTMLElement ,
63+ { scrollHeight = 1000 , clientHeight = 400 , scrollTop = 600 } = { } ,
64+ ) {
65+ let currentScrollTop = scrollTop ;
66+ Object . defineProperty ( element , "scrollHeight" , { value : scrollHeight , configurable : true } ) ;
67+ Object . defineProperty ( element , "clientHeight" , { value : clientHeight , configurable : true } ) ;
68+ Object . defineProperty ( element , "scrollTop" , {
69+ get : ( ) => currentScrollTop ,
70+ set : ( value : number ) => {
71+ currentScrollTop = value ;
72+ } ,
73+ configurable : true ,
74+ } ) ;
75+ }
76+
6177describe ( "TaskChatThread draft pass-through" , ( ) => {
6278 it ( "keeps the composer dock aligned with the thread's horizontal padding" , ( ) => {
6379 render (
@@ -203,6 +219,110 @@ describe("TaskChatThread blocker links", () => {
203219 expect ( container . textContent ) . not . toContain ( "Ultimately blocked by" ) ;
204220 } ) ;
205221
222+ it ( "keeps a server-selected intermediate blocker on its direct chain" , ( ) => {
223+ const selectedIntermediate = {
224+ id : "intermediate-2" ,
225+ identifier : "PAP-650" ,
226+ title : "Stalled intermediate review" ,
227+ } ;
228+ const selectedDirect = {
229+ id : "direct-2" ,
230+ identifier : "PAP-600" ,
231+ title : "Selected dependency" ,
232+ status : "blocked" as const ,
233+ priority : "medium" as const ,
234+ assigneeAgentId : "agent-1" ,
235+ assigneeUserId : null ,
236+ terminalBlockers : [ {
237+ id : "leaf-2" ,
238+ identifier : "PAP-700" ,
239+ title : "Deeper structural leaf" ,
240+ status : "todo" as const ,
241+ priority : "medium" as const ,
242+ assigneeAgentId : "agent-2" ,
243+ assigneeUserId : null ,
244+ } ] ,
245+ } ;
246+
247+ render (
248+ < TaskChatThread
249+ comments = { [ ] }
250+ onAdd = { async ( ) => { } }
251+ issueStatus = "blocked"
252+ blockedBy = { [
253+ {
254+ id : "direct-1" ,
255+ identifier : "PAP-500" ,
256+ title : "Unrelated dependency" ,
257+ status : "todo" ,
258+ priority : "low" ,
259+ assigneeAgentId : null ,
260+ assigneeUserId : null ,
261+ } ,
262+ selectedDirect ,
263+ ] }
264+ blockerAttention = { {
265+ state : "stalled" ,
266+ reason : "stalled_review" ,
267+ unresolvedBlockerCount : 2 ,
268+ coveredBlockerCount : 0 ,
269+ stalledBlockerCount : 1 ,
270+ attentionBlockerCount : 1 ,
271+ sampleBlockerIdentifier : "PAP-650" ,
272+ sampleStalledBlockerIdentifier : "PAP-650" ,
273+ directBlockerIssueId : selectedDirect . id ,
274+ terminalBlockerIssueId : selectedIntermediate . id ,
275+ terminalBlocker : selectedIntermediate ,
276+ } }
277+ /> ,
278+ ) ;
279+
280+ for ( const notice of container . querySelectorAll ( '[data-testid="task-chat-blocker-links"]' ) ) {
281+ expect ( notice . textContent ) . toContain ( "Blocked byPAP-600Selected dependency" ) ;
282+ expect ( notice . textContent ) . toContain ( "Ultimately blocked byPAP-650Stalled intermediate review" ) ;
283+ }
284+ expect ( container . textContent ) . not . toContain ( "Unrelated dependency" ) ;
285+ expect ( container . textContent ) . not . toContain ( "Deeper structural leaf" ) ;
286+ } ) ;
287+
288+ it ( "auto-follows the new bottom blocker row when a pinned thread becomes blocked" , ( ) => {
289+ const comment = {
290+ id : "comment-1" ,
291+ companyId : "company-1" ,
292+ issueId : "issue-1" ,
293+ authorType : "user" as const ,
294+ authorAgentId : null ,
295+ authorUserId : "user-1" ,
296+ body : "Waiting for the dependency." ,
297+ presentation : null ,
298+ metadata : null ,
299+ createdAt : new Date ( "2026-08-15T12:00:00.000Z" ) ,
300+ updatedAt : new Date ( "2026-08-15T12:00:00.000Z" ) ,
301+ } ;
302+ const directBlocker = {
303+ id : "direct-1" ,
304+ identifier : "PAP-500" ,
305+ title : "Direct dependency" ,
306+ status : "in_progress" as const ,
307+ priority : "medium" as const ,
308+ assigneeAgentId : "agent-1" ,
309+ assigneeUserId : null ,
310+ } ;
311+ const baseProps = {
312+ comments : [ comment ] ,
313+ onAdd : async ( ) => { } ,
314+ blockedBy : [ directBlocker ] ,
315+ } ;
316+
317+ render ( < TaskChatThread { ...baseProps } issueStatus = "in_progress" /> ) ;
318+ const scroller = container . querySelector < HTMLElement > ( '[data-testid="task-chat-scroller"]' ) ! ;
319+ fakeScrollGeometry ( scroller ) ;
320+
321+ render ( < TaskChatThread { ...baseProps } issueStatus = "blocked" /> ) ;
322+
323+ expect ( scroller . scrollTop ) . toBe ( scroller . scrollHeight ) ;
324+ } ) ;
325+
206326 it ( "does not show blocker rows outside the blocked state" , ( ) => {
207327 render (
208328 < TaskChatThread
0 commit comments