Files
hyperframes/packages/studio/src/components/editor/domEditingAgentPrompt.ts
T
Miguel Ángel 91bdffffe6 fix(ci): scope LOC check to studio, split useTimelinePlayer + hyperframes-player under 500 LOC (#750)
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each)

* fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files

* feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson

Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux:
- Detects the platform automatically
- Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM)
- Falls back to clear manual instructions with exact commands
- 'hyperframes browser ensure' guides through the setup interactively
- After setup, all render commands work without any flags

* fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds

Path exclusions are insufficient — Defender re-scans new files created
during bun install before the exclusion takes effect. Disable real-time
monitoring for the entire job duration instead (standard CI practice).

* refactor(studio): split all files >500 LOC + extract useToast, delete allowlist

All 11 large files split into focused modules under 500 LOC.
App.tsx extracted toast logic into useToast hook (493 LOC now).
.filesize-allowlist deleted — no longer needed.

* fix: remove unused imports from split files, extract useToast from App.tsx

App.tsx: 504 → 493 lines (toast logic extracted to useToast hook)
timelineDOM.ts: remove unused imports from re-export pattern
MotionPanel.tsx: remove unused clampStudioCustomEasePoints import
studioMotionOps.ts: remove unused StudioGsapMotionDirection import

* fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs)

* fix(producer): use node --experimental-strip-types instead of tsx for build:fonts

Eliminates the tsx binary dependency that Windows Defender locks during
bun install, causing EPERM errors. Node 22.6+ strips TypeScript types
natively with no external binary.

* chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500)

* fix(ci): disable Windows Defender before checkout to prevent all EPERM races

* fix(producer): skip build:fonts if fontData.generated.ts already exists

The generated file is tracked in git, so CI doesn't need to regenerate
it. This avoids @fontsource/inter node_modules access on Windows which
triggers EPERM from Defender scanning during bun install.
2026-05-13 01:48:12 +02:00

98 lines
3.0 KiB
TypeScript

/**
* Agent prompt builder for HyperFrames element edit requests.
*/
import { formatTime } from "../../player/lib/time";
import type { DomEditSelection, DomEditTextField } from "./domEditingTypes";
function formatBoundingBox(bounds: DomEditSelection["boundingBox"]): string {
return `x=${Math.round(bounds.x)}, y=${Math.round(bounds.y)}, width=${Math.round(bounds.width)}, height=${Math.round(bounds.height)}`;
}
function formatStyleBlock(styles: Record<string, string>): string {
return Object.entries(styles)
.filter(([, value]) => value && value !== "initial")
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
}
function formatTextFields(fields: DomEditTextField[]): string {
return fields
.map(
(field) =>
`- key=${field.key}; tag=<${field.tagName}>; source=${field.source}; text=${JSON.stringify(field.value)}`,
)
.join("\n");
}
export function buildElementAgentPrompt({
selection,
currentTime,
tagSnippet,
selectionContext,
userInstruction,
sourceFilePath,
}: {
selection: DomEditSelection;
currentTime: number;
tagSnippet?: string;
selectionContext?: string;
userInstruction?: string;
sourceFilePath?: string;
}): string {
const displayedSourceFile = sourceFilePath?.trim() || selection.sourceFile;
const lines = [
"## HyperFrames element edit request v1",
"Schema version: 1",
"",
userInstruction?.trim() || "Edit this selected HyperFrames element.",
"",
`Composition: ${selection.compositionPath}`,
`Playback time: ${formatTime(currentTime)}`,
`Source file: ${displayedSourceFile}`,
`DOM id: ${selection.id ?? "(none)"}`,
`Selector: ${selection.selector ?? "(none)"}`,
`Selector index: ${selection.selectorIndex ?? 0}`,
`Tag: <${selection.tagName}>`,
`Bounds: ${formatBoundingBox(selection.boundingBox)}`,
];
if (selection.textContent) {
lines.push(`Text: ${selection.textContent}`);
}
const trimmedSelectionContext = selectionContext?.trim();
if (trimmedSelectionContext) {
lines.push("", "Selection context:", trimmedSelectionContext);
}
const textFieldsBlock = formatTextFields(selection.textFields);
if (textFieldsBlock) {
lines.push("", "Text fields:", textFieldsBlock);
}
const inlineStyleBlock = formatStyleBlock(selection.inlineStyles);
if (inlineStyleBlock) {
lines.push("", "Inline styles:", inlineStyleBlock);
}
const computedStyleBlock = formatStyleBlock(selection.computedStyles);
if (computedStyleBlock) {
lines.push("", "Computed styles (browser-resolved):", computedStyleBlock);
}
if (tagSnippet) {
lines.push("", "Target HTML:", tagSnippet);
}
lines.push(
"",
"Guardrails:",
"- Make a targeted change to this element only.",
"- Preserve the rest of the composition and its timing.",
"- Do not modify other elements' data-* attributes or positioning.",
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
);
return lines.join("\n");
}