Skip to content
Merged
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
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,46 @@

- name: Build
run: pnpm build

# Upload build artifacts for Lighthouse CI job
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 1

lighthouse:
runs-on: ubuntu-latest
needs: ci

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install Lighthouse CI
run: npm install -g @lhci/cli@0.14.x

- name: Run Lighthouse CI
run: lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

- name: Upload Lighthouse report
uses: actions/upload-artifact@v4
if: always()
with:
name: lighthouse-report
path: .lighthouseci
retention-days: 7
Comment on lines +49 to +81

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 declare an explicit permissions block in the workflow so that the GITHUB_TOKEN has only the minimal rights needed. For this CI workflow, both jobs only need to read the repository contents (for checkout) and do not need to write to the repo, issues, or pull requests. Artifact upload/download uses the workflow’s internal permissions and does not require repository write scopes. Therefore we can safely set contents: read at the workflow level, which will apply to both ci and lighthouse jobs.

The best fix with minimal functional change is to add a root-level permissions key after the name: CI line and before the on: block in .github/workflows/ci.yml:

  • Add:
    permissions:
      contents: read
    at the top level of the workflow.
  • No changes to the job steps are required.
  • No imports or additional methods are needed since this is purely a YAML configuration change.
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.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pnpm-debug.log*
# Test files
test-*.html
test-*.mjs
*-test.png
*-screenshot.png
test-*.js
preview-*.mjs
launch-*.mjs
Expand All @@ -35,6 +37,9 @@ test-results-*.md
*.svg
svg-preview.html

# Lighthouse CI
.lighthouseci/

# Playwright
playwright-report/
test-results/
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
35 changes: 35 additions & 0 deletions lighthouserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"ci": {
"collect": {
"staticDistDir": "./dist",
"numberOfRuns": 3,
"url": ["http://localhost/"],
"settings": {
"preset": "desktop",
"skipAudits": ["uses-http2", "redirects-http"]
}
},
"assert": {
"assertions": {
"first-contentful-paint": ["warn", { "maxNumericValue": 2000 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["warn", { "maxNumericValue": 300 }],
"speed-index": ["warn", { "maxNumericValue": 3400 }],
"categories:performance": ["warn", { "minScore": 0.9 }],
"categories:accessibility": ["error", { "minScore": 0.9 }],
"categories:best-practices": ["warn", { "minScore": 0.9 }],
"categories:seo": ["warn", { "minScore": 0.9 }],
"uses-text-compression": "off",
"uses-responsive-images": "warn",
"offscreen-images": "warn",
"unused-javascript": "warn",
"unused-css-rules": "warn",
"render-blocking-resources": "warn"
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
20 changes: 18 additions & 2 deletions src/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,26 @@
}
</script>

<!-- Geist Font (Vercel) -->
<!-- DNS Prefetch for external resources -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://fonts.gstatic.com" />
<link rel="dns-prefetch" href="https://static.cloudflareinsights.com" />

<!-- Preconnect for fonts (critical) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;600;700&display=swap" rel="stylesheet" />

<!-- Non-blocking font loading with preload + async pattern -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;600;700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;600;700&display=swap" media="print" onload="this.media='all'" />
<noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;600;700&display=swap" />
</noscript>

<!-- Critical CSS for initial render (prevents FOUT) -->
<style>
body { font-family: 'Geist', system-ui, -apple-system, sans-serif; }
</style>

<!-- Additional SEO Meta Tags -->
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
Expand Down
Binary file removed ui-screenshot.png
Binary file not shown.
Binary file removed visual-test.png
Binary file not shown.
14 changes: 12 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ export default defineConfig({
build: {
outDir: '../../dist',
emptyOutDir: true,
// Target modern browsers for smaller bundles
target: 'es2022',
// Enable CSS code splitting
cssCodeSplit: true,
// Optimize chunk size
chunkSizeWarningLimit: 500,
rollupOptions: {
output: {
manualChunks: {
// Core React runtime
'react-vendor': ['react', 'react-dom'],
// Routing
'router': ['react-router-dom'],
router: ['react-router-dom'],
// Icons (large library, lazy load when needed)
icons: ['lucide-react'],
// UI primitives (Radix UI)
'radix-ui': [
'@radix-ui/react-checkbox',
Expand All @@ -28,7 +36,9 @@ export default defineConfig({
'@radix-ui/react-tooltip',
],
// Internationalization
'i18n': ['i18next', 'react-i18next'],
i18n: ['i18next', 'react-i18next'],
// Utilities
utils: ['clsx', 'tailwind-merge', 'class-variance-authority'],
},
},
},
Expand Down
Loading