From c88dc3b9a6f7e752f631f76c803581a8368a44c1 Mon Sep 17 00:00:00 2001 From: Edison Augusthy Date: Thu, 4 Jun 2026 12:16:15 +0200 Subject: [PATCH] feat: demo updates --- .github/workflows/deploy-demo.yml | 58 ++++ .github/workflows/pr-validation.yml | 2 +- README.md | 8 + packages/angular-render-scan/README.md | 357 ++++++++++++++++++++++--- 4 files changed, 394 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/deploy-demo.yml diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml new file mode 100644 index 0000000..29a27e7 --- /dev/null +++ b/.github/workflows/deploy-demo.yml @@ -0,0 +1,58 @@ +name: Deploy Demo + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: pages + cancel-in-progress: true + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + name: Build Demo + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build:scan + + - name: Build demo + run: npm run build:demo -- --configuration production --base-href /angular-render-scan/ + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: dist/demo/browser + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index f9ffe7e..310b832 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: "npm" - name: Install dependencies diff --git a/README.md b/README.md index 5eb39c5..6e7e4fb 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Angular Render Scan is a visual debugging overlay for Angular change detection. ![Angular Render Scan in Action](docs/assets/angular-render-scan-demo.gif) +[Live Demo](https://edisonaugusthy.github.io/angular-render-scan/) + ## Features - **Automatic Angular Telemetry:** Out-of-the-box zero-setup component auto-instrumentation using Angular dev-mode profiler hooks. @@ -377,6 +379,12 @@ Use that option carefully. The scanner adds runtime instrumentation, DOM reads, ## Demo +Open the hosted demo: + +```txt +https://edisonaugusthy.github.io/angular-render-scan/ +``` + Run the local demo: ```sh diff --git a/packages/angular-render-scan/README.md b/packages/angular-render-scan/README.md index 4f8a879..6c42ed6 100644 --- a/packages/angular-render-scan/README.md +++ b/packages/angular-render-scan/README.md @@ -1,8 +1,24 @@ # Angular Render Scan -Angular Render Scan is a visual debugging overlay for Angular change detection. It helps you see which components update, how often they update, and how long those updates take. +Angular Render Scan is a visual debugging overlay for Angular change detection. It is inspired by the React Scan experience: install it, run your app, interact with the UI, and see which Angular components are updating, how often they update, and how long they take. -![Angular Render Scan demo](https://raw.githubusercontent.com/edisonaugusthy/angular-render-scan/main/docs/assets/angular-render-scan-demo.gif) +![Angular Render Scan in Action](https://raw.githubusercontent.com/edisonaugusthy/angular-render-scan/main/docs/assets/angular-render-scan-demo.gif) + +[Live Demo](https://edisonaugusthy.github.io/angular-render-scan/) + +## Features + +- **Automatic Angular Telemetry:** Out-of-the-box zero-setup component auto-instrumentation using Angular dev-mode profiler hooks. +- **Heatmap & Outlines:** Highlights are colored dynamically based on DOM mutations: **green** for no-op wasted renders, **blue** for text/attribute mutations, and **prominent red borders** for expensive renders exceeding thresholds, making bottlenecks instantly recognizable. +- **CD Waterfall View:** Click the SVG sparkline in the toolbar to expand a nested horizontal bar breakdown of component check execution stack offsets and children offsets. +- **Non-Intrusive Budget Alerts Feed:** Standardized budget violations (warning/error millisecond limits and rate alerts) are elegantly grouped and logged in a collapsible alerts feed panel, handling concurrent violations cleanly. +- **Live CPU & Main-Thread Telemetry:** Dotted CPU metric toggles a live popup showing detailed frame-lag latency and total main-thread blocking times. +- **Memory Leak Detector Badge:** Automatically tracks zombie components whose DOM elements were disconnected but not properly destroyed. +- **Click-to-Source IDE Integration:** Inspected details panel provides an "Open in Editor" link that deep links directly to Cursor, VS Code or WebStorm, and automatically copies the class query to your clipboard for instant search. +- **Session Export JSON:** Download a full profiling JSON bundle including CPU, cycle timelines, wasted statistics, and active budget violation logs. +- **Dark Mode & Theme Presets:** Sleek dark mode styles that match `prefers-color-scheme`, customizable dynamically via options. +- **Keyboard Shortcuts:** Keyboard hotkeys mapped to toggle scan, details panel, copy prompts, and clear stats instantly. +- **Production Guard:** Automatic safety guard shutting down package overhead entirely outside developer mode. ## Install @@ -17,31 +33,31 @@ Angular Render Scan expects Angular 9+ as a peer dependency. Add `provideAngularRenderScan()` to your Angular bootstrap providers. ```ts -import { bootstrapApplication } from "@angular/platform-browser"; -import { provideAngularRenderScan } from "angular-render-scan"; -import { AppComponent } from "./app/app.component"; +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideAngularRenderScan } from 'angular-render-scan'; +import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent, { providers: [ provideAngularRenderScan({ enabled: true, - animationSpeed: "fast", - }), - ], + animationSpeed: 'fast' + }) + ] }); ``` -Open your app in development mode and interact with the UI. Updated components flash on screen, and the floating toolbar shows FPS, cycle time, changed component count, the slowest component, and a copyable AI performance prompt focused on slow/error components. +Open your app in development mode and interact with the UI. Updated components will flash on screen and the toolbar will update live. ## Script Usage -The package also exposes a browser global build for script-tag usage. +The package also exposes a browser global build for script-tag style usage. ```html ``` -Provider mode is recommended for Angular component-level instrumentation because it has access to Angular app references and dev-mode hooks. +The global build starts the overlay with default options. For Angular component-level instrumentation, provider mode is still recommended because it has access to Angular app references and dev-mode hooks. ## API @@ -52,8 +68,8 @@ import { getOptions, scan, setOptions, - stop, -} from "angular-render-scan"; + stop +} from 'angular-render-scan'; scan(); setOptions({ enabled: false }); @@ -64,18 +80,85 @@ console.log(getOptions()); stop(); ``` +### `scan(options?)` + +Starts Angular Render Scan and creates the overlay if it is not already mounted. + +```ts +scan({ + enabled: true, + showToolbar: true, + animationSpeed: 'fast' +}); +``` + +### `setOptions(options)` + +Updates scanner options at runtime. + +```ts +setOptions({ + log: true, + animationSpeed: 'slow' +}); +``` + +### `getOptions()` + +Returns the current resolved options. + +```ts +const options = getOptions(); +``` + +### `stop()` + +Destroys the overlay and clears scanner state. + +```ts +stop(); +``` + ## Options +```ts +interface AngularRenderScanOptions { + enabled?: boolean; + showToolbar?: boolean; + animationSpeed?: 'slow' | 'fast' | 'off'; + showFPS?: boolean; + log?: boolean; + dangerouslyForceRunInProduction?: boolean; + minDurationMs?: number; + minRenderCount?: number; + include?: Array; + exclude?: Array; + maxLabelCount?: number; + maxRecordedCycles?: number; + showCopyPrompt?: boolean; + promptContext?: string; + theme?: Partial; + editorProtocol?: 'vscode' | 'webstorm' | 'cursor' | string; + darkMode?: 'auto' | 'dark' | 'light'; + onCycleStart?: () => void; + onRender?: (entry: AngularRenderEntry) => void; + onCycleFinish?: (cycle: AngularRenderCycle) => void; + onBudgetViolation?: (violation: BudgetViolation) => void; +} +``` + +### Common Options + ```ts provideAngularRenderScan({ enabled: true, showToolbar: true, showFPS: true, - animationSpeed: "fast", - log: false, + animationSpeed: 'fast', maxLabelCount: 20, maxRecordedCycles: 30, showCopyPrompt: true, + log: false }); ``` @@ -84,38 +167,203 @@ provideAngularRenderScan({ - `animationSpeed`: controls highlight readability. `'fast'` keeps borders visible for about 1.2s, `'slow'` keeps them visible for about 2.4s, and `'off'` disables visual flashes. - `showFPS`: shows FPS in the toolbar. - `log`: prints cycle summaries to the console. -- `minDurationMs`, `minRenderCount`, `include`, `exclude`: hide low-signal entries. -- `maxLabelCount`: caps visible component labels. +- `dangerouslyForceRunInProduction`: allows the scanner to run outside Angular dev mode. +- `minDurationMs`, `minRenderCount`, `include`, `exclude`: filter low-signal render entries. +- `maxLabelCount`: limits how many highlighted components receive labels. - `maxRecordedCycles`: controls how many recent cycles are included in the copied AI prompt. - `showCopyPrompt`, `promptContext`: control the copyable AI performance prompt. -- `dangerouslyForceRunInProduction`: allows the scanner to run outside Angular dev mode. -## Basic Debug Config +### Basic Debug Config ```ts provideAngularRenderScan({ enabled: true, showToolbar: true, - animationSpeed: "slow", + animationSpeed: 'slow', maxLabelCount: 12, maxRecordedCycles: 20, - promptContext: "Angular app using signals and OnPush components", + promptContext: 'Angular app using signals and OnPush components' }); ``` Use `animationSpeed: 'slow'` when you want more time to read the borders and labels while interacting with the page. +## Callbacks + +```ts +provideAngularRenderScan({ + onCycleStart() { + console.log('cycle started'); + }, + onRender(entry) { + console.log(entry.name, entry.latestDuration); + }, + onCycleFinish(cycle) { + console.log(cycle.renderedCount, cycle.slowest?.name); + } +}); +``` + +```ts +interface AngularRenderEntry { + id: string; + name: string; + element: Element; + rect: DOMRect; + count: number; + latestDuration: number; + averageDuration: number; + latestCycleId: number; + reason?: 'input' | 'event' | 'tick' | 'dom' | 'unknown'; + changedInputs?: Array<{ name: string; previous: string; current: string }>; + selector?: string; +} + +interface AngularRenderCycle { + id: number; + startedAt: number; + finishedAt: number; + duration: number; + renderedCount: number; + slowest?: AngularRenderEntry; + entries: AngularRenderEntry[]; +} +``` + +## Theme + +Use `theme` to tune the highlight colors. + +```ts +provideAngularRenderScan({ + theme: { + fast: [147, 197, 253], + medium: [253, 224, 71], + slow: [239, 68, 68], + labelBackground: [124, 58, 237], + labelBackgroundSlow: [220, 38, 38] + } +}); +``` + +```ts +interface AngularRenderScanTheme { + fast: readonly [number, number, number]; + medium: readonly [number, number, number]; + slow: readonly [number, number, number]; + labelBackground: readonly [number, number, number]; + labelBackgroundSlow: readonly [number, number, number]; +} +``` + +## Toolbar + +The toolbar shows: + +- scan on/off switch +- FPS +- latest cycle time +- changed component count +- slowest component +- copy slow issues prompt +- clear stats button + +Drag the toolbar to move it. Use `Details` to inspect one component at a time and pin a recommendation panel. + +## Details Mode + +Use the `Details` checkbox in the toolbar to inspect individual components without keyboard modifiers. + +1. Interact with the page so Angular Render Scan captures a render cycle. +2. Check `Details` in the toolbar. +3. Hover over a captured component to show a dashed highlight. +4. Click the component to pin the recommendation panel. +5. Close the panel when finished. + +The recommendation panel shows severity, latest duration, average duration, render count, reason, selector, changed inputs, recent cycles, estimated cost, and component-local Angular recommendations based on the captured issue. For slow components, the panel also shows `Copy Slow Issue Prompt`, which copies a prompt for only that component. + ## AI Performance Prompt -Click `Copy Slow Issues Prompt` in the toolbar, or call `copyAIPrompt()`, to copy a self-contained prompt for an AI coding assistant. It includes environment details, recent cycle history, Angular render-cycle evidence, thresholds, and an issue list for components exceeding the performance warning threshold (10ms by default). +Use `Copy Slow Issues Prompt` in the toolbar, or call `getAIPrompt()` / `copyAIPrompt()`, to generate a self-contained prompt for an AI coding assistant. The prompt includes environment details, recent cycle history, the latest cycle, configured thresholds, and an issue list for components exceeding the performance warning threshold (10ms by default). + +The copied prompt is intentionally focused: it does not copy every render entry. It lists slow components with selector, latest render time, average render time, render count, reason, changed inputs when available, and an estimated cost based on latest duration, cycle share, and observed render count. It does not include raw DOM nodes, component instances, or source code. + +```ts +provideAngularRenderScan({ + promptContext: 'Angular 18 app using signals and OnPush components', + maxRecordedCycles: 20 +}); +``` + +## Keyboard Shortcuts + +The visual overlay responds to the following keyboard shortcuts when enabled: + +| Shortcut | Description | +|---|---| +| `Alt+Shift+S` | Toggles the active/enabled state of the scanner. | +| `Alt+Shift+D` | Toggles Details Mode (hover inspect and recommendations). | +| `Alt+Shift+C` | Copies the AI performance diagnostic prompt. | +| `Alt+Shift+X` | Clears all telemetry counts and history. | +| `Alt+Shift+T` | Toggles the floating toolbar visibility. | +| `Escape` | Closes any pinned recommendation, CPU breakdown, or CD waterfall panel. | + +## Playwright Headless Audit API + +You can programmatically verify Angular performance inside Playwright end-to-end tests using the headless audit API. + +```ts +import { test, expect } from '@playwright/test'; +import { startRenderAudit } from 'angular-render-scan'; + +test('verify no performance regressions or wasted checks', async ({ page }) => { + await page.goto('/'); + + // Start the audit session + const audit = await startRenderAudit(page); + + // Interact with the page + await page.click('button.expensive-operation'); + + // Stop the audit session and fetch the telemetry report + const report = await audit.stop(); + + // Validate rendering frequency + const cardRenders = await report.rendersFor('ProductCardComponent'); + expect(cardRenders).toBeLessThanOrEqual(2); -The copied prompt is intentionally focused: it does not copy every render entry. It lists slow components with selector, latest render time, average render time, render count, reason, changed inputs when available, and an estimated cost based on latest duration, cycle share, and observed render count. It does not include raw DOM nodes, component instances, source code, or large object values. + // Validate render duration (ms) + const maxDuration = await report.maxDurationFor('ProductCardComponent'); + expect(maxDuration).toBeLessThan(16.7); // smooth 60fps check -## Component Detail Panel + // Validate overall no-op waste ratio + const wasteRatio = await report.wastedRenderPercentage(); + expect(wasteRatio).toBeLessThan(20); // max 20% wasted checks -Check `Details` in the toolbar to turn on component inspection. Hover over a captured component to show a dashed highlight, then click it to pin the recommendation panel. The panel stays open until you close it. + // Validate budget violations + const violations = await report.budgetViolations(); + expect(violations.length).toBe(0); +}); +``` -The panel shows severity, latest duration, average duration, render count, reason, selector, changed inputs, recent cycles, estimated cost, and component-local Angular recommendations based on the captured issue. For slow components, it also shows `Copy Slow Issue Prompt`, which copies a prompt for only that component with the details needed by an AI coding assistant. +## Manual Marking + +Automatic instrumentation is preferred. If you need a specific manual target, you can still mark an element with `AngularRenderScanMarkDirective`. + +```ts +import { AngularRenderScanMarkDirective } from 'angular-render-scan'; + +@Component({ + standalone: true, + imports: [AngularRenderScanMarkDirective], + template: ` +
+ ... +
+ ` +}) +export class CartSummaryComponent {} +``` ## Production Behavior @@ -123,14 +371,63 @@ Angular Render Scan is intended for development and demo debugging. Provider mod ```ts provideAngularRenderScan({ - dangerouslyForceRunInProduction: true, + dangerouslyForceRunInProduction: true }); ``` Use that option carefully. The scanner adds runtime instrumentation, DOM reads, canvas work, and console/debug behavior. -## Repository +## Demo + +Open the hosted demo: + +```txt +https://edisonaugusthy.github.io/angular-render-scan/ +``` + +Run the local demo: + +```sh +npm install +npm run dev +``` + +Open: + +```txt +http://127.0.0.1:4200/ +``` + +The demo includes signal updates, `OnPush` updates, nested components, and intentionally slow work to show the heatmap behavior. + +## Development + +```sh +npm run test +npm run build +npm run test:e2e +``` + +Useful project docs: + +- [agent.md](agent.md): DDD rules, domain boundaries, type/style guide, and quality bar. +- [feature.md](feature.md): feature spec, domain model, and roadmap. + +## Release + +Release runs automatically when changes are pushed to `main`. The workflow bumps +`packages/angular-render-scan/package.json` by one patch version, commits that +version bump back to `main`, publishes the package to npm, and creates a GitHub +release for the new version. + +Before the first automated publish, add a valid npm publish token as the GitHub +Actions secret `NPM_TOKEN`. After the package exists on npm, you can instead +configure npm trusted publishing for repository `edisonaugusthy/angular-render-scan` +and workflow filename `release.yml`. -Source, issues, and full documentation are available at: +To publish manually: -https://github.com/edisonaugusthy/angular-render-scan +1. Go to GitHub Actions. +2. Select `Release`. +3. Choose the `main` branch. +4. Click `Run workflow`.