Skip to content

Commit e4f5d46

Browse files
committed
feat(server): adapt dynamic context control to ProcessedPointedDOMElement
- Add cssComputed and textContent fields to ProcessedPointedDOMElement type - Update ElementProcessor to capture full computed styles from raw data - Adapt element-detail.ts to shape ProcessedPointedDOMElement instead of TargetedElement - Update tests to use ProcessedPointedDOMElement - All shaping logic now works server-side on processed elements
1 parent c3628e3 commit e4f5d46

File tree

4 files changed

+63
-47
lines changed

4 files changed

+63
-47
lines changed

packages/server/src/__tests__/utils/element-detail.test.ts

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,40 @@ import {
44
normalizeTextDetail,
55
shapeElementForDetail,
66
} from '../../utils/element-detail';
7-
import { createMockElement } from '../test-helpers';
7+
import { ProcessedPointedDOMElement } from '../../types';
8+
9+
function createMockProcessedElement(): ProcessedPointedDOMElement {
10+
return {
11+
selector: 'div.test-element',
12+
tagName: 'DIV',
13+
id: 'test-id',
14+
classes: ['test-class'],
15+
innerText: 'Visible text',
16+
textContent: 'Visible text with hidden content',
17+
attributes: { 'data-test': 'true' },
18+
position: {
19+
x: 100, y: 200, width: 300, height: 50,
20+
},
21+
cssProperties: {
22+
display: 'block',
23+
position: 'relative',
24+
fontSize: '16px',
25+
color: 'rgb(0, 0, 0)',
26+
backgroundColor: 'rgb(255, 255, 255)',
27+
},
28+
cssComputed: {
29+
display: 'block',
30+
position: 'relative',
31+
fontSize: '16px',
32+
color: 'rgb(0, 0, 0)',
33+
backgroundColor: 'rgb(255, 255, 255)',
34+
marginTop: '10px',
35+
paddingLeft: '5px',
36+
},
37+
timestamp: new Date().toISOString(),
38+
url: 'https://example.com',
39+
};
40+
}
841

942
describe('element-detail utilities', () => {
1043
describe('normalizeTextDetail', () => {
@@ -41,19 +74,18 @@ describe('element-detail utilities', () => {
4174

4275
describe('shapeElementForDetail', () => {
4376
it('omits text and css when levels request none', () => {
44-
const element = createMockElement();
77+
const element = createMockProcessedElement();
4578
const shaped = shapeElementForDetail(element, 'none', 0);
4679

47-
expect(shaped.innerText).toBeUndefined();
80+
expect(shaped.innerText).toBe('');
4881
expect(shaped.textContent).toBeUndefined();
4982
expect(shaped.cssProperties).toBeUndefined();
50-
expect(shaped.cssLevel).toBe(0);
5183
});
5284

5385
it('returns visible text and level 1 css subset', () => {
54-
const element = createMockElement();
55-
element.textVariants!.visible = 'Visible text only';
56-
element.textVariants!.full = 'Visible text only with hidden';
86+
const element = createMockProcessedElement();
87+
element.innerText = 'Visible text only';
88+
element.textContent = 'Visible text only with hidden';
5789
const shaped = shapeElementForDetail(element, 'visible', 1);
5890

5991
expect(shaped.innerText).toBe('Visible text only');
@@ -64,17 +96,11 @@ describe('element-detail utilities', () => {
6496
});
6597

6698
it('returns full css when level 3 requested', () => {
67-
const element = createMockElement();
68-
element.cssComputed = {
69-
...element.cssProperties!,
70-
marginTop: '5px',
71-
};
72-
99+
const element = createMockProcessedElement();
73100
const shaped = shapeElementForDetail(element, 'full', 3);
74-
expect(shaped.cssProperties).toEqual({
75-
...element.cssComputed,
76-
});
77-
expect(shaped.textContent).toBe(element.textVariants!.full);
101+
102+
expect(shaped.cssProperties).toEqual(element.cssComputed);
103+
expect(shaped.textContent).toBe(element.textContent);
78104
});
79105
});
80106
});

packages/server/src/services/element-processor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ export default class ElementProcessor {
2626
classes: element ? Array.from(element.classList) : [],
2727
attributes: element ? this.getAttributes(element) : {},
2828
innerText: element?.textContent || '',
29+
textContent: element?.textContent || undefined,
2930
selector: element ? generateSelector(element) : 'unknown',
3031

3132
position: this.getPosition(raw.boundingClientRect),
3233
url: raw.url,
3334
timestamp: new Date(raw.timestamp).toISOString(),
3435

3536
cssProperties: this.getRelevantStyles(raw.computedStyles),
37+
cssComputed: raw.computedStyles ? { ...raw.computedStyles } : undefined,
3638
componentInfo: this.getComponentInfo(raw.reactFiber),
3739

3840
warnings: allWarnings.length > 0 ? allWarnings : undefined,

packages/server/src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ export interface ProcessedPointedDOMElement {
2121
url: string;
2222
timestamp: string; // ISO format
2323

24-
// Optional processing
24+
// Full CSS data for shaping
2525
cssProperties?: CSSProperties;
26+
cssComputed?: Record<string, string>; // Full computed styles
2627
componentInfo?: ComponentInfo;
2728

29+
// Text content (full, including hidden nodes)
30+
textContent?: string;
31+
2832
// Processing metadata
2933
warnings?: string[];
3034
}

packages/server/src/utils/element-detail.ts

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import {
33
CSSProperties,
44
DEFAULT_CSS_LEVEL,
55
DEFAULT_TEXT_DETAIL,
6-
TargetedElement,
76
TextDetailLevel,
8-
TextSnapshots,
97
} from '@mcp-pointer/shared/types';
108
import {
119
CSS_LEVEL_FIELD_MAP,
1210
isValidCSSLevel,
1311
isValidTextDetail,
1412
} from '@mcp-pointer/shared/detail';
13+
import { ProcessedPointedDOMElement } from '../types';
1514

1615
export interface DetailParameters {
1716
textDetail?: unknown;
@@ -88,33 +87,24 @@ export function normalizeDetailParameters(
8887
};
8988
}
9089

91-
function resolveTextVariants(element: TargetedElement): TextSnapshots {
92-
const visible = element.textVariants?.visible ?? element.innerText ?? '';
93-
const full = element.textVariants?.full ?? element.textContent ?? visible;
94-
95-
return {
96-
visible,
97-
full,
98-
};
99-
}
100-
10190
function resolveTextContent(
102-
variants: TextSnapshots,
91+
element: ProcessedPointedDOMElement,
10392
detail: TextDetailLevel,
10493
): string | undefined {
10594
if (detail === 'none') {
10695
return undefined;
10796
}
10897

10998
if (detail === 'visible') {
110-
return variants.visible;
99+
return element.innerText;
111100
}
112101

113-
return variants.full || variants.visible;
102+
// 'full' - return textContent if available, otherwise innerText
103+
return element.textContent ?? element.innerText;
114104
}
115105

116106
function buildCssProperties(
117-
element: TargetedElement,
107+
element: ProcessedPointedDOMElement,
118108
cssLevel: CSSDetailLevel,
119109
): CSSProperties | undefined {
120110
if (cssLevel === 0) {
@@ -156,34 +146,28 @@ function buildCssProperties(
156146
}
157147

158148
export function shapeElementForDetail(
159-
element: TargetedElement,
149+
element: ProcessedPointedDOMElement,
160150
detail: TextDetailLevel,
161151
cssLevel: CSSDetailLevel,
162-
): TargetedElement {
163-
const variants = resolveTextVariants(element);
164-
const resolvedText = resolveTextContent(variants, detail);
165-
const textContent = detail === 'full' ? variants.full : undefined;
152+
): ProcessedPointedDOMElement {
153+
const resolvedText = resolveTextContent(element, detail);
154+
const textContent = detail === 'full' ? element.textContent : undefined;
166155
const cssProperties = buildCssProperties(element, cssLevel);
167156

168-
const shaped: TargetedElement = {
157+
const shaped: ProcessedPointedDOMElement = {
169158
selector: element.selector,
170159
tagName: element.tagName,
171160
id: element.id,
172161
classes: [...element.classes],
173162
attributes: { ...element.attributes },
174163
position: { ...element.position },
175-
cssLevel,
176164
componentInfo: element.componentInfo ? { ...element.componentInfo } : undefined,
177165
timestamp: element.timestamp,
178166
url: element.url,
179-
tabId: element.tabId,
180-
textDetail: detail,
167+
innerText: resolvedText ?? '',
168+
warnings: element.warnings,
181169
};
182170

183-
if (resolvedText !== undefined) {
184-
shaped.innerText = resolvedText;
185-
}
186-
187171
if (textContent !== undefined) {
188172
shaped.textContent = textContent;
189173
}

0 commit comments

Comments
 (0)