Skip to content

Commit d2f69a4

Browse files
committed
feat(iris): redesign context dropdown with sectioned scroll layout
Restructures the dropdown into four sections, each capped at ~3 visible rows with internal scroll: 1. Conversations (when context is active and search is empty) — sessions list + "+ New Conversation" button 2. Workspace shortcuts — "Chat about Workspace Exercise" (existing) plus new "Chat about Workspace Course" button (resolves the course of the workspace exercise via courseId lookup) 3. Exercises — sorted, with course shortName tags 4. Courses — sorted Workspace shortcut buttons hide while searching to keep filter results clean. Dropdown max-height changes from a fixed 480px to calc(100vh - 80px), so it can extend to the bottom of the panel when many sections are populated, while internal section scrolls keep each section compact.
1 parent c679fff commit d2f69a4

2 files changed

Lines changed: 161 additions & 98 deletions

File tree

extension/src/webview/views/IrisChat/components/ContextSelector.module.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,23 @@
8383
border: 1px solid var(--vscode-notifications-border);
8484
border-radius: 0 0 6px 6px;
8585
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
86-
max-height: 480px;
86+
max-height: calc(100vh - 80px);
8787
display: flex;
8888
flex-direction: column;
8989
z-index: 1000;
9090
overflow: hidden;
9191
}
9292

93+
.sessionsScroll {
94+
max-height: 168px;
95+
overflow-y: auto;
96+
}
97+
98+
.itemsScroll {
99+
max-height: 108px;
100+
overflow-y: auto;
101+
}
102+
93103
.searchContainer {
94104
padding: 6px;
95105
border-bottom: 1px solid var(--vscode-panel-border);

extension/src/webview/views/IrisChat/components/ContextSelector.tsx

Lines changed: 150 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,22 @@ export function ContextSelector({
6464
)
6565
: courses;
6666

67-
const hasWorkspaceExercise = exercises.some(ex => ex.isWorkspace);
67+
const workspaceExercise = exercises.find(ex => ex.isWorkspace);
68+
const workspaceCourse = workspaceExercise?.courseId
69+
? courses.find(c => c.id === workspaceExercise.courseId)
70+
: undefined;
71+
6872
const isInWorkspaceContext = context?.source === 'workspace-detected';
73+
const isInWorkspaceCourseContext = !!workspaceCourse
74+
&& context?.type === 'course'
75+
&& context.id === workspaceCourse.id;
6976
const canCreateNewSession = sessions.length > 0 && sessions.some(s => s.messageCount > 0);
7077
const activeSession = context ? sessions.find(s => s.id === activeSessionId) : undefined;
7178
const messageCount = activeSession?.messageCount || 0;
72-
const showSessionsSection = context !== null && q.length === 0;
79+
80+
const showShortcuts = q.length === 0;
81+
const showConversationsSection = context !== null && q.length === 0;
82+
const showWorkspaceShortcuts = showShortcuts && (workspaceExercise || workspaceCourse);
7383

7484
const toggleDropdown = () => {
7585
setIsOpen(!isOpen);
@@ -171,60 +181,66 @@ export function ContextSelector({
171181
</div>
172182

173183
<div className={styles.dropdownContent}>
174-
{showSessionsSection && (
184+
{showConversationsSection && (
175185
<div className={styles.section}>
176-
<div className={styles.sectionHeader}>Sessions</div>
177-
{sessions.map(session => (
178-
<button
179-
key={session.id}
180-
className={clsx(styles.sessionItem, {
181-
[styles.sessionItemActive]:
182-
session.id === activeSessionId,
183-
})}
184-
onClick={() => handleSelectSession(session.id)}
185-
>
186-
<svg
187-
width="14"
188-
height="14"
189-
viewBox="0 0 24 24"
190-
fill="none"
191-
className={styles.sessionIcon}
192-
>
193-
<path
194-
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
195-
stroke="currentColor"
196-
strokeWidth="2"
197-
strokeLinecap="round"
198-
strokeLinejoin="round"
199-
/>
200-
</svg>
201-
<div className={styles.sessionContent}>
202-
<span className={styles.sessionPreview}>
203-
{session.title || session.preview}
204-
</span>
205-
<span className={styles.sessionMeta}>
206-
{session.messageCount} messages · {formatRelativeTime(session.lastActivity)}
207-
</span>
186+
{sessions.length > 0 && (
187+
<>
188+
<div className={styles.sectionHeader}>Conversations</div>
189+
<div className={styles.sessionsScroll}>
190+
{sessions.map(session => (
191+
<button
192+
key={session.id}
193+
className={clsx(styles.sessionItem, {
194+
[styles.sessionItemActive]:
195+
session.id === activeSessionId,
196+
})}
197+
onClick={() => handleSelectSession(session.id)}
198+
>
199+
<svg
200+
width="14"
201+
height="14"
202+
viewBox="0 0 24 24"
203+
fill="none"
204+
className={styles.sessionIcon}
205+
>
206+
<path
207+
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
208+
stroke="currentColor"
209+
strokeWidth="2"
210+
strokeLinecap="round"
211+
strokeLinejoin="round"
212+
/>
213+
</svg>
214+
<div className={styles.sessionContent}>
215+
<span className={styles.sessionPreview}>
216+
{session.title || session.preview}
217+
</span>
218+
<span className={styles.sessionMeta}>
219+
{session.messageCount} messages · {formatRelativeTime(session.lastActivity)}
220+
</span>
221+
</div>
222+
{session.id === activeSessionId && (
223+
<svg
224+
width="16"
225+
height="16"
226+
viewBox="0 0 24 24"
227+
fill="none"
228+
className={styles.checkIcon}
229+
>
230+
<polyline
231+
points="20 6 9 17 4 12"
232+
stroke="currentColor"
233+
strokeWidth="2"
234+
strokeLinecap="round"
235+
strokeLinejoin="round"
236+
/>
237+
</svg>
238+
)}
239+
</button>
240+
))}
208241
</div>
209-
{session.id === activeSessionId && (
210-
<svg
211-
width="16"
212-
height="16"
213-
viewBox="0 0 24 24"
214-
fill="none"
215-
className={styles.checkIcon}
216-
>
217-
<polyline
218-
points="20 6 9 17 4 12"
219-
stroke="currentColor"
220-
strokeWidth="2"
221-
strokeLinecap="round"
222-
strokeLinejoin="round"
223-
/>
224-
</svg>
225-
)}
226-
</button>
227-
))}
242+
</>
243+
)}
228244
<div className={styles.actions}>
229245
<button
230246
className={styles.actionButton}
@@ -242,7 +258,14 @@ export function ContextSelector({
242258
New Conversation
243259
</span>
244260
</button>
245-
{hasWorkspaceExercise && (
261+
</div>
262+
</div>
263+
)}
264+
265+
{showWorkspaceShortcuts && (
266+
<div className={styles.section}>
267+
<div className={styles.actions}>
268+
{workspaceExercise && (
246269
<button
247270
className={styles.actionButton}
248271
disabled={isInWorkspaceContext}
@@ -253,7 +276,33 @@ export function ContextSelector({
253276
>
254277
<span className={styles.actionButtonContent}>
255278
<FolderGit2 size={14} />
256-
{isInWorkspaceContext ? 'Workspace Exercise (Active)' : 'Chat about Workspace Exercise'}
279+
{isInWorkspaceContext
280+
? `Workspace Exercise (Active): ${workspaceExercise.title}`
281+
: `Chat about Workspace Exercise: ${workspaceExercise.title}`}
282+
</span>
283+
</button>
284+
)}
285+
{workspaceCourse && (
286+
<button
287+
className={styles.actionButton}
288+
disabled={isInWorkspaceCourseContext}
289+
onClick={() => {
290+
handleSelectContext(
291+
'course',
292+
workspaceCourse.id,
293+
workspaceCourse.title,
294+
workspaceCourse.shortName
295+
);
296+
}}
297+
>
298+
<span className={styles.actionButtonContent}>
299+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
300+
<path d="M22 10v6M2 10l10-5 10 5-10 5z" />
301+
<path d="M6 12v5c3 3 9 3 12 0v-5" />
302+
</svg>
303+
{isInWorkspaceCourseContext
304+
? `Workspace Course (Active): ${workspaceCourse.title}`
305+
: `Chat about Workspace Course: ${workspaceCourse.title}`}
257306
</span>
258307
</button>
259308
)}
@@ -264,57 +313,61 @@ export function ContextSelector({
264313
{filteredExercises.length > 0 && (
265314
<div className={styles.section}>
266315
<div className={styles.sectionHeader}>Exercises</div>
267-
{filteredExercises.map((exercise) => {
268-
const courseTag = exercise.courseId ? courseShortById.get(exercise.courseId) : undefined;
269-
return (
316+
<div className={styles.itemsScroll}>
317+
{filteredExercises.map((exercise) => {
318+
const courseTag = exercise.courseId ? courseShortById.get(exercise.courseId) : undefined;
319+
return (
320+
<button
321+
key={exercise.id}
322+
className={styles.contextItem}
323+
onClick={() =>
324+
handleSelectContext(
325+
'exercise',
326+
exercise.id,
327+
exercise.title,
328+
exercise.shortName
329+
)
330+
}
331+
>
332+
{exercise.isWorkspace && (
333+
<FolderGit2 size={14} className={styles.itemIcon} />
334+
)}
335+
<span className={styles.itemText}>
336+
{exercise.title}
337+
</span>
338+
{courseTag && (
339+
<span className={styles.itemTag}>{courseTag}</span>
340+
)}
341+
</button>
342+
);
343+
})}
344+
</div>
345+
</div>
346+
)}
347+
348+
{filteredCourses.length > 0 && (
349+
<div className={styles.section}>
350+
<div className={styles.sectionHeader}>Courses</div>
351+
<div className={styles.itemsScroll}>
352+
{filteredCourses.map((course) => (
270353
<button
271-
key={exercise.id}
354+
key={course.id}
272355
className={styles.contextItem}
273356
onClick={() =>
274357
handleSelectContext(
275-
'exercise',
276-
exercise.id,
277-
exercise.title,
278-
exercise.shortName
358+
'course',
359+
course.id,
360+
course.title,
361+
course.shortName
279362
)
280363
}
281364
>
282-
{exercise.isWorkspace && (
283-
<FolderGit2 size={14} className={styles.itemIcon} />
284-
)}
285365
<span className={styles.itemText}>
286-
{exercise.title}
366+
{course.title}
287367
</span>
288-
{courseTag && (
289-
<span className={styles.itemTag}>{courseTag}</span>
290-
)}
291368
</button>
292-
);
293-
})}
294-
</div>
295-
)}
296-
297-
{filteredCourses.length > 0 && (
298-
<div className={styles.section}>
299-
<div className={styles.sectionHeader}>Courses</div>
300-
{filteredCourses.map((course) => (
301-
<button
302-
key={course.id}
303-
className={styles.contextItem}
304-
onClick={() =>
305-
handleSelectContext(
306-
'course',
307-
course.id,
308-
course.title,
309-
course.shortName
310-
)
311-
}
312-
>
313-
<span className={styles.itemText}>
314-
{course.title}
315-
</span>
316-
</button>
317-
))}
369+
))}
370+
</div>
318371
</div>
319372
)}
320373

0 commit comments

Comments
 (0)