diff --git a/packages/cli/src/commands/coreSkillContent.test.ts b/packages/cli/src/commands/coreSkillContent.test.ts index c537dc9d1..e7578ed58 100644 --- a/packages/cli/src/commands/coreSkillContent.test.ts +++ b/packages/cli/src/commands/coreSkillContent.test.ts @@ -35,6 +35,20 @@ describe("hyperframes-core contract docs", () => { expect(renderReference).toContain("OUTCOME:"); expect(renderReference).toContain("WORKAROUND:"); }); + + it("mandates a composition-structure block for visual-defect feedback", () => { + const skill = read("skills", "hyperframes-cli", "SKILL.md"); + const renderReference = read("skills", "hyperframes-cli", "references", "preview-render.md"); + + // Skill teaches the mandate at a high level. + expect(skill).toContain("COMPOSITION_STRUCTURE:"); + // Reference carries the fillable block + agent-helper pointer. + expect(renderReference).toContain("COMPOSITION_STRUCTURE:"); + expect(renderReference).toContain("elements: video="); + expect(renderReference).toContain("attributes:"); + expect(renderReference).toContain("timeline:"); + expect(renderReference).toContain("buildCompositionCensus"); + }); }); describe("media-use TTS documentation", () => { diff --git a/packages/cli/src/commands/feedback.ts b/packages/cli/src/commands/feedback.ts index d4eb23fba..fc06b0cd8 100644 --- a/packages/cli/src/commands/feedback.ts +++ b/packages/cli/src/commands/feedback.ts @@ -12,6 +12,7 @@ import { buildIssueUrl, HYPERFRAMES_REPO_URL } from "../utils/feedbackIssue.js"; import { VERSION } from "../version.js"; import { c } from "../ui/colors.js"; import { parseFeedbackRating } from "../utils/feedbackRating.js"; +import { lintFeedbackComment } from "../utils/feedbackLint.js"; export const examples: Example[] = [ ["Submit render feedback", 'hyperframes feedback --rating 8 --comment "fast but font missing"'], @@ -156,6 +157,13 @@ export default defineCommand({ const comment = normalizeComment(args.comment); const doctorSummary = await getDoctorSummary(); + // Soft-warn (never blocks) when the comment for a non-clean report is + // missing the mandated reproduction-packet markers. Prints before the + // submission ack so the reporter sees the nudge while their run is fresh. + for (const warning of lintFeedbackComment({ rating, comment })) { + console.log(c.warn(`⚠ ${warning.message}`)); + } + // The standalone command runs separately from `render`, so it has no real // elapsed time to report. Omit it rather than recording a fake duration. trackRenderFeedback({ rating, comment, doctorSummary }); diff --git a/packages/cli/src/utils/compositionCensus.test.ts b/packages/cli/src/utils/compositionCensus.test.ts new file mode 100644 index 000000000..ec9ee5224 --- /dev/null +++ b/packages/cli/src/utils/compositionCensus.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from "vitest"; + +import { buildCompositionCensus, renderCompositionCensusBlock } from "./compositionCensus.js"; + +const MINIMAL_HTML = ` + + +
+ +`; + +const RICH_HTML = ` + + + + + + +
+ + + + + + + + +
+
+
+
+
+
+ + +`; + +describe("buildCompositionCensus", () => { + it("counts zero media on a minimal composition", () => { + const c = buildCompositionCensus(MINIMAL_HTML); + expect(c.elementCensus).toEqual({ + video: 0, + audio: 0, + img: 0, + svg: 0, + canvas: 0, + subCompositionMounts: 0, + }); + expect(c.timelineShape.nested).toBe(false); + expect(c.timelineShape.subCompositionCount).toBe(0); + expect(c.timelineShape.usesGsap).toBe(false); + expect(c.timelineShape.usesDataTimeline).toBe(true); + }); + + it("counts each element category on a rich composition", () => { + const c = buildCompositionCensus(RICH_HTML); + expect(c.elementCensus).toEqual({ + video: 2, + audio: 1, + img: 3, + svg: 1, + canvas: 1, + subCompositionMounts: 2, + }); + }); + + it("detects structural attributes from both inline style and