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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,17 @@
cache: 'npm'
- run: npm ci
- run: npm run build

e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: [build]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

needs: [build] is misleading—e2e tests don't use build artifacts.

Per playwright.config.ts, the e2e tests run npm run dev (port 5173), starting their own development server. The build job's artifacts are not consumed. Consider either:

  1. Remove needs: [build] and run e2e in parallel with build, or
  2. Update playwright config to use vite preview against dist/ so e2e actually tests the production build.

Option 2 is generally preferred for CI since it validates the actual build output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml at line 62, The CI job currently declares needs:
[build] even though the e2e tests (per playwright.config.ts) run npm run dev
against port 5173 and do not consume build artifacts; either remove needs:
[build] so e2e runs in parallel with build, or (preferred) change the e2e
workflow and playwright.config.ts to run a production preview: have the CI build
produce dist/, start the server with vite preview (serving dist/) and point
Playwright to that URL so the tests validate the production build instead of the
dev server.

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run e2e
Comment on lines +60 to +71

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 3 months ago

In general, the fix is to add an explicit permissions block that grants only the minimal scopes required. For a typical CI pipeline that just checks out code and runs builds/tests, contents: read is sufficient. Since all jobs in this workflow share that same pattern and none push code, create releases, or interact with issues/PRs via the token, we can safely define a single top-level permissions block that applies to all jobs.

The best fix with no functional change is: in .github/workflows/ci.yml, add a root-level permissions: section after the name: (or after on:) that sets contents: read. This documents the intended permissions, ensures they remain restricted if repository defaults change, and addresses the CodeQL warning for all jobs, including the highlighted e2e job. No additional imports, tools, or code changes are needed.

Concretely: edit .github/workflows/ci.yml to insert:

permissions:
  contents: read

at the top workflow level (aligned with name and on). No other lines need to be modified.

Suggested changeset 1
.github/workflows/ci.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,5 +1,8 @@
 name: CI
 
+permissions:
+  contents: read
+
 on:
   push:
     branches: [main]
EOF
@@ -1,5 +1,8 @@
name: CI

permissions:
contents: read

on:
push:
branches: [main]
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +59 to +71

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add explicit permissions to limit GITHUB_TOKEN scope.

The CodeQL analysis correctly flags that this job lacks a permissions block. Adding minimal permissions improves security posture.

🛡️ Proposed fix
   e2e:
     name: E2E Tests
     runs-on: ubuntu-latest
     needs: [build]
+    permissions:
+      contents: read
     steps:
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 60-71: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 59 - 71, The e2e job currently exposes
a full GITHUB_TOKEN scope; add an explicit minimal permissions block to the e2e
job to limit token scope (e.g., under the e2e job define permissions: contents:
read and any other minimal scopes your tests actually require). Update the job
named "e2e" to include this permissions map (adjust scopes only if your
Playwright/e2e steps require additional read/write access) so GITHUB_TOKEN is
restricted during the Run steps and npm/playwright/e2e commands.

Comment on lines +69 to +71

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This E2E job runs npm run e2e, but Playwright is configured for http://localhost:5173 while Vite dev server is configured for port 3000 (vite.config.ts). The job will likely fail waiting on the wrong port. Align the Playwright webServer.port/baseURL with Vite's server.port (or vice versa).

Copilot uses AI. Check for mistakes.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
node_modules/
dist/

*storybook.log
storybook-static
17 changes: 17 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
"stories": [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-onboarding"
],
"framework": "@storybook/react-vite"
};
export default config;
21 changes: 21 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Preview } from '@storybook/react-vite';

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo',
},
},
};

export default preview;
135 changes: 66 additions & 69 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,83 @@
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";

import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';

export default [
js.configs.recommended,
{
ignores: ['dist/', 'node_modules/', 'vite.config.js'],
export default [js.configs.recommended, {
ignores: ['dist/', 'node_modules/', 'vite.config.js'],
}, {
files: ['*.config.ts', '*.config.js'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
globals: {
...globals.node,
},
},
{
files: ['*.config.ts', '*.config.js'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
globals: {
...globals.node,
}, {
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
globals: {
window: 'readonly',
document: 'readonly',
console: 'readonly',
localStorage: 'readonly',
navigator: 'readonly',
setTimeout: 'readonly',
File: 'readonly',
Element: 'readonly',
HTMLInputElement: 'readonly',
React: 'readonly',
},
Comment on lines +34 to +45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use globals.browser instead of manually listing browser globals.

The globals package already exports a browser object containing all standard browser globals. This eliminates manual maintenance and ensures completeness.

♻️ Proposed fix
   globals: {
-    window: 'readonly',
-    document: 'readonly',
-    console: 'readonly',
-    localStorage: 'readonly',
-    navigator: 'readonly',
-    setTimeout: 'readonly',
-    File: 'readonly',
-    Element: 'readonly',
-    HTMLInputElement: 'readonly',
-    React: 'readonly',
+    ...globals.browser,
   },

Apply the same change to the **/*.{ts,tsx} block (lines 61-72).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
globals: {
window: 'readonly',
document: 'readonly',
console: 'readonly',
localStorage: 'readonly',
navigator: 'readonly',
setTimeout: 'readonly',
File: 'readonly',
Element: 'readonly',
HTMLInputElement: 'readonly',
React: 'readonly',
},
globals: {
...globals.browser,
React: 'readonly',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.js` around lines 34 - 45, Replace the manual browser global
list in the eslint config's globals object with the packaged browser set: import
or reference globals.browser and assign it instead of the explicit properties
(the existing globals: { window: ..., React: ... } block); also update the other
TypeScript/TSX globals block (the **/*.{ts,tsx} globals section) to use
globals.browser as well so both places rely on the canonical browser globals
export.

},
rules: {
'no-unused-vars': 'warn',
},
{
files: ['**/*.{js,jsx}'],
languageOptions: {
}, {
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
globals: {
window: 'readonly',
document: 'readonly',
console: 'readonly',
localStorage: 'readonly',
navigator: 'readonly',
setTimeout: 'readonly',
File: 'readonly',
Element: 'readonly',
HTMLInputElement: 'readonly',
React: 'readonly',
ecmaFeatures: {
jsx: true,
},
},
rules: {
'no-unused-vars': 'warn',
globals: {
window: 'readonly',
document: 'readonly',
console: 'readonly',
localStorage: 'readonly',
navigator: 'readonly',
setTimeout: 'readonly',
File: 'readonly',
Element: 'readonly',
HTMLInputElement: 'readonly',
React: 'readonly',
},
},
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
globals: {
window: 'readonly',
document: 'readonly',
console: 'readonly',
localStorage: 'readonly',
navigator: 'readonly',
setTimeout: 'readonly',
File: 'readonly',
Element: 'readonly',
HTMLInputElement: 'readonly',
React: 'readonly',
},
},
plugins: {
'@typescript-eslint': typescript,
'react-refresh': reactRefresh,
},
rules: {
...typescript.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': 'warn',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
plugins: {
'@typescript-eslint': typescript,
'react-refresh': reactRefresh,
},
rules: {
...typescript.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': 'warn',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
];
}, ...storybook.configs["flat/recommended"]];
6 changes: 6 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
/>
</head>
<body class="bg-dark-950 text-dark-100 antialiased" style="scroll-padding-top: 80px">
<a
href="#main-content"
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:px-4 focus:py-2 focus:bg-teal-600 focus:text-white focus:rounded-md focus:outline-none"
>
Skip to main content
</a>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
Loading
Loading