Skip to content

Commit 64366e7

Browse files
Merge pull request #33 from DeDuckProject/feat/config-change-triggers-general-demo
feat: trigger general demo when git-glimpse config/workflow files change
2 parents 35644f2 + 6570f8a commit 64366e7

File tree

9 files changed

+175
-44
lines changed

9 files changed

+175
-44
lines changed

packages/action/dist/check.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53549,6 +53549,10 @@ function parseGlimpseCommand(commentBody, commandPrefix = "/glimpse") {
5354953549
}
5355053550

5355153551
// ../core/dist/trigger/index.js
53552+
var CONFIG_FILE_PATTERNS = [
53553+
/^git-glimpse\.config\.[jt]s$/,
53554+
/^\.github\/workflows\/.*glimpse.*\.ya?ml$/i
53555+
];
5355253556
function evaluateTrigger(opts) {
5355353557
const { files, triggerConfig, eventType, command } = opts;
5355453558
if (command?.force) {
@@ -53568,6 +53572,16 @@ function evaluateTrigger(opts) {
5356853572
triggerSource: "comment"
5356953573
};
5357053574
}
53575+
const configFiles = files.filter((f) => CONFIG_FILE_PATTERNS.some((p) => p.test(f.path)));
53576+
if (configFiles.length > 0) {
53577+
return {
53578+
shouldRun: true,
53579+
reason: `git-glimpse configuration changed (${configFiles.map((f) => f.path).join(", ")}). Running a general app demo to validate the setup.`,
53580+
matchedFiles: configFiles.map((f) => f.path),
53581+
triggerSource: "auto",
53582+
generalDemo: true
53583+
};
53584+
}
5357153585
if (triggerConfig.mode === "on-demand") {
5357253586
return {
5357353587
shouldRun: false,

packages/action/dist/check.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/action/dist/index.js

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65396,6 +65396,41 @@ export async function demo(page: Page): Promise<void> {
6539665396
// your implementation
6539765397
}`;
6539865398
}
65399+
function buildGeneralDemoPrompt(options) {
65400+
return `You are a Playwright script generator. Generate a TypeScript Playwright script that records a general overview demo of a web app \u2014 as if showing it to someone for the first time.
65401+
65402+
## Goal
65403+
Produce a short, visually engaging tour that demonstrates the app is running and shows its main UI. This is a setup-validation demo, not a feature showcase.
65404+
65405+
## Rules
65406+
- Navigate to the home page and 1-2 other meaningful routes if they exist
65407+
- Scroll gently to reveal content, hover over key UI elements
65408+
- Do NOT test anything \u2014 just demonstrate that the app loads and looks reasonable
65409+
- Use resilient selectors: text content > ARIA roles > CSS classes
65410+
- Act as a real user: only interact through the UI. Never re-implement app features in the script.
65411+
- Do NOT inject code via \`page.evaluate\`, \`page.addInitScript\`, or inline scripts/styles
65412+
- Always call \`await page.waitForLoadState('networkidle')\` after navigation
65413+
65414+
## Timing
65415+
- Total demo under ${options.maxDuration} seconds
65416+
- Use \`await page.waitForTimeout(400)\` between actions \u2014 keep it brisk
65417+
65418+
## Mouse movement
65419+
- Move naturally before clicks: one intermediate \`page.mouse.move(x, y)\` waypoint is enough
65420+
- Use plausible coordinates within the ${options.viewport.width}x${options.viewport.height} viewport
65421+
65422+
## Context
65423+
- Base URL: ${options.baseUrl}
65424+
- Viewport: ${options.viewport.width}x${options.viewport.height}
65425+
65426+
## Output format
65427+
Respond with ONLY the TypeScript script, no markdown fences, no explanation:
65428+
65429+
import type { Page } from '@playwright/test';
65430+
65431+
export async function demo(page: Page): Promise<void> {
65432+
}`;
65433+
}
6539965434
function buildRetryPrompt(originalScript, errorMessage, screenshotDescription, options) {
6540065435
return `The following Playwright demo script failed with an error. Please fix it.
6540165436

@@ -65442,7 +65477,7 @@ function stripMarkdownFences(script) {
6544265477

6544365478
// ../core/dist/generator/script-generator.js
6544465479
var MAX_RETRIES = 2;
65445-
async function generateDemoScript(client, analysis, rawDiff, baseUrl, config) {
65480+
async function generateDemoScript(client, analysis, rawDiff, baseUrl, config, generalDemo = false) {
6544665481
const recording = config.recording ?? { viewport: { width: 1280, height: 720 }, maxDuration: 30, format: "gif", deviceScaleFactor: 2 };
6544765482
const llm = config.llm ?? { provider: "anthropic", model: "claude-sonnet-4-6" };
6544865483
const promptOptions = {
@@ -65456,7 +65491,7 @@ async function generateDemoScript(client, analysis, rawDiff, baseUrl, config) {
6545665491
const errors = [];
6545765492
let lastScript = "";
6545865493
for (let attempt = 1; attempt <= MAX_RETRIES + 1; attempt++) {
65459-
const prompt = attempt === 1 ? buildScriptGenerationPrompt(promptOptions) : buildRetryPrompt(lastScript, errors[errors.length - 1] ?? "", "", promptOptions);
65494+
const prompt = attempt === 1 ? generalDemo ? buildGeneralDemoPrompt({ baseUrl, maxDuration: recording.maxDuration, viewport: recording.viewport }) : buildScriptGenerationPrompt(promptOptions) : buildRetryPrompt(lastScript, errors[errors.length - 1] ?? "", "", promptOptions);
6546065495
const response = await client.messages.create({
6546165496
model: llm.model,
6546265497
max_tokens: 4096,
@@ -65767,7 +65802,7 @@ function sanitizeRoute(route) {
6576765802

6576865803
// ../core/dist/pipeline.js
6576965804
async function runPipeline(options) {
65770-
const { diff, baseUrl, config } = options;
65805+
const { diff, baseUrl, config, generalDemo = false } = options;
6577165806
const outputDir = options.outputDir ?? "./recordings";
6577265807
const errors = [];
6577365808
const recording = config.recording ?? {
@@ -65779,20 +65814,22 @@ async function runPipeline(options) {
6577965814
};
6578065815
const llm = config.llm ?? { provider: "anthropic", model: "claude-sonnet-4-6" };
6578165816
const parsedDiff = parseDiff(diff);
65782-
const uiFiles = filterUIFiles(parsedDiff.files, config.trigger);
65783-
if (uiFiles.length === 0) {
65784-
return {
65785-
success: false,
65786-
script: "",
65787-
analysis: {
65788-
changedFiles: parsedDiff.files.map((f2) => f2.path),
65789-
affectedRoutes: [],
65790-
changeDescription: "No UI files changed.",
65791-
suggestedDemoFlow: ""
65792-
},
65793-
attempts: 0,
65794-
errors: ["No UI files detected in diff"]
65795-
};
65817+
if (!generalDemo) {
65818+
const uiFiles = filterUIFiles(parsedDiff.files, config.trigger);
65819+
if (uiFiles.length === 0) {
65820+
return {
65821+
success: false,
65822+
script: "",
65823+
analysis: {
65824+
changedFiles: parsedDiff.files.map((f2) => f2.path),
65825+
affectedRoutes: [],
65826+
changeDescription: "No UI files changed.",
65827+
suggestedDemoFlow: ""
65828+
},
65829+
attempts: 0,
65830+
errors: ["No UI files detected in diff"]
65831+
};
65832+
}
6579665833
}
6579765834
const routes = detectRoutes(parsedDiff, {
6579865835
routeMap: config.routeMap,
@@ -65803,7 +65840,7 @@ async function runPipeline(options) {
6580365840
throw new Error("ANTHROPIC_API_KEY environment variable is required");
6580465841
const client = new sdk_default({ apiKey });
6580565842
const analysis = await summarizeChanges(client, parsedDiff, routes, llm.model);
65806-
const { script, attempts, errors: genErrors } = await generateDemoScript(client, analysis, diff, baseUrl, config);
65843+
const { script, attempts, errors: genErrors } = await generateDemoScript(client, analysis, diff, baseUrl, config, generalDemo);
6580765844
errors.push(...genErrors);
6580865845
try {
6580965846
const recordingResult = await runScriptAndRecord({
@@ -70152,6 +70189,10 @@ function parseGlimpseCommand(commentBody, commandPrefix = "/glimpse") {
7015270189
}
7015370190

7015470191
// ../core/dist/trigger/index.js
70192+
var CONFIG_FILE_PATTERNS = [
70193+
/^git-glimpse\.config\.[jt]s$/,
70194+
/^\.github\/workflows\/.*glimpse.*\.ya?ml$/i
70195+
];
7015570196
function evaluateTrigger(opts) {
7015670197
const { files, triggerConfig, eventType, command } = opts;
7015770198
if (command?.force) {
@@ -70171,6 +70212,16 @@ function evaluateTrigger(opts) {
7017170212
triggerSource: "comment"
7017270213
};
7017370214
}
70215+
const configFiles = files.filter((f2) => CONFIG_FILE_PATTERNS.some((p2) => p2.test(f2.path)));
70216+
if (configFiles.length > 0) {
70217+
return {
70218+
shouldRun: true,
70219+
reason: `git-glimpse configuration changed (${configFiles.map((f2) => f2.path).join(", ")}). Running a general app demo to validate the setup.`,
70220+
matchedFiles: configFiles.map((f2) => f2.path),
70221+
triggerSource: "auto",
70222+
generalDemo: true
70223+
};
70224+
}
7017470225
if (triggerConfig.mode === "on-demand") {
7017570226
return {
7017670227
shouldRun: false,
@@ -70341,7 +70392,7 @@ async function run() {
7034170392
}
7034270393
try {
7034370394
core.info("Running git-glimpse pipeline...");
70344-
const result = await runPipeline({ diff, baseUrl, outputDir: "./recordings", config });
70395+
const result = await runPipeline({ diff, baseUrl, outputDir: "./recordings", config, generalDemo: decision.generalDemo });
7034570396
if (result.errors.length > 0) {
7034670397
core.warning(`Pipeline completed with errors:
7034770398
${result.errors.join("\n")}`);

packages/action/dist/index.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/action/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ async function run(): Promise<void> {
175175

176176
try {
177177
core.info('Running git-glimpse pipeline...');
178-
const result = await runPipeline({ diff, baseUrl, outputDir: './recordings', config });
178+
const result = await runPipeline({ diff, baseUrl, outputDir: './recordings', config, generalDemo: decision.generalDemo });
179179

180180
if (result.errors.length > 0) {
181181
core.warning(`Pipeline completed with errors:\n${result.errors.join('\n')}`);

packages/core/src/generator/prompts.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,42 @@ export async function demo(page: Page): Promise<void> {
6464
}`;
6565
}
6666

67+
export function buildGeneralDemoPrompt(options: Pick<ScriptPromptOptions, 'baseUrl' | 'maxDuration' | 'viewport'>): string {
68+
return `You are a Playwright script generator. Generate a TypeScript Playwright script that records a general overview demo of a web app — as if showing it to someone for the first time.
69+
70+
## Goal
71+
Produce a short, visually engaging tour that demonstrates the app is running and shows its main UI. This is a setup-validation demo, not a feature showcase.
72+
73+
## Rules
74+
- Navigate to the home page and 1-2 other meaningful routes if they exist
75+
- Scroll gently to reveal content, hover over key UI elements
76+
- Do NOT test anything — just demonstrate that the app loads and looks reasonable
77+
- Use resilient selectors: text content > ARIA roles > CSS classes
78+
- Act as a real user: only interact through the UI. Never re-implement app features in the script.
79+
- Do NOT inject code via \`page.evaluate\`, \`page.addInitScript\`, or inline scripts/styles
80+
- Always call \`await page.waitForLoadState('networkidle')\` after navigation
81+
82+
## Timing
83+
- Total demo under ${options.maxDuration} seconds
84+
- Use \`await page.waitForTimeout(400)\` between actions — keep it brisk
85+
86+
## Mouse movement
87+
- Move naturally before clicks: one intermediate \`page.mouse.move(x, y)\` waypoint is enough
88+
- Use plausible coordinates within the ${options.viewport.width}x${options.viewport.height} viewport
89+
90+
## Context
91+
- Base URL: ${options.baseUrl}
92+
- Viewport: ${options.viewport.width}x${options.viewport.height}
93+
94+
## Output format
95+
Respond with ONLY the TypeScript script, no markdown fences, no explanation:
96+
97+
import type { Page } from '@playwright/test';
98+
99+
export async function demo(page: Page): Promise<void> {
100+
}`;
101+
}
102+
67103
export function buildRetryPrompt(
68104
originalScript: string,
69105
errorMessage: string,

packages/core/src/generator/script-generator.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Anthropic from '@anthropic-ai/sdk';
22
import type { ChangeAnalysis } from '../analyzer/change-summarizer.js';
33
import type { GitGlimpseConfig } from '../config/schema.js';
4-
import { buildScriptGenerationPrompt, buildRetryPrompt, type ScriptPromptOptions } from './prompts.js';
4+
import { buildScriptGenerationPrompt, buildGeneralDemoPrompt, buildRetryPrompt, type ScriptPromptOptions } from './prompts.js';
55
import { validateScript } from './validator.js';
66

77
const MAX_RETRIES = 2;
@@ -17,7 +17,8 @@ export async function generateDemoScript(
1717
analysis: ChangeAnalysis,
1818
rawDiff: string,
1919
baseUrl: string,
20-
config: GitGlimpseConfig
20+
config: GitGlimpseConfig,
21+
generalDemo = false
2122
): Promise<ScriptGenerationResult> {
2223
const recording = config.recording ?? { viewport: { width: 1280, height: 720 }, maxDuration: 30, format: 'gif' as const, deviceScaleFactor: 2 };
2324
const llm = config.llm ?? { provider: 'anthropic' as const, model: 'claude-sonnet-4-6' };
@@ -37,7 +38,9 @@ export async function generateDemoScript(
3738
for (let attempt = 1; attempt <= MAX_RETRIES + 1; attempt++) {
3839
const prompt =
3940
attempt === 1
40-
? buildScriptGenerationPrompt(promptOptions)
41+
? (generalDemo
42+
? buildGeneralDemoPrompt({ baseUrl, maxDuration: recording.maxDuration, viewport: recording.viewport })
43+
: buildScriptGenerationPrompt(promptOptions))
4144
: buildRetryPrompt(lastScript, errors[errors.length - 1] ?? '', '', promptOptions);
4245

4346
const response = await client.messages.create({

packages/core/src/pipeline.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export interface PipelineOptions {
1515
baseUrl: string;
1616
outputDir?: string;
1717
config: GitGlimpseConfig;
18+
/** When true, skip UI-file filtering and run a general app overview demo. */
19+
generalDemo?: boolean;
1820
}
1921

2022
export interface DemoResult {
@@ -33,7 +35,7 @@ export interface DemoResult {
3335
}
3436

3537
export async function runPipeline(options: PipelineOptions): Promise<DemoResult> {
36-
const { diff, baseUrl, config } = options;
38+
const { diff, baseUrl, config, generalDemo = false } = options;
3739
const outputDir = options.outputDir ?? './recordings';
3840
const errors: string[] = [];
3941

@@ -48,20 +50,23 @@ export async function runPipeline(options: PipelineOptions): Promise<DemoResult>
4850

4951
// 1. Parse diff
5052
const parsedDiff = parseDiff(diff);
51-
const uiFiles = filterUIFiles(parsedDiff.files, config.trigger);
52-
if (uiFiles.length === 0) {
53-
return {
54-
success: false,
55-
script: '',
56-
analysis: {
57-
changedFiles: parsedDiff.files.map((f) => f.path),
58-
affectedRoutes: [],
59-
changeDescription: 'No UI files changed.',
60-
suggestedDemoFlow: '',
61-
},
62-
attempts: 0,
63-
errors: ['No UI files detected in diff'],
64-
};
53+
54+
if (!generalDemo) {
55+
const uiFiles = filterUIFiles(parsedDiff.files, config.trigger);
56+
if (uiFiles.length === 0) {
57+
return {
58+
success: false,
59+
script: '',
60+
analysis: {
61+
changedFiles: parsedDiff.files.map((f) => f.path),
62+
affectedRoutes: [],
63+
changeDescription: 'No UI files changed.',
64+
suggestedDemoFlow: '',
65+
},
66+
attempts: 0,
67+
errors: ['No UI files detected in diff'],
68+
};
69+
}
6570
}
6671

6772
// 2. Detect routes
@@ -84,7 +89,8 @@ export async function runPipeline(options: PipelineOptions): Promise<DemoResult>
8489
analysis,
8590
diff,
8691
baseUrl,
87-
config
92+
config,
93+
generalDemo
8894
);
8995
errors.push(...genErrors);
9096

packages/core/src/trigger/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ export interface TriggerDecision {
1212
reason: string;
1313
matchedFiles: string[];
1414
triggerSource: 'auto' | 'comment' | 'force';
15+
/** True when triggered by a config/workflow file change — pipeline runs a general app demo. */
16+
generalDemo?: boolean;
1517
}
1618

19+
/** Files whose changes should always trigger a general demo run. */
20+
const CONFIG_FILE_PATTERNS = [
21+
/^git-glimpse\.config\.[jt]s$/,
22+
/^\.github\/workflows\/.*glimpse.*\.ya?ml$/i,
23+
];
24+
1725
export interface EvaluateTriggerOptions {
1826
files: DiffFile[];
1927
triggerConfig: TriggerConfig;
@@ -50,6 +58,19 @@ export function evaluateTrigger(opts: EvaluateTriggerOptions): TriggerDecision {
5058
};
5159
}
5260

61+
// Config/workflow file changes always trigger a general demo, regardless of mode.
62+
// This lets users validate their git-glimpse setup on a branch before merging.
63+
const configFiles = files.filter((f) => CONFIG_FILE_PATTERNS.some((p) => p.test(f.path)));
64+
if (configFiles.length > 0) {
65+
return {
66+
shouldRun: true,
67+
reason: `git-glimpse configuration changed (${configFiles.map((f) => f.path).join(', ')}). Running a general app demo to validate the setup.`,
68+
matchedFiles: configFiles.map((f) => f.path),
69+
triggerSource: 'auto',
70+
generalDemo: true,
71+
};
72+
}
73+
5374
// Push event + on-demand mode: skip and wait for explicit trigger
5475
if (triggerConfig.mode === 'on-demand') {
5576
return {

0 commit comments

Comments
 (0)