From 0318372525075c3e3f7f7b69826333409d6603d9 Mon Sep 17 00:00:00 2001 From: yu-sugimoto Date: Tue, 24 Feb 2026 00:02:54 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20CI=20=E3=83=AF=E3=83=BC=E3=82=AF?= =?UTF-8?q?=E3=83=95=E3=83=AD=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check.yml | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..15e0449 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,104 @@ +name: Check + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + frontend-lint: + name: "Frontend: Lint" + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + - run: npm ci + - run: npm run lint + + frontend-format: + name: "Frontend: Format" + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + - run: npm ci + - run: npm run format:check + + frontend-typecheck: + name: "Frontend: Type Check" + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + - run: npm ci + - run: npm run type-check + + backend-lint: + name: "Backend: Lint" + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + - run: uv run ruff check . + + backend-format: + name: "Backend: Format" + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + - run: uv run ruff format --check . + + backend-typecheck: + name: "Backend: Type Check" + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + - run: uv run mypy . + + backend-test: + name: "Backend: Test" + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra test + - run: uv run pytest From 3b753dae31a10393a42e8802f13ba202475edcad Mon Sep 17 00:00:00 2001 From: yu-sugimoto Date: Tue, 24 Feb 2026 00:07:34 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20Prettier=20=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.prettierignore | 3 +++ frontend/.prettierrc | 7 +++++++ frontend/package-lock.json | 17 +++++++++++++++++ frontend/package.json | 4 ++++ 4 files changed, 31 insertions(+) create mode 100644 frontend/.prettierignore create mode 100644 frontend/.prettierrc diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..91a3983 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,3 @@ +dist +node_modules +package-lock.json diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..86c151f --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 2, + "printWidth": 100 +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4c68124..7e685f3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "prettier": "^3.8.1", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", "vite": "^7.3.1" @@ -2831,6 +2832,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4e18d32..29dc9d8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,9 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", + "type-check": "tsc -b", + "format:check": "prettier --check .", + "format": "prettier --write .", "preview": "vite preview" }, "dependencies": { @@ -24,6 +27,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "prettier": "^3.8.1", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", "vite": "^7.3.1" From 6cd909d1ee80f378f0de6dfb911c25ad7a879cdd Mon Sep 17 00:00:00 2001 From: yu-sugimoto Date: Tue, 24 Feb 2026 00:08:16 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=E3=82=B3=E3=83=BC=E3=83=89=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=AB=E3=81=AE=E7=B5=B1=E4=B8=80=E3=81=A8?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E6=94=B9=E8=A1=8C=E3=81=AE=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/README.md | 6 +- frontend/src/App.tsx | 8 +- frontend/src/components/DiffViewer.tsx | 27 +++-- frontend/src/components/ProposalCard.tsx | 20 ++-- .../src/components/ScreenshotComparison.tsx | 15 +-- frontend/src/components/StatusBadge.tsx | 10 +- frontend/src/hooks/useJobPolling.ts | 71 ++++++------ frontend/src/pages/About.tsx | 8 +- frontend/src/pages/Dashboard.tsx | 62 ++++++----- frontend/src/pages/ErrorPage.tsx | 18 +-- frontend/src/pages/Home.tsx | 26 ++--- frontend/src/services/api.ts | 104 ++++++++---------- frontend/tsconfig.json | 5 +- frontend/vite.config.ts | 6 +- 14 files changed, 189 insertions(+), 197 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 6813d90..ef6ce59 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,6 +1,7 @@ # UI Recommender Frontend ## セットアップ + ```bash # 依存パッケージのインストール npm install @@ -8,9 +9,11 @@ npm install # 開発サーバー起動 npm run dev ``` + 開発サーバーが http://localhost:5173 で起動します。 ## 利用可能なコマンド + ```bash # 開発サーバー起動 npm run dev @@ -26,6 +29,7 @@ npm run lint ``` ## 技術スタック + - React 19 - TypeScript -- Vite \ No newline at end of file +- Vite diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3d7ded3..bdd9af2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,16 +18,12 @@ function App() {

Vite + React

- +

Edit src/App.tsx and save to test HMR

-

- Click on the Vite and React logos to learn more -

+

Click on the Vite and React logos to learn more

) } diff --git a/frontend/src/components/DiffViewer.tsx b/frontend/src/components/DiffViewer.tsx index b080e82..abb1d47 100644 --- a/frontend/src/components/DiffViewer.tsx +++ b/frontend/src/components/DiffViewer.tsx @@ -1,7 +1,7 @@ type DiffViewerProps = { - diff: string; - onClose: () => void; -}; + diff: string + onClose: () => void +} export default function DiffViewer({ diff, onClose }: DiffViewerProps) { return ( @@ -32,7 +32,14 @@ export default function DiffViewer({ diff, onClose }: DiffViewerProps) { }} onClick={(e) => e.stopPropagation()} > -
+

Changes Diff

- ); + ) } diff --git a/frontend/src/components/ProposalCard.tsx b/frontend/src/components/ProposalCard.tsx index 939b0d2..6eca2a0 100644 --- a/frontend/src/components/ProposalCard.tsx +++ b/frontend/src/components/ProposalCard.tsx @@ -1,18 +1,14 @@ -import type { Proposal } from '../services/api'; +import type { Proposal } from '../services/api' type ProposalCardProps = { - proposal: Proposal; - selected: boolean; - onToggle: (index: number) => void; -}; + proposal: Proposal + selected: boolean + onToggle: (index: number) => void +} -export default function ProposalCard({ - proposal, - selected, - onToggle, -}: ProposalCardProps) { +export default function ProposalCard({ proposal, selected, onToggle }: ProposalCardProps) { if (proposal.status !== 'completed' || !proposal.after_screenshot_url) { - return null; + return null } return ( @@ -39,5 +35,5 @@ export default function ProposalCard({ - ); + ) } diff --git a/frontend/src/components/ScreenshotComparison.tsx b/frontend/src/components/ScreenshotComparison.tsx index 532efc4..c06e7ad 100644 --- a/frontend/src/components/ScreenshotComparison.tsx +++ b/frontend/src/components/ScreenshotComparison.tsx @@ -1,13 +1,10 @@ type ScreenshotComparisonProps = { - beforeUrl: string | null; - afterUrl: string | null; -}; + beforeUrl: string | null + afterUrl: string | null +} -export default function ScreenshotComparison({ - beforeUrl, - afterUrl, -}: ScreenshotComparisonProps) { - if (!beforeUrl && !afterUrl) return null; +export default function ScreenshotComparison({ beforeUrl, afterUrl }: ScreenshotComparisonProps) { + if (!beforeUrl && !afterUrl) return null return (
@@ -40,5 +37,5 @@ export default function ScreenshotComparison({
)} - ); + ) } diff --git a/frontend/src/components/StatusBadge.tsx b/frontend/src/components/StatusBadge.tsx index 7e6970e..00e7cdd 100644 --- a/frontend/src/components/StatusBadge.tsx +++ b/frontend/src/components/StatusBadge.tsx @@ -1,6 +1,6 @@ type StatusBadgeProps = { - status: string; -}; + status: string +} const STATUS_STYLES: Record = { pending: { bg: '#e5e7eb', text: '#374151', label: 'Pending' }, @@ -9,10 +9,10 @@ const STATUS_STYLES: Record implementing: { bg: '#fef3c7', text: '#92400e', label: 'Implementing' }, completed: { bg: '#d1fae5', text: '#065f46', label: 'Completed' }, failed: { bg: '#fee2e2', text: '#991b1b', label: 'Failed' }, -}; +} export default function StatusBadge({ status }: StatusBadgeProps) { - const style = STATUS_STYLES[status] || STATUS_STYLES.pending; + const style = STATUS_STYLES[status] || STATUS_STYLES.pending return ( {style.label} - ); + ) } diff --git a/frontend/src/hooks/useJobPolling.ts b/frontend/src/hooks/useJobPolling.ts index 3a85749..df2cef7 100644 --- a/frontend/src/hooks/useJobPolling.ts +++ b/frontend/src/hooks/useJobPolling.ts @@ -1,63 +1,62 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; -import { getJob, type Job } from '../services/api'; +import { useState, useEffect, useRef, useCallback } from 'react' +import { getJob, type Job } from '../services/api' -const TERMINAL_STATUSES = ['analyzed', 'completed', 'failed']; +const TERMINAL_STATUSES = ['analyzed', 'completed', 'failed'] -export function useJobPolling( - jobId: string | null, - intervalMs: number = 3000, -) { - const [job, setJob] = useState(null); - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const timerRef = useRef(null); +export function useJobPolling(jobId: string | null, intervalMs: number = 3000) { + const [job, setJob] = useState(null) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const timerRef = useRef(null) const stopPolling = useCallback(() => { if (timerRef.current !== null) { - clearInterval(timerRef.current); - timerRef.current = null; + clearInterval(timerRef.current) + timerRef.current = null } - }, []); + }, []) useEffect(() => { if (!jobId) { - setJob(null); - return; + return } - setIsLoading(true); - setError(null); - + let isFirstPoll = true const poll = async () => { + if (isFirstPoll) { + setIsLoading(true) + setError(null) + isFirstPoll = false + } try { - const data = await getJob(jobId); - setJob(data); - setIsLoading(false); + const data = await getJob(jobId) + setJob(data) + setIsLoading(false) if (TERMINAL_STATUSES.includes(data.status)) { - stopPolling(); + stopPolling() } } catch (e) { - setError((e as Error).message); - setIsLoading(false); + setError((e as Error).message) + setIsLoading(false) } - }; + } - poll(); - timerRef.current = window.setInterval(poll, intervalMs); + poll() + timerRef.current = window.setInterval(poll, intervalMs) - return stopPolling; - }, [jobId, intervalMs, stopPolling]); + return stopPolling + }, [jobId, intervalMs, stopPolling]) const refetch = useCallback(async () => { - if (!jobId) return; + if (!jobId) return try { - const data = await getJob(jobId); - setJob(data); + const data = await getJob(jobId) + setJob(data) } catch (e) { - setError((e as Error).message); + setError((e as Error).message) } - }, [jobId]); + }, [jobId]) - return { job, error, isLoading, refetch }; + return { job, error, isLoading, refetch } } diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx index 2d2024c..c4d4740 100644 --- a/frontend/src/pages/About.tsx +++ b/frontend/src/pages/About.tsx @@ -1,4 +1,4 @@ -import { Link } from 'react-router-dom'; +import { Link } from 'react-router-dom' export default function About() { return ( @@ -6,8 +6,10 @@ export default function About() {

About Page

This is an example page using React Router v7.

- Go back to Home + + Go back to Home +
- ); + ) } diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 82eca67..bf39e4f 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,39 +1,39 @@ -import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { createJob, listJobs, type Job } from '../services/api'; -import StatusBadge from '../components/StatusBadge'; +import { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { createJob, listJobs, type Job } from '../services/api' +import StatusBadge from '../components/StatusBadge' export default function Dashboard() { - const navigate = useNavigate(); - const [repoUrl, setRepoUrl] = useState(''); - const [branch, setBranch] = useState('main'); - const [instruction, setInstruction] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); - const [submitError, setSubmitError] = useState(null); - const [jobs, setJobs] = useState([]); + const navigate = useNavigate() + const [repoUrl, setRepoUrl] = useState('') + const [branch, setBranch] = useState('main') + const [instruction, setInstruction] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + const [submitError, setSubmitError] = useState(null) + const [jobs, setJobs] = useState([]) useEffect(() => { listJobs() .then(setJobs) - .catch(() => {}); - }, []); + .catch(() => {}) + }, []) const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!repoUrl || !instruction) return; + e.preventDefault() + if (!repoUrl || !instruction) return - setIsSubmitting(true); - setSubmitError(null); + setIsSubmitting(true) + setSubmitError(null) try { - const job = await createJob({ repo_url: repoUrl, branch, instruction }); - navigate(`/jobs/${job.id}`); + const job = await createJob({ repo_url: repoUrl, branch, instruction }) + navigate(`/jobs/${job.id}`) } catch (err) { - setSubmitError((err as Error).message); + setSubmitError((err as Error).message) } finally { - setIsSubmitting(false); + setIsSubmitting(false) } - }; + } return (
@@ -41,7 +41,9 @@ export default function Dashboard() {
-