Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
43aeff4
"Claude PR Assistant workflow"
MaryWylde Apr 26, 2026
290de3d
"Claude Code Review workflow"
MaryWylde Apr 26, 2026
e7ad6f3
Merge pull request #86 from keepsimpleio/add-claude-github-actions-17…
MaryWylde Apr 26, 2026
4f90a4f
chore: add Playwright e2e QA infrastructure
MaryWylde Apr 27, 2026
fbbcfed
chore: change Claude PR rules
MaryWylde Apr 27, 2026
75569c6
chore: add scheduled Playwright workflow
MaryWylde Apr 27, 2026
d186cdc
Merge pull request #87 from keepsimpleio/feat/QA-Agent
MaryWylde Apr 27, 2026
59a902b
chore: change var to secret on workflow
MaryWylde Apr 27, 2026
d2a6a19
Merge pull request #88 from keepsimpleio/feat/QA-Agent
MaryWylde Apr 27, 2026
fe8938e
chore: update playwright yml
MaryWylde Apr 27, 2026
58be9ba
Merge pull request #89 from keepsimpleio/feat/QA-Agent
MaryWylde Apr 27, 2026
1f39078
chore: update playwright yml, AGAIN
MaryWylde Apr 27, 2026
41752ef
Merge pull request #90 from keepsimpleio/feat/QA-Agent
MaryWylde Apr 27, 2026
d907edc
chore: update playwright yml, AGAIN
MaryWylde Apr 27, 2026
bdfc00c
Merge pull request #91 from keepsimpleio/feat/QA-Agent
MaryWylde Apr 27, 2026
2478a84
feat: add AI Atlas page with Environment/Security toggle
manager May 5, 2026
2b278c5
feat: AI Atlas Security mode + Russian localization + polish
manager May 6, 2026
52e71e5
fix: drop xmlns attr from foreignObject HTML wrapper
manager May 6, 2026
e37c95d
chore: ignore .playwright-mcp screenshot dir
manager May 6, 2026
8f053ed
chore: ignore CLAUDE.local.md
manager May 6, 2026
195e4e3
Merge pull request #93 from keepsimpleio/feat/ai-atlas
MaryWylde May 6, 2026
941f1c5
chore(ai-atlas): tighten Terminal copy and classify Elea's products
manager May 6, 2026
8027b13
fix(ai-atlas): mobile layout polish and drop services metric
manager May 6, 2026
f45363b
Merge pull request #94 from keepsimpleio/feat/ai-atlas
MaryWylde May 6, 2026
f6a6787
fix(mobile): logo navigates home; AI Atlas no longer hover-locks on A…
manager May 6, 2026
992f568
Merge pull request #95 from keepsimpleio/feat/ai-atlas
MaryWylde May 6, 2026
5e891cf
fix(ai-atlas): stop security callouts overflowing canvas on mobile
MaryWylde May 6, 2026
1eb45b2
Merge pull request #96 from keepsimpleio/chore/ai-atlas-mobile
MaryWylde May 6, 2026
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
44 changes: 44 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Claude Code Review

on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"

jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

50 changes: 50 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read

# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr *)'

146 changes: 146 additions & 0 deletions .github/workflows/playwright-scheduled.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: Playwright scheduled

# Runs the Playwright suite weekly and on manual dispatch.
# Does NOT gate pull requests — pull-request-check.yml is unrelated.
# See QA_PLAN.md §5 for the full specification.
#
# Mirrors the cypress-manual.yml pattern: the workflow builds *this branch's
# source* with the chosen environment's `.env.<target>` file, starts a local
# `next start` server, and runs Playwright against `http://localhost:3005`.
# This decouples the test from whatever is currently deployed at staging /
# prod URLs — newly added selectors, fixtures, and components are exercised
# against real Strapi data without waiting for a deploy.

on:
schedule:
# Monday 06:00 UTC = Monday 10:00 Yerevan (UTC+4).
- cron: '0 6 * * 1'
workflow_dispatch:
inputs:
target_env:
description: 'Which env file to build with (controls Strapi backend, OAuth IDs, etc.)'
type: choice
required: true
default: staging
options:
- staging
- prod
scope:
description: 'Which tier to run (ignored if spec_path is provided)'
type: choice
required: true
default: all
options:
- all
- P0
- P1
- P2
spec_path:
description: 'Optional specific spec file or glob (overrides scope)'
type: string
required: false
default: ''
browser:
description: 'Browser to run against'
type: choice
required: true
default: chromium
options:
- chromium
- firefox
- webkit
- all

permissions:
contents: read

jobs:
playwright:
name: Playwright (${{ github.event.inputs.target_env || 'staging' }} / ${{ github.event.inputs.scope || 'all' }} / ${{ github.event.inputs.browser || 'chromium' }})
runs-on: ubuntu-latest
timeout-minutes: 45
env:
CI: 'true'
APP_ENV: ${{ github.event.inputs.target_env || 'staging' }}
# NODE_ENV is intentionally NOT set at the job level — yarn treats
# NODE_ENV=production as a signal to skip devDependencies, which
# would drop @axe-core/playwright (used by tests/p2/a11y.spec.ts).
# The build step and the webServer block in playwright.config.ts
# each wrap their own command in `cross-env NODE_ENV=production`,
# so production mode is still enforced where it matters.
#
# The webServer block in playwright.config.ts reads APP_ENV and runs
# `next start` against the freshly-built `.next/`. baseURL stays at
# the default localhost:3005.
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
# Intentionally NOT caching yarn — a stale runner cache was
# leaving @axe-core/playwright out of node_modules even though
# it sat in yarn.lock. Mirrors cypress-manual.yml which also
# opts out of caching.

- name: Install dependencies
# Plain `yarn install` (no --frozen-lockfile) — matches the
# cypress-manual.yml pattern. Slower per run (~30-60s vs cached)
# but guarantees a clean node_modules every time.
run: yarn install

- name: Decode prod env file
if: env.APP_ENV == 'prod'
run: echo "${{ secrets.ENV_PRODUCTION }}" | base64 -d > .env.prod

- name: Decode staging env file
if: env.APP_ENV == 'staging'
run: echo "${{ secrets.ENV_STAGING }}" | base64 -d > .env.staging

- name: Install Playwright browsers
run: yarn playwright install --with-deps ${{ github.event.inputs.browser == 'all' && 'chromium firefox webkit' || github.event.inputs.browser || 'chromium' }}

- name: Build app
run: yarn cross-env NODE_ENV=production APP_ENV=${{ env.APP_ENV }} next build

- name: Resolve scope path
id: scope
run: |
SPEC_PATH="${{ github.event.inputs.spec_path }}"
SCOPE="${{ github.event.inputs.scope || 'all' }}"
if [ -n "$SPEC_PATH" ]; then
echo "path=$SPEC_PATH" >> "$GITHUB_OUTPUT"
else
case "$SCOPE" in
P0) echo "path=tests/p0/" >> "$GITHUB_OUTPUT" ;;
P1) echo "path=tests/p1/" >> "$GITHUB_OUTPUT" ;;
P2) echo "path=tests/p2/" >> "$GITHUB_OUTPUT" ;;
*) echo "path=tests/" >> "$GITHUB_OUTPUT" ;;
esac
fi

- name: Resolve Playwright projects
id: projects
run: |
BROWSER="${{ github.event.inputs.browser || 'chromium' }}"
if [ "$BROWSER" = "all" ]; then
echo "args=--project=chromium --project=firefox --project=webkit" >> "$GITHUB_OUTPUT"
else
echo "args=--project=$BROWSER" >> "$GITHUB_OUTPUT"
fi

- name: Run Playwright
# webServer block in playwright.config.ts spawns `next start` itself
# (because APP_ENV is set), waits for http://localhost:3005, runs
# the suite, and tears the server down at the end.
run: yarn playwright test ${{ steps.scope.outputs.path }} ${{ steps.projects.outputs.args }}

- name: Upload HTML report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ github.run_id }}
path: playwright-report
retention-days: 14
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.vscode
.history
.idea/
.codex

# Logs
logs
Expand Down Expand Up @@ -116,6 +117,16 @@ dist
package-lock.json

.claude/settings.local.json
CLAUDE.local.md

# Playwright
playwright-report/
test-results/
blob-report/
.playwright/
.playwright-mcp/

# Local QA planning notes (do not commit)
QA_PLAN.md
QA_RECON.md
TODO.md
10 changes: 6 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ interface CardProps {

const Card = ({ title, isActive, className }: CardProps) => {
return (
<div className={cn(styles.Card, className, { [styles.active]: isActive })}>
<div
className={cn(styles.card, className, { [styles.cardItem]: isActive })}
>
{title}
</div>
);
Expand Down Expand Up @@ -154,23 +156,23 @@ No Tailwind, no CSS-in-JS, no inline styles (except single dynamic properties li
import cn from 'classnames';
import styles from './Thing.module.scss';

<div className={cn(styles.Wrapper, { [styles.active]: isActive })} />;
<div className={cn(styles.wrapper, { [styles.active]: isActive })} />;
```

### Conditional classes

Always use `classnames` (imported as `cn`):

```tsx
className={cn(styles.Button, {
className={cn(styles.button, {
[styles.primary]: variant === 'primary',
[styles.disabled]: disabled,
})}
```

### SCSS class naming

PascalCase for new code: `.Card`, `.Wrapper`, `.Title`. The codebase mixes PascalCase and camelCase — prefer PascalCase going forward.
camelCase for new code: `.card`, `.cardItem`, `.wrapper`, `.title`. The codebase mixes PascalCase and camelCase — prefer camelCase going forward.

### Global styles

Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"test:firefox": "cypress run --browser firefox",
"test:edge": "cypress run --browser edge",
"test:all": "npm run test:chrome && npm run test:firefox && npm run test:edge",
"test:e2e": "playwright test --project=chromium",
"test:e2e:ui": "playwright test --project=chromium --ui",
"test:e2e:p0": "playwright test tests/p0 --project=chromium",
"test:e2e:p1": "playwright test tests/p1 --project=chromium",
"test:e2e:p2": "playwright test tests/p2 --project=chromium",
"test:e2e:report": "playwright show-report",
"prepare": "husky install"
},
"dependencies": {
Expand Down Expand Up @@ -67,9 +73,11 @@
]
},
"devDependencies": {
"@axe-core/playwright": "^4.11.2",
"@babel/core": "7.19.3",
"@cypress/react": "^9.0.1",
"@cypress/vite-dev-server": "^6.0.3",
"@playwright/test": "^1.59.1",
"@types/amplitude-js": "8.16.2",
"@types/classnames": "2.2.11",
"@types/lodash.debounce": "4.0.7",
Expand Down
64 changes: 64 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/// <reference types="node" />
import { defineConfig, devices } from '@playwright/test';

const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3005';
const isCI = !!process.env.CI;

export default defineConfig({
testDir: './tests',
testIgnore: ['**/fixtures/**', '**/helpers/**'],
fullyParallel: true,
forbidOnly: isCI,
retries: isCI ? 2 : 1,
// Cap local workers. `next dev` compiles routes on-demand; unbounded
// parallelism starves the server and the first-hit latency blows past
// navigationTimeout on pages the compiler hasn't warmed yet. 2 is
// deliberately conservative — the suite still runs under 3 min.
workers: isCI ? 1 : 2,
reporter: [['list'], ['html', { open: 'never' }]],
use: {
baseURL,
locale: 'en-US',
trace: 'on-first-retry',
testIdAttribute: 'data-testid',
actionTimeout: 15_000,
// Raised from 30s — accommodates `next dev`'s compile-on-first-hit cost
// when multiple workers each land on cold routes simultaneously.
navigationTimeout: 60_000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
// Server selection:
// - PLAYWRIGHT_NO_SERVER=1 → don't manage one (e.g. workflow already
// started it, or running against a deployed URL).
// - APP_ENV=staging|prod → pre-built production server (`next start`).
// The CI workflow runs `next build` first, then this block boots
// `next start` against the chosen env file. See
// .github/workflows/playwright-scheduled.yml.
// - else → local `yarn dev`.
webServer: process.env.PLAYWRIGHT_NO_SERVER
? undefined
: {
command:
process.env.APP_ENV === 'staging' || process.env.APP_ENV === 'prod'
? `cross-env NODE_ENV=production APP_ENV=${process.env.APP_ENV} next start -p 3005`
: 'yarn dev',
url: baseURL,
reuseExistingServer: !isCI,
timeout: 180_000,
stdout: 'ignore',
stderr: 'pipe',
},
});
Binary file added public/ai-atlas/bg.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading