@@ -4,6 +4,7 @@ import { useMutation } from "@tanstack/solid-query"
44import { Button } from "@opencode-ai/ui/button"
55import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
66import { Icon } from "@opencode-ai/ui/icon"
7+ import { IconButton } from "@opencode-ai/ui/icon-button"
78import { showToast } from "@opencode-ai/ui/toast"
89import type { QuestionAnswer , QuestionRequest } from "@opencode-ai/sdk/v2"
910import { useLanguage } from "@/context/language"
@@ -73,6 +74,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
7374 customOn : cached ?. customOn ?? ( [ ] as boolean [ ] ) ,
7475 editing : false ,
7576 focus : 0 ,
77+ sending : false ,
78+ collapsed : false ,
7679 } )
7780
7881 let root : HTMLDivElement | undefined
@@ -87,6 +90,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
8790 const on = createMemo ( ( ) => store . customOn [ store . tab ] === true )
8891 const multi = createMemo ( ( ) => question ( ) ?. multiple === true )
8992 const count = createMemo ( ( ) => options ( ) . length + 1 )
93+ const selectedCount = createMemo ( ( ) => store . answers [ store . tab ] ?. length ?? 0 )
9094
9195 const summary = createMemo ( ( ) => {
9296 const n = Math . min ( store . tab + 1 , total ( ) )
@@ -98,6 +102,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
98102
99103 const last = createMemo ( ( ) => store . tab >= total ( ) - 1 )
100104
105+ const fold = ( ) => setStore ( "collapsed" , ( value ) => ! value )
106+
101107 const customUpdate = ( value : string , selected : boolean = on ( ) ) => {
102108 const prev = input ( ) . trim ( )
103109 const next = value . trim ( )
@@ -426,9 +432,21 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
426432 ref = { ( el ) => ( root = el ) }
427433 onKeyDown = { nav }
428434 header = {
429- < >
435+ < div
436+ data-action = "session-question-toggle"
437+ class = "flex flex-1 min-w-0 items-center gap-2 cursor-default select-none"
438+ role = "button"
439+ tabIndex = { 0 }
440+ style = { { margin : "0 -10px" , padding : "0 0 0 10px" } }
441+ onClick = { fold }
442+ onKeyDown = { ( event ) => {
443+ if ( event . key !== "Enter" && event . key !== " " ) return
444+ event . preventDefault ( )
445+ fold ( )
446+ } }
447+ >
430448 < div data-slot = "question-header-title" > { summary ( ) } </ div >
431- < div data-slot = "question-progress" >
449+ < div data-slot = "question-progress" class = "ml-auto mr-1" >
432450 < For each = { questions ( ) } >
433451 { ( _ , i ) => (
434452 < button
@@ -437,13 +455,38 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
437455 data-active = { i ( ) === store . tab }
438456 data-answered = { answered ( i ( ) ) }
439457 disabled = { sending ( ) }
440- onClick = { ( ) => jump ( i ( ) ) }
458+ onMouseDown = { ( event ) => {
459+ event . preventDefault ( )
460+ event . stopPropagation ( )
461+ } }
462+ onClick = { ( event ) => {
463+ event . stopPropagation ( )
464+ jump ( i ( ) )
465+ } }
441466 aria-label = { `${ language . t ( "ui.tool.questions" ) } ${ i ( ) + 1 } ` }
442467 />
443468 ) }
444469 </ For >
445470 </ div >
446- </ >
471+ < div >
472+ < IconButton
473+ data-action = "session-question-toggle-button"
474+ icon = "chevron-down"
475+ size = "normal"
476+ variant = "ghost"
477+ classList = { { "rotate-180" : store . collapsed } }
478+ onMouseDown = { ( event ) => {
479+ event . preventDefault ( )
480+ event . stopPropagation ( )
481+ } }
482+ onClick = { ( event ) => {
483+ event . stopPropagation ( )
484+ fold ( )
485+ } }
486+ aria-label = { store . collapsed ? language . t ( "session.todo.expand" ) : language . t ( "session.todo.collapse" ) }
487+ />
488+ </ div >
489+ </ div >
447490 }
448491 footer = {
449492 < >
@@ -469,72 +512,95 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
469512 </ >
470513 }
471514 >
472- < div data-slot = "question-text" > { question ( ) ?. question } </ div >
473- < Show when = { multi ( ) } fallback = { < div data-slot = "question-hint" > { language . t ( "ui.question.singleHint" ) } </ div > } >
474- < div data-slot = "question-hint" > { language . t ( "ui.question.multiHint" ) } </ div >
515+ < div
516+ data-slot = "question-text"
517+ class = "cursor-default"
518+ classList = { {
519+ "mb-6" : store . collapsed && selectedCount ( ) === 0 ,
520+ } }
521+ role = { store . collapsed ? "button" : undefined }
522+ tabIndex = { store . collapsed ? 0 : undefined }
523+ onClick = { fold }
524+ onKeyDown = { ( event ) => {
525+ if ( ! store . collapsed ) return
526+ if ( event . key !== "Enter" && event . key !== " " ) return
527+ event . preventDefault ( )
528+ fold ( )
529+ } }
530+ >
531+ { question ( ) ?. question }
532+ </ div >
533+ < Show when = { store . collapsed && selectedCount ( ) > 0 } >
534+ < div data-slot = "question-hint" class = "cursor-default mb-6" >
535+ { selectedCount ( ) } answer{ selectedCount ( ) === 1 ? "" : "s" } selected
536+ </ div >
475537 </ Show >
476- < div data-slot = "question-options" >
477- < For each = { options ( ) } >
478- { ( opt , i ) => (
479- < Option
480- multi = { multi ( ) }
481- picked = { picked ( opt . label ) }
482- label = { opt . label }
483- description = { opt . description }
484- disabled = { sending ( ) }
485- ref = { ( el ) => ( optsRef [ i ( ) ] = el ) }
486- onFocus = { ( ) => setStore ( "focus" , i ( ) ) }
487- onClick = { ( ) => selectOption ( i ( ) ) }
488- />
489- ) }
490- </ For >
491-
492- < Show
493- when = { store . editing }
494- fallback = {
495- < button
496- type = "button"
497- ref = { customRef }
538+ < div data-slot = "question-answers" hidden = { store . collapsed } aria-hidden = { store . collapsed } >
539+ < Show when = { multi ( ) } fallback = { < div data-slot = "question-hint" > { language . t ( "ui.question.singleHint" ) } </ div > } >
540+ < div data-slot = "question-hint" > { language . t ( "ui.question.multiHint" ) } </ div >
541+ </ Show >
542+ < div data-slot = "question-options" >
543+ < For each = { options ( ) } >
544+ { ( opt , i ) => (
545+ < Option
546+ multi = { multi ( ) }
547+ picked = { picked ( opt . label ) }
548+ label = { opt . label }
549+ description = { opt . description }
550+ disabled = { sending ( ) }
551+ ref = { ( el ) => ( optsRef [ i ( ) ] = el ) }
552+ onFocus = { ( ) => setStore ( "focus" , i ( ) ) }
553+ onClick = { ( ) => selectOption ( i ( ) ) }
554+ />
555+ ) }
556+ </ For >
557+
558+ < Show
559+ when = { store . editing }
560+ fallback = {
561+ < button
562+ type = "button"
563+ ref = { customRef }
564+ data-slot = "question-option"
565+ data-custom = "true"
566+ data-picked = { on ( ) }
567+ role = { multi ( ) ? "checkbox" : "radio" }
568+ aria-checked = { on ( ) }
569+ disabled = { sending ( ) }
570+ onFocus = { ( ) => setStore ( "focus" , options ( ) . length ) }
571+ onClick = { customOpen }
572+ >
573+ < Mark multi = { multi ( ) } picked = { on ( ) } onClick = { toggleCustomMark } />
574+ < span data-slot = "question-option-main" >
575+ < span data-slot = "option-label" > { customLabel ( ) } </ span >
576+ < span data-slot = "option-description" > { input ( ) || customPlaceholder ( ) } </ span >
577+ </ span >
578+ </ button >
579+ }
580+ >
581+ < form
498582 data-slot = "question-option"
499583 data-custom = "true"
500584 data-picked = { on ( ) }
501585 role = { multi ( ) ? "checkbox" : "radio" }
502586 aria-checked = { on ( ) }
503- disabled = { sending ( ) }
504- onFocus = { ( ) => setStore ( "focus" , options ( ) . length ) }
505- onClick = { customOpen }
587+ onMouseDown = { ( e ) => {
588+ if ( sending ( ) ) {
589+ e . preventDefault ( )
590+ return
591+ }
592+ if ( e . target instanceof HTMLTextAreaElement ) return
593+ const input = e . currentTarget . querySelector ( '[data-slot="question-custom-input"]' )
594+ if ( input instanceof HTMLTextAreaElement ) input . focus ( )
595+ } }
596+ onSubmit = { ( e ) => {
597+ e . preventDefault ( )
598+ commitCustom ( )
599+ } }
506600 >
507601 < Mark multi = { multi ( ) } picked = { on ( ) } onClick = { toggleCustomMark } />
508602 < span data-slot = "question-option-main" >
509603 < span data-slot = "option-label" > { customLabel ( ) } </ span >
510- < span data-slot = "option-description" > { input ( ) || customPlaceholder ( ) } </ span >
511- </ span >
512- </ button >
513- }
514- >
515- < form
516- data-slot = "question-option"
517- data-custom = "true"
518- data-picked = { on ( ) }
519- role = { multi ( ) ? "checkbox" : "radio" }
520- aria-checked = { on ( ) }
521- onMouseDown = { ( e ) => {
522- if ( sending ( ) ) {
523- e . preventDefault ( )
524- return
525- }
526- if ( e . target instanceof HTMLTextAreaElement ) return
527- const input = e . currentTarget . querySelector ( '[data-slot="question-custom-input"]' )
528- if ( input instanceof HTMLTextAreaElement ) input . focus ( )
529- } }
530- onSubmit = { ( e ) => {
531- e . preventDefault ( )
532- commitCustom ( )
533- } }
534- >
535- < Mark multi = { multi ( ) } picked = { on ( ) } onClick = { toggleCustomMark } />
536- < span data-slot = "question-option-main" >
537- < span data-slot = "option-label" > { customLabel ( ) } </ span >
538604 < textarea
539605 ref = { focusCustom }
540606 data-slot = "question-custom-input"
0 commit comments