Skip to content

Commit bf79586

Browse files
committed
wip: shadow dom fix
1 parent 9f01e22 commit bf79586

File tree

10 files changed

+116
-58
lines changed

10 files changed

+116
-58
lines changed

front_end/panels/ai_chat/ui/AgentSessionHeaderComponent.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export type SessionStatus = 'running' | 'completed' | 'error';
1414
@customElement('agent-session-header')
1515
export class AgentSessionHeaderComponent extends HTMLElement {
1616
static readonly litTagName = Lit.StaticHtml.literal`agent-session-header`;
17-
private readonly shadow = this.attachShadow({mode: 'open'});
17+
// Use Light DOM
18+
// private readonly shadow = this.attachShadow({mode: 'open'});
19+
private readonly shadow = this;
1820

1921
private session: AgentSession | null = null;
2022
private isExpanded = true;
@@ -80,7 +82,7 @@ export class AgentSessionHeaderComponent extends HTMLElement {
8082

8183
Lit.render(html`
8284
<style>
83-
:host {
85+
agent-session-header {
8486
display: block;
8587
}
8688

front_end/panels/ai_chat/ui/ChatView.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ export interface Props {
177177
@customElement('devtools-chat-view')
178178
export class ChatView extends HTMLElement {
179179
static readonly litTagName = Lit.StaticHtml.literal`devtools-chat-view`;
180-
readonly #shadow = this.attachShadow({mode: 'open'});
180+
// readonly #shadow = this.attachShadow({mode: 'open'});
181+
// Use Light DOM for accessibility/automation
182+
readonly #shadow = this;
181183
readonly #boundRender = this.#render.bind(this);
182184

183185
#messages: ChatMessage[] = [];
@@ -235,10 +237,6 @@ export class ChatView extends HTMLElement {
235237
#isVersionBannerDismissed = false;
236238

237239
connectedCallback(): void {
238-
const sheet = new CSSStyleSheet();
239-
sheet.replaceSync(chatViewStyles);
240-
this.#shadow.adoptedStyleSheets = [sheet];
241-
242240
// Initialize the prompt button click handler
243241
this.#updatePromptButtonClickHandler();
244242

@@ -779,6 +777,9 @@ export class ChatView extends HTMLElement {
779777
// All messages are rendered directly now, including AgentSessionMessage
780778
let messagesToRender = this.#messages;
781779

780+
const cssText = (chatViewStyles as any).cssText || chatViewStyles.toString();
781+
const stylesTemplate = html`<style>${cssText.replace(/:host/g, 'devtools-chat-view')}</style>`;
782+
782783
// Build a set of nested child session IDs present in the current message set.
783784
// Include both nestedSessions[].sessionId and any handoff anchors in messages that
784785
// have a concrete nestedSessionId (ignore pending-* placeholders). Also build
@@ -865,6 +866,7 @@ export class ChatView extends HTMLElement {
865866

866867
const suggestions = this.#renderExampleSuggestions();
867868
Lit.render(html`
869+
${stylesTemplate}
868870
<div class="chat-view-container centered-view">
869871
${this.#renderVersionBanner()}
870872
<div class="centered-content">
@@ -886,6 +888,7 @@ export class ChatView extends HTMLElement {
886888
} else {
887889
// Render normal expanded view for conversation
888890
Lit.render(html`
891+
${stylesTemplate}
889892
<div class="chat-view-container expanded-view">
890893
${this.#renderVersionBanner()}
891894
<ai-message-list .messages=${[]} .state=${this.#state} .agentViewMode=${this.#agentViewMode}>

front_end/panels/ai_chat/ui/ConversationHistoryList.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ const {unsafeHTML} = Directives;
1818
*/
1919
export class ConversationHistoryList extends HTMLElement {
2020
static readonly litTagName = Lit.StaticHtml.literal`ai-conversation-history-list`;
21-
readonly #shadow = this.attachShadow({mode: 'open'});
21+
// Use Light DOM
22+
// readonly #shadow = this.attachShadow({mode: 'open'});
23+
readonly #shadow = this;
2224
readonly #boundRender = this.#render.bind(this);
2325

2426
#conversations: ConversationMetadata[] = [];
@@ -119,7 +121,7 @@ export class ConversationHistoryList extends HTMLElement {
119121
Lit.render(
120122
html`
121123
<style>
122-
${unsafeHTML(getConversationHistoryStyles())}
124+
${unsafeHTML(getConversationHistoryStyles().replace(/:host/g, 'ai-conversation-history-list'))}
123125
</style>
124126
125127
<div class="history-content">

front_end/panels/ai_chat/ui/FileListDisplay.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ const {html, nothing} = Lit;
1717
*/
1818
export class FileListDisplay extends HTMLElement {
1919
static readonly litTagName = Lit.StaticHtml.literal`ai-file-list-display`;
20-
readonly #shadow = this.attachShadow({mode: 'open'});
20+
// Use Light DOM
21+
// readonly #shadow = this.attachShadow({mode: 'open'});
22+
readonly #shadow = this;
2123
readonly #boundRender = this.#render.bind(this);
2224

2325
#files: FileSummary[] = [];
@@ -212,7 +214,7 @@ export class FileListDisplay extends HTMLElement {
212214

213215
Lit.render(html`
214216
<style>
215-
:host {
217+
ai-file-list-display {
216218
display: block;
217219
}
218220

front_end/panels/ai_chat/ui/LiveAgentSessionComponent.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ const {customElement} = Decorators;
1616
@customElement('live-agent-session')
1717
export class LiveAgentSessionComponent extends HTMLElement {
1818
static readonly litTagName = Lit.StaticHtml.literal`live-agent-session`;
19-
private readonly shadow = this.attachShadow({mode: 'open'});
19+
// Use Light DOM
20+
// private readonly shadow = this.attachShadow({mode: 'open'});
21+
private readonly shadow = this;
2022

2123
private _session: AgentSession | null = null;
2224
private _variant: 'full'|'nested' = 'full';
@@ -122,7 +124,8 @@ export class LiveAgentSessionComponent extends HTMLElement {
122124
this.shadow.innerHTML = `
123125
<style>
124126
/* Import timeline styles from chatView.css */
125-
:host {
127+
live-agent-session {
128+
display: block;
126129
--sys-color-surface-variant-rgb: 128, 128, 128;
127130
}
128131
.agent-execution-timeline {

front_end/panels/ai_chat/ui/PromptEditDialog.ts

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export class PromptEditDialog {
109109
background-color: var(--color-background);
110110
max-width: 90vw;
111111
max-height: 90vh;
112+
display: flex;
113+
flex-direction: column;
114+
overflow: hidden;
112115
}
113116
114117
.prompt-edit-content {
@@ -118,6 +121,16 @@ export class PromptEditDialog {
118121
height: 100%;
119122
min-width: 600px;
120123
min-height: 500px;
124+
overflow: hidden;
125+
}
126+
127+
.prompt-edit-body {
128+
flex: 1 1 auto;
129+
height: 0;
130+
overflow: hidden;
131+
display: flex;
132+
flex-direction: column;
133+
position: relative;
121134
}
122135
123136
.prompt-edit-header {
@@ -127,6 +140,9 @@ export class PromptEditDialog {
127140
padding: 16px 20px;
128141
border-bottom: 1px solid var(--color-details-hairline);
129142
flex-shrink: 0;
143+
background-color: var(--color-background);
144+
position: relative;
145+
z-index: 10;
130146
}
131147
132148
.prompt-edit-title {
@@ -160,19 +176,30 @@ export class PromptEditDialog {
160176
.prompt-edit-section {
161177
padding: 16px 20px;
162178
border-bottom: 1px solid var(--color-details-hairline);
179+
flex-shrink: 0;
180+
}
181+
182+
.prompt-edit-section-main {
183+
flex: 1;
184+
min-height: 0;
185+
display: flex;
186+
flex-direction: column;
187+
overflow: hidden;
163188
}
164189
165190
.prompt-edit-label {
166191
font-size: 14px;
167192
font-weight: 500;
168193
margin-bottom: 6px;
169194
color: var(--color-text-primary);
195+
flex-shrink: 0;
170196
}
171197
172198
.prompt-edit-hint {
173199
font-size: 12px;
174200
color: var(--color-text-secondary);
175201
margin-bottom: 8px;
202+
flex-shrink: 0;
176203
}
177204
178205
.prompt-edit-agent-value {
@@ -209,9 +236,10 @@ export class PromptEditDialog {
209236
font-family: 'Menlo', 'Monaco', 'Consolas', monospace;
210237
font-size: 13px;
211238
line-height: 1.4;
212-
resize: vertical;
239+
resize: none;
213240
box-sizing: border-box;
214-
min-height: 300px;
241+
flex: 1;
242+
min-height: 0;
215243
}
216244
217245
.prompt-edit-textarea:focus {
@@ -250,6 +278,9 @@ export class PromptEditDialog {
250278
padding: 16px 20px;
251279
border-top: 1px solid var(--color-details-hairline);
252280
flex-shrink: 0;
281+
background-color: var(--color-background);
282+
position: relative;
283+
z-index: 10;
253284
}
254285
255286
.prompt-edit-button {
@@ -333,10 +364,15 @@ export class PromptEditDialog {
333364

334365
headerDiv.appendChild(closeButton);
335366

367+
// Body container
368+
const bodyDiv = document.createElement('div');
369+
bodyDiv.className = 'prompt-edit-body';
370+
contentDiv.appendChild(bodyDiv);
371+
336372
// Agent type display
337373
const agentSection = document.createElement('div');
338374
agentSection.className = 'prompt-edit-section';
339-
contentDiv.appendChild(agentSection);
375+
bodyDiv.appendChild(agentSection);
340376

341377
const agentLabel = document.createElement('div');
342378
agentLabel.className = 'prompt-edit-label';
@@ -358,8 +394,8 @@ export class PromptEditDialog {
358394

359395
// Prompt editing section
360396
const promptSection = document.createElement('div');
361-
promptSection.className = 'prompt-edit-section';
362-
contentDiv.appendChild(promptSection);
397+
promptSection.className = 'prompt-edit-section prompt-edit-section-main';
398+
bodyDiv.appendChild(promptSection);
363399

364400
const promptLabel = document.createElement('div');
365401
promptLabel.className = 'prompt-edit-label';
@@ -374,8 +410,9 @@ export class PromptEditDialog {
374410
const promptTextarea = document.createElement('textarea');
375411
promptTextarea.className = 'prompt-edit-textarea';
376412
promptTextarea.value = options.currentPrompt;
377-
promptTextarea.rows = DIALOG_CONSTANTS.TEXTAREA_ROWS;
378-
promptTextarea.cols = DIALOG_CONSTANTS.TEXTAREA_COLS;
413+
// Let CSS handle sizing
414+
// promptTextarea.rows = DIALOG_CONSTANTS.TEXTAREA_ROWS;
415+
// promptTextarea.cols = DIALOG_CONSTANTS.TEXTAREA_COLS;
379416
promptTextarea.setAttribute('aria-label', i18nString(UIStrings.promptLabel));
380417
promptTextarea.setAttribute('aria-describedby', 'prompt-hint');
381418
promptHint.id = 'prompt-hint';
@@ -386,7 +423,7 @@ export class PromptEditDialog {
386423
statusMessage.className = 'prompt-edit-status-message';
387424
statusMessage.setAttribute('role', 'status');
388425
statusMessage.setAttribute('aria-live', 'polite');
389-
contentDiv.appendChild(statusMessage);
426+
bodyDiv.appendChild(statusMessage);
390427

391428
let statusTimeout: number | null = null;
392429
const showStatus = (message: string, type: 'success' | 'error'): void => {

front_end/panels/ai_chat/ui/TodoListDisplay.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ interface ParsedTodos {
2424
@customElement('ai-todo-list')
2525
export class TodoListDisplay extends HTMLElement {
2626
static readonly litTagName = Lit.StaticHtml.literal`ai-todo-list`;
27-
readonly #shadow = this.attachShadow({mode: 'open'});
27+
// Use Light DOM
28+
// readonly #shadow = this.attachShadow({mode: 'open'});
29+
readonly #shadow = this;
2830

2931
#collapsed = false;
3032
#todos = '';
@@ -100,7 +102,7 @@ export class TodoListDisplay extends HTMLElement {
100102

101103
render(html`
102104
<style>
103-
:host {
105+
ai-todo-list {
104106
display: block;
105107
}
106108

front_end/panels/ai_chat/ui/message/MessageList.ts

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ const {customElement} = Decorators as any;
1111
@customElement('ai-message-list')
1212
export class MessageList extends HTMLElement {
1313
static readonly litTagName = Lit.StaticHtml.literal`ai-message-list`;
14-
readonly #shadow = this.attachShadow({mode: 'open'});
14+
// Use Light DOM
15+
// readonly #shadow = this.attachShadow({mode: 'open'});
16+
readonly #shadow = this;
1517

1618
// Public API properties (no decorators; manual setters trigger render)
1719
#messages: ChatMessage[] = [];
@@ -27,57 +29,60 @@ export class MessageList extends HTMLElement {
2729

2830
// Internal state
2931
#pinToBottom = true;
30-
#container?: HTMLElement;
3132
#resizeObserver = new ResizeObserver(() => { if (this.#pinToBottom) this.#scrollToBottom(); });
3233

33-
connectedCallback(): void { this.#render(); }
34-
disconnectedCallback(): void { this.#resizeObserver.disconnect(); }
34+
connectedCallback(): void {
35+
this.#render();
36+
this.addEventListener('scroll', this.#onScroll);
37+
this.#resizeObserver.observe(this);
38+
}
39+
40+
disconnectedCallback(): void {
41+
this.#resizeObserver.disconnect();
42+
this.removeEventListener('scroll', this.#onScroll);
43+
}
3544

3645
#onScroll = (e: Event) => {
3746
const el = e.target as HTMLElement;
3847
const SCROLL_ROUNDING_OFFSET = 1;
3948
this.#pinToBottom = el.scrollTop + el.clientHeight + SCROLL_ROUNDING_OFFSET >= el.scrollHeight;
4049
};
4150

42-
#scrollToBottom(): void { if (this.#container) this.#container.scrollTop = this.#container.scrollHeight; }
51+
#scrollToBottom(): void { this.scrollTop = this.scrollHeight; }
4352

4453
#render(): void {
45-
const refFn = (el?: Element) => {
46-
if (this.#container) { this.#resizeObserver.unobserve(this.#container); }
47-
this.#container = el as HTMLElement | undefined;
48-
if (this.#container) {
49-
this.#resizeObserver.observe(this.#container);
50-
this.#scrollToBottom();
51-
} else {
52-
this.#pinToBottom = true;
53-
}
54-
};
55-
56-
// Container mode: project messages via slot from parent.
57-
Lit.render(html`
58-
<style>
59-
:host { display: block; height: 100%; flex: 1 1 auto; position: relative; z-index: 0; }
60-
.container {
61-
overflow-y: auto;
54+
// In Light DOM, we don't want to overwrite children projected by the parent (ChatView).
55+
// We only need to ensure styles are applied.
56+
// ChatView renders <ai-message-list> ... children ... </ai-message-list>
57+
// So we don't need to Lit.render() content here, as it would wipe the children.
58+
59+
// We just inject styles once if needed, or rely on global styles.
60+
// But to ensure self-contained behavior, we can inject a style tag if not present.
61+
if (!this.querySelector('style[data-message-list-styles]')) {
62+
const style = document.createElement('style');
63+
style.setAttribute('data-message-list-styles', '');
64+
style.textContent = `
65+
ai-message-list {
66+
display: block;
6267
height: 100%;
68+
flex: 1 1 auto;
69+
position: relative;
70+
z-index: 0;
71+
overflow-y: auto;
6372
display: flex;
6473
flex-direction: column;
6574
scroll-behavior: smooth;
6675
padding: 12px 16px;
6776
background-color: var(--color-background);
6877
padding-bottom: 12px;
6978
min-height: 100px;
70-
position: relative;
71-
z-index: 0;
7279
}
73-
.container::-webkit-scrollbar { width: 4px; }
74-
.container::-webkit-scrollbar-track { background: transparent; }
75-
.container::-webkit-scrollbar-thumb { background-color: var(--color-scrollbar); border-radius: 4px; }
76-
</style>
77-
<div class="container" @scroll=${this.#onScroll} ${Lit.Directives.ref(refFn)}>
78-
<slot></slot>
79-
</div>
80-
`, this.#shadow, {host: this});
80+
ai-message-list::-webkit-scrollbar { width: 4px; }
81+
ai-message-list::-webkit-scrollbar-track { background: transparent; }
82+
ai-message-list::-webkit-scrollbar-thumb { background-color: var(--color-scrollbar); border-radius: 4px; }
83+
`;
84+
this.prepend(style);
85+
}
8186
}
8287
}
8388

0 commit comments

Comments
 (0)