From 19116cb4d94388d1f620c1ad00f543fc2b9e7614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 29 May 2026 13:23:33 +0800 Subject: [PATCH] feat(desktop): add element metadata builder Co-Authored-By: Claude Sonnet 4.6 --- desktop/src/preview-agent/metadata.test.ts | 20 +++++++++++++++++ desktop/src/preview-agent/metadata.ts | 25 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 desktop/src/preview-agent/metadata.test.ts create mode 100644 desktop/src/preview-agent/metadata.ts diff --git a/desktop/src/preview-agent/metadata.test.ts b/desktop/src/preview-agent/metadata.test.ts new file mode 100644 index 00000000..fca41d8d --- /dev/null +++ b/desktop/src/preview-agent/metadata.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it, beforeEach, vi } from 'vitest' +import { buildElementMetadata } from './metadata' + +beforeEach(() => { document.body.innerHTML = `

Hello World

` }) + +describe('buildElementMetadata', () => { + it('captures tag/id/classes/text/selector and styles', () => { + const el = document.getElementById('t')! + el.getBoundingClientRect = () => ({ x: 1, y: 2, width: 3, height: 4, top: 2, left: 1, right: 4, bottom: 6, toJSON: () => ({}) }) as DOMRect + vi.spyOn(window, 'getComputedStyle').mockReturnValue({ color: 'rgb(0,0,0)', backgroundColor: 'rgba(0,0,0,0)', opacity: '1', fontFamily: 'serif', fontSize: '40px' } as unknown as CSSStyleDeclaration) + const m = buildElementMetadata(el) + expect(m.tag).toBe('h1') + expect(m.id).toBe('t') + expect(m.classes).toEqual(['a', 'b']) + expect(m.text).toBe('Hello World') + expect(m.boundingBox).toEqual({ x: 1, y: 2, w: 3, h: 4 }) + expect(m.computedStyles.color).toBe('rgb(0,0,0)') + expect(m.selector).toBe('#t') + }) +}) diff --git a/desktop/src/preview-agent/metadata.ts b/desktop/src/preview-agent/metadata.ts new file mode 100644 index 00000000..fe98a244 --- /dev/null +++ b/desktop/src/preview-agent/metadata.ts @@ -0,0 +1,25 @@ +import { buildSelector, buildNthPath } from './selector' + +export type ElementMetadata = { + selector: string; nthPath: string; tag: string; id?: string; classes: string[] + text?: string; boundingBox: { x: number; y: number; w: number; h: number } + computedStyles: Record; outerHtmlSnippet?: string +} + +const STYLE_KEYS = ['color', 'backgroundColor', 'opacity', 'fontFamily', 'fontSize', 'fontWeight', 'textAlign', 'padding', 'margin'] as const + +export function buildElementMetadata(el: Element): ElementMetadata { + const cs = window.getComputedStyle(el) + const styles: Record = {} + for (const k of STYLE_KEYS) styles[k] = (cs as unknown as Record)[k] ?? '' + const r = el.getBoundingClientRect() + const sel = buildSelector(el) + return { + selector: sel, nthPath: buildNthPath(el), tag: el.tagName.toLowerCase(), + id: el.id || undefined, classes: Array.from(el.classList), + text: (el.textContent ?? '').trim().slice(0, 200) || undefined, + boundingBox: { x: r.x, y: r.y, w: r.width, h: r.height }, + computedStyles: styles, + outerHtmlSnippet: sel.includes(':nth') ? el.outerHTML.slice(0, 300) : undefined, + } +}