Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
19 changes: 7 additions & 12 deletions .github/workflows/app-storybook-generate-baseline.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
name: Storybook Regenerate Baseline

# Baseline tool for the visual layer. The PR/push CHECK lives in the ordered
# governance pipeline (governance.yml → job "visual", gated by the Governance
# Gate); this workflow is dispatch-only and exists to regenerate the committed
# linux baselines in apps/storybook/.storybook/test-visual/__image_snapshots__/linux/.
#
# Baselines are platform-specific, so ONLY this workflow (or a linux container)
# may generate them. To bootstrap or intentionally update: run it with
# update_baselines=true, download the "visual-baselines-linux" artifact and
# commit its contents. Dispatching with the default (false) runs a one-off
# comparison against the committed baselines, outside any PR.

on:
workflow_dispatch:
inputs:
update_baselines:
description: 'Regenerate linux baselines and upload them as an artifact (commit manually)'
type: boolean
default: false
modes:
description: 'Comma-separated visual modes to (re)generate, e.g. light-desktop,dark-mobile (empty = full matrix; see .storybook/visual-modes.js)'
type: string
default: ''

permissions:
contents: read
Expand All @@ -30,7 +23,7 @@ jobs:
visual:
name: Storybook Generate Baseline
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 60
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

Expand Down Expand Up @@ -63,6 +56,8 @@ jobs:
- name: Regenerate baselines
if: ${{ github.event.inputs.update_baselines == 'true' }}
run: pnpm --filter storybook run test:visual:update
env:
VISUAL_MODES: ${{ github.event.inputs.modes }}

- name: Upload regenerated baselines
if: ${{ github.event.inputs.update_baselines == 'true' }}
Expand Down
60 changes: 14 additions & 46 deletions .github/workflows/governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ permissions:
pull-requests: read

jobs:
# Detect whether webkit sources changed. Required checks below stay
# registered on every PR; jobs short-circuit when nothing relevant moved.
changes:
name: Detect Changes
runs-on: ubuntu-latest
Expand All @@ -34,39 +32,31 @@ jobs:
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
toolkit:
- 'packages/webkit/**'
- '.specs/**'
- '.claude/hooks/**'
- '.claude/rules/**'
- 'apps/storybook/**'
# Stories are fixtures of the unit suite (composeStories), so
# storybook changes retrigger the tests too.
- 'packages/webkit/**'
tests:
- 'packages/webkit/**'
- 'apps/storybook/**'
# Pixels react to any styled layer: components, tokens, icons, stories.
visual:
- 'packages/webkit/**'
- 'packages/theme/**'
- 'packages/icons/**'
- 'apps/storybook/**'
# Lint canaries guard the lint CONFIGS: anything that can change a
# rule's behavior (configs, the fixtures themselves, tooling deps)
# retriggers the canary job.
canary:
- 'packages/webkit/**'
- 'lint-canaries/**'
- 'eslint.config.js'
- 'packages/webkit/eslint.config.js'
- '.stylelintrc.json'
- '.prettierrc.json'
- '.prettierignore'
- 'commitlint.config.js'
- 'package.json'
- 'pnpm-lock.yaml'
- '.stylelintrc.json'
- '.prettierrc.json'
- '.prettierignore'
- '.github/workflows/governance.yml'

# Security Scanning (parallel, fast)
security:
name: Security Scans
needs: changes
Expand Down Expand Up @@ -103,12 +93,6 @@ jobs:
working-directory: packages/webkit
continue-on-error: true

# Lint Canary (parallel, side by side with Security Scans) — INVERTED logic.
# Every fixture in lint-canaries/ violates one configured lint rule ON
# PURPOSE and must KEEP failing it. A fixture that lints clean means the
# rule was weakened or removed (eslint.config.js / .stylelintrc.json /
# .prettierrc.json / commitlint.config.js), so the job fails and stops the
# pipeline. See lint-canaries/README.md.
lint-canary:
name: Lint Canary (broken samples must stay broken)
needs: changes
Expand All @@ -132,7 +116,6 @@ jobs:
- name: Run lint canaries (every fixture must still fail its rule)
run: node lint-canaries/index.js

# Linting & Formatting (parallel)
lint:
name: Linting & Formatting
needs: changes
Expand Down Expand Up @@ -162,7 +145,6 @@ jobs:
- name: Prettier check (fail on diff)
run: pnpm prettier --check "packages/webkit/**/*.{js,ts,vue,css,json,md}"

# Type Safety & Coverage (parallel)
types:
name: TypeScript & Type Coverage
needs: changes
Expand Down Expand Up @@ -191,7 +173,6 @@ jobs:
run: npx type-coverage -p . --at-least 95 --detail
working-directory: packages/webkit

# Build Verification (needs lint + types)
build:
name: Build Verification
runs-on: ubuntu-latest
Expand All @@ -212,10 +193,6 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Pack dry run
run: pnpm pack:dry
working-directory: packages/webkit

- name: Assert no test files leak into the tarball
run: pnpm pack:check
working-directory: packages/webkit
Expand All @@ -224,7 +201,6 @@ jobs:
run: pnpm run size
working-directory: packages/webkit

# Storybook Smoke Test
storybook:
name: Storybook Build
runs-on: ubuntu-latest
Expand All @@ -251,10 +227,6 @@ jobs:
- name: Verify Storybook output
run: test -d apps/storybook/dist && echo "Storybook built successfully"

# Adoption toolkit — the toolkit's own node:test suites (ESLint rules, MCP, CLI,
# stylelint config) + catalog drift check. Everything ships inside @aziontech/webkit;
# the catalog is generated from package.json#exports + .specs, so this asserts the
# committed catalog.json matches a fresh build and can never go stale.
toolkit:
name: Adoption Toolkit (tests + catalog drift)
needs: changes
Expand All @@ -263,7 +235,7 @@ jobs:
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # full history so check-tests can diff a PR against its base
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
Expand All @@ -289,10 +261,6 @@ jobs:
- name: Every component has its test (testing.md)
run: pnpm --filter @aziontech/webkit run test:gate

# Vitest browser mode (real Chromium), sharded by 4 — behavior/structure/ARIA
# on an unstyled DOM. Ordered after lint + types (same tier as build/storybook).
# `!contains(needs.*.result, 'failure')` keeps it runnable when upstream jobs
# were cleanly skipped (e.g. a storybook-only PR skips the webkit-scoped lint).
tests:
name: Vitest browser (shard ${{ matrix.shard }}/4)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -332,17 +300,16 @@ jobs:
run: pnpm exec vitest run --shard=${{ matrix.shard }}/4
working-directory: packages/webkit

# Visual regression — the styled layer: every story screenshotted via
# @storybook/test-runner and compared against the committed linux baselines
# (test:visual:ci fails on any missing baseline). Last link of the chain:
# pixels only run once behavior (tests) and both builds are green. Baseline
# regeneration lives in app-storybook-test-visual-regression.yml (dispatch).
visual:
name: Storybook visual tests
name: Storybook visual tests (shard ${{ matrix.shard }}/4)
runs-on: ubuntu-latest
needs: [changes, security, lint-canary, lint, types, build, storybook, tests]
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') && needs.changes.outputs.visual == 'true' }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

Expand Down Expand Up @@ -372,19 +339,20 @@ jobs:
- name: Build Storybook
run: pnpm run storybook:build

- name: Run visual tests
- name: Run visual tests (shard ${{ matrix.shard }}/4)
run: pnpm --filter storybook run test:visual:ci
env:
VISUAL_SHARD: ${{ matrix.shard }}/4

- name: Upload diff output on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: visual-diff-output
name: visual-diff-output-shard-${{ matrix.shard }}
path: apps/storybook/.storybook/test-visual/__diff_output__/
retention-days: 7
if-no-files-found: ignore

# Summary Gate — passes when every upstream job is success or cleanly skipped.
governance-check:
name: Governance Gate
runs-on: ubuntu-latest
Expand Down
48 changes: 18 additions & 30 deletions apps/storybook/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,31 @@
import { setup } from '@storybook/vue3'
import PrimeVue from 'primevue/config'
import Tooltip from 'primevue/tooltip'
import { withThemeByClassName } from '@storybook/addon-themes'
import { addons } from '@storybook/preview-api'
import { GLOBALS_UPDATED } from '@storybook/core-events'

// v3 token build: primitives, --bg-* / --text-* / --border-*, and .text-body-md etc.
// (replaces injectCssVars(), which only emitted --background-* aliases — webkit components use --bg-*)
import '@aziontech/theme/globals.css'
import 'primeflex/primeflex.css'
import '../src/styles/preview.css'
import '@aziontech/theme'
import '@aziontech/icons'
import '@aziontech/webkit/styles/country-flags'

setup((app) => {
app.use(PrimeVue, {
ripple: false
})
import { withThemeByClassName } from '@storybook/addon-themes'
import { GLOBALS_UPDATED } from '@storybook/core-events'
import { addons } from '@storybook/preview-api'
import { setup } from '@storybook/vue3'

app.directive('tooltip', Tooltip)
})
import { STORYBOOK_VIEWPORTS, THEME_CLASSES } from './visual-modes.js'

// withThemeByClassName only runs as a story decorator, so on pure-MDX docs pages
// (Get Started, Style Guide) the toolbar toggle never updated the html class.
// Mirror the `theme` global onto <html> ourselves so those pages switch too.
const THEME_CLASSES = {
light: ['azion', 'azion-light'],
dark: ['azion', 'azion-dark']
}
function applyThemeClass(name) {
const el = document.documentElement
el.classList.remove('azion', 'azion-light', 'azion-dark')
el.classList.add(...(THEME_CLASSES[name] || THEME_CLASSES.dark))
const docElement = document.documentElement

docElement.classList.remove('azion', 'azion-light', 'azion-dark')
docElement.classList.add(...(THEME_CLASSES[name] || THEME_CLASSES.dark))
}

applyThemeClass('dark')

try {
addons.getChannel().on(GLOBALS_UPDATED, ({ globals }) => {
if (globals && globals.theme) applyThemeClass(globals.theme)
})
} catch {
/* headless/test contexts without a channel */
} catch(error) {
console.error(`[ERROR]: ${error}`)
}

export const parameters = {
Expand Down Expand Up @@ -83,6 +68,9 @@ export const parameters = {
inputBorderRadius: 4
}
},
viewport: {
viewports: STORYBOOK_VIEWPORTS
},
backgrounds: {
default: 'azion azion-dark',
values: [
Expand Down Expand Up @@ -113,8 +101,8 @@ export const parameters = {
export const decorators = [
withThemeByClassName({
themes: {
light: 'azion azion-light',
dark: 'azion azion-dark'
light: THEME_CLASSES.light.join(' '),
dark: THEME_CLASSES.dark.join(' ')
},
defaultTheme: 'dark'
})
Expand Down
Loading
Loading