Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
50ae88c
Add play / pause controls to hero computer animation
webstackdev Dec 5, 2025
2fd4d2e
Lint fix in BreadCrumbs page object model
webstackdev Dec 5, 2025
1eae48c
Update to tasks for lint in package.json to align with Action workflow
webstackdev Dec 5, 2025
3731f76
Update deployment action workflow to manually add succeed / fail depl…
webstackdev Dec 5, 2025
260e3ba
Spelling fix, alone to trigger new PR workflow
webstackdev Dec 5, 2025
3d2597d
Update deployment workflow to always update with deployment outcome e…
webstackdev Dec 5, 2025
0a25ea8
Update deployment workflow with wider permissions for Vercel bot
webstackdev Dec 5, 2025
c4f64e2
Improve notification card for Vercel deployment action workflow
webstackdev Dec 5, 2025
65e4fae
Update link for failed preview in Vercel deployment action workflow
webstackdev Dec 5, 2025
7d0e8ae
Update package.json prepare script so it only runs Husky when a .git …
webstackdev Dec 6, 2025
0f70211
Trivial doc fix to trigger Action workflow for debugging
webstackdev Dec 6, 2025
2b2b427
Merge branch 'main' into feature/pause-and-play-on-hero
webstackdev Dec 6, 2025
5f2aed5
Refactor date-check strategy in privacy policy unit test to run on Ve…
webstackdev Dec 6, 2025
b233de5
Fix lint error in privacy policy unit test
webstackdev Dec 6, 2025
bfc5124
Fix import error in privacy policy unit test
webstackdev Dec 6, 2025
2150348
Remove relative paths from Husky called from 'prepare' task on npm in…
webstackdev Dec 6, 2025
256f31c
Add single API endpoint to trigger all CRON jobs, avoiding need to up…
webstackdev Dec 6, 2025
ebc0e95
Fix lint errors in new CRON runner and test
webstackdev Dec 6, 2025
524db89
Open preview link in new tab, fix output for 'Trigggered by'
webstackdev Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 62 additions & 6 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,63 @@ jobs:
- name: Comment preview URL on PR
if: always() && steps.vercel-preview.outcome == 'success'
uses: actions/github-script@v8
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
with:
script: |
const previewUrl = '${{ steps.vercel-preview.outputs.preview-url }}';
const workflowRun = context.payload.workflow_run;
const pr = workflowRun?.pull_requests?.[0];
if (!pr || !previewUrl) {
core.warning('Missing pull request metadata or preview URL; skipping preview success comment.');
if (!pr) {
core.warning('Missing pull request metadata; skipping preview success comment.');
return;
}

const rawPreviewUrl = '${{ steps.vercel-preview.outputs.preview-url }}'.trim();
const isVercelUrl = (url) => typeof url === 'string' && /vercel\.(app|com)/.test(url);
const fetchDeploymentUrl = async () => {
if (!process.env.VERCEL_TOKEN || !process.env.VERCEL_PROJECT_ID) {
core.info('Missing Vercel credentials; cannot query deployment API.');
return null;
}
if (typeof fetch !== 'function') {
core.info('Fetch API unavailable in this runtime.');
return null;
}
const query = new URLSearchParams({
projectId: process.env.VERCEL_PROJECT_ID,
'meta-githubCommitSha': workflowRun?.head_sha ?? '',
limit: '1'
});
if (process.env.VERCEL_ORG_ID) {
query.set('teamId', process.env.VERCEL_ORG_ID);
}
const response = await fetch(`https://api.vercel.com/v6/deployments?${query.toString()}`, {
headers: {
Authorization: `Bearer ${process.env.VERCEL_TOKEN}`
}
});
if (!response.ok) {
core.warning(`Unable to fetch deployment info (status ${response.status}).`);
return null;
}
const data = await response.json();
const deployment = data?.deployments?.[0];
if (deployment?.url) {
return `https://${deployment.url}`;
}
if (deployment?.inspectorUrl) {
return deployment.inspectorUrl.startsWith('http')
? deployment.inspectorUrl
: `https://${deployment.inspectorUrl}`;
}
return null;
};

let previewUrl = isVercelUrl(rawPreviewUrl) ? rawPreviewUrl : await fetchDeploymentUrl();
if (!isVercelUrl(previewUrl)) {
core.warning('Unable to resolve Vercel preview URL; skipping preview success comment.');
return;
}

Expand All @@ -89,7 +139,13 @@ jobs:
const commitUrl = sha
? `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${sha}`
: `https://github.com/${context.repo.owner}/${context.repo.repo}`;
const actor = workflowRun.actor ?? 'workflow_run';
const actorLogin = typeof workflowRun.actor === 'string'
? workflowRun.actor
: workflowRun.actor?.login;
const actor = actorLogin ?? context.actor ?? 'workflow_run';
const actorLink = workflowRun.actor?.html_url || (actorLogin ? `https://github.com/${actorLogin}` : null);
const actorDisplay = actor.startsWith('@') ? actor : `@${actor}`;
const triggeredBy = actorLink ? `[${actorDisplay}](${actorLink})` : actorDisplay;

const body = [
commentTag,
Expand All @@ -99,9 +155,9 @@ jobs:
'| --- | --- |',
`| Branch | \`${branch}\` |`,
`| Commit | [${shortSha}](${commitUrl}) |`,
`| Preview | [Open preview](${previewUrl}) |`,
`| Preview | <a href="${previewUrl}" target="_blank" rel="noopener noreferrer">Open preview</a> |`,
'',
`_Triggered by @${actor}_`
`_Triggered by ${triggeredBy}_`
].join('\n');

const comments = await github.paginate(github.rest.issues.listComments, {
Expand Down
30 changes: 30 additions & 0 deletions .husky/prepare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env node
/* eslint-disable no-undef */
/**
* Husky prepare script to install git hooks. It's designed to quiet warnings on
* CI environments where .git directory may be missing when "prepare" script runs
* (e.g., during "npm install" step).
*/
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import { execSync } from 'node:child_process'

const projectRoot = process.cwd()
const gitDirectory = join(projectRoot, '.git')

if (!existsSync(gitDirectory)) {
console.warn(`βœ… Skipping Husky install: missing .git directory at ${gitDirectory}`)
process.exit(0)
}

try {
console.log(`Running Husky install from ${projectRoot}`)
execSync('husky', { stdio: 'inherit', cwd: projectRoot })
console.log('βœ… Husky install complete')
} catch (error) {
console.error('❌ Husky install failed')
const status = typeof error === 'object' && error && 'status' in error && typeof error.status === 'number'
? error.status
: 1
process.exit(status)
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"squoosh",
"tanabata",
"TIMESTAMPTZ",
"tktco",
"Trino",
"TRUNC",
"Tscompile",
Expand Down
1 change: 1 addition & 0 deletions @types/window.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ declare global {
updateConsent?: (_category: 'analytics' | 'marketing' | 'functional', _value: boolean) => void
cacheEmbed?: (_key: string, _data: unknown, _ttl: number) => void
saveMastodonInstance?: (_domain: string) => void
setOverlayPauseState?: (_source: string, _isPaused: boolean) => void

/**
* Custom evaluation error injected during Playwright tests
Expand Down
15 changes: 2 additions & 13 deletions _TODO.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
<!-- markdownlint-disable-file -->
# TODO

## Pause and Play

Next, I'd like to add a "pause" and "play" icon to src/components/Animations/Computers
There are icons with those names already configured for the Icon component.
There are hooks for pause and play already setup in the component.
The icon should be displayed in the low right hand corner of the animation, with 4px of padding from the bottom and right side. It should overlay the animation, not expand the bounding box of the animation.

## Performance

Implement mitigations in test/e2e/specs/07-performance/PERFORMANCE.md

## GitHub

- Make sure actions workflows are working correctly after performance tests pass and whole suite is green
- Change Dependabut to open a single PR with all dependency updates
- Add 'hotfix' branch and add branch protection rules

## Analytics

Vercel Analytics
Expand All @@ -35,6 +22,8 @@ See note in src/components/scripts/sentry/client.ts - "User Feedback - allow use

docs/CONTACT_FORM.md

Where to upload to?

## Search

Add Upstash Search as a Vercel Marketplace Integration.
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"format:code": "FORCE_COLOR=1 npx prettier --write \"@types/**/*.{js,ts}\" \"src/**/*.{js,ts,tsx,astro}\" --plugin=prettier-plugin-astro",
"format:json": "FORCE_COLOR=1 npx prettier --write '**/*.json' --cache --ignore-path .gitignore",
"format:style": "FORCE_COLOR=1 npx stylelint --fix \"src/**/*.{css,astro}\"",
"lint": "npm run lint:base && npm run lint:actions",
"lint": "npm run lint:base && npm run lint:actions && npm run check",
"lint:base": "npm run lint:json && npm run lint:style && npm run lint:tsc:check && npm run lint:code",
"lint:code": "npx eslint \"@types/**/*.{js,ts}\" \"src/**/*.{js,ts,tsx,astro}\" \"test/**/*.{js,ts,tsx,astro}\"",
"lint:tsc:check": "tsc --noEmit -p tsconfig.json --pretty false",
Expand All @@ -60,7 +60,7 @@
"test:e2e:full": "dotenv -e .env.development -- cross-env FORCE_COLOR=1 E2E_MOCKS=1 npx playwright test",
"test:unit": "FORCE_COLOR=1 npx vitest run",
"upgrade": "npx @astrojs/upgrade",
"prepare": "node -e \"const fs=require('node:fs');if(!fs.existsSync('.git')){console.log('Skipping Husky install (missing .git directory)');process.exit(0);}\" && husky"
"prepare": "node .husky/prepare.js"
},
"dependencies": {
"@astrojs/check": "0.9.6",
Expand Down Expand Up @@ -158,7 +158,7 @@
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-astro": "1.5.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jsdoc": "61.4.1",
"eslint-plugin-jsdoc": "61.4.2",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-security": "3.0.1",
"eslint-plugin-yml": "1.19.0",
Expand Down
5 changes: 4 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { defineConfig, devices } from '@playwright/test'
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
import dotenv from 'dotenv'
import { isCI } from 'src/lib/config/environmentServer'

if ( !isCI() ) dotenv.config({ path: '.env.development' })

/**
* See https://playwright.dev/docs/test-configuration.
Expand Down
28 changes: 28 additions & 0 deletions src/components/Animations/Computers/client/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,19 @@ describe('ComputersAnimationElement', () => {
const controllerArgs = createAnimationControllerMock.mock.calls[0]?.[0]
const pauseHandler = controllerArgs?.onPause
const resumeHandler = controllerArgs?.onPlay
const toggleButton = element.querySelector<HTMLButtonElement>('[data-animation-toggle]')

pauseHandler?.()
expect(element.getAttribute('data-animation-state')).toBe('paused')
expect(toggleButton?.getAttribute('aria-pressed')).toBe('true')
expect(toggleButton?.getAttribute('aria-label')).toBe('Play animation')
resumeHandler?.()

expect(timelineMock.pause).toHaveBeenCalled()
expect(timelineMock.play).toHaveBeenCalled()
expect(element.getAttribute('data-animation-state')).toBe('playing')
expect(toggleButton?.getAttribute('aria-pressed')).toBe('false')
expect(toggleButton?.getAttribute('aria-label')).toBe('Pause animation')
expect(getBreadcrumbOperations()).toEqual(expect.arrayContaining(['pause', 'resume']))
})
})
Expand Down Expand Up @@ -221,10 +228,31 @@ describe('ComputersAnimationElement', () => {
element.initialize()

element.pause()
expect(element.getAttribute('data-animation-state')).toBe('paused')
element.resume()

expect(timelineMock.pause).toHaveBeenCalledTimes(1)
expect(timelineMock.play).toHaveBeenCalledTimes(1)
expect(element.getAttribute('data-animation-state')).toBe('playing')
})
})

it('requests pause and play through the animation controller when the toggle is clicked', async () => {
await renderComputersAnimation(async ({ element }) => {
element.initialize()

const toggleButton = element.querySelector<HTMLButtonElement>('[data-animation-toggle]')
const controllerHandle = getLastControllerHandle()

expect(toggleButton).toBeTruthy()

toggleButton?.click()
expect(controllerHandle?.requestPause).toHaveBeenCalledTimes(1)

element.pause()

toggleButton?.click()
expect(controllerHandle?.requestPlay).toHaveBeenCalledTimes(1)
})
})

Expand Down
Loading
Loading