From ef06437f93e36178b4491531ba9235ec6c63faff Mon Sep 17 00:00:00 2001 From: yu-sugimoto Date: Sat, 7 Mar 2026 01:00:08 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=E3=82=BF=E3=83=96=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E8=A1=A8=E7=A4=BA=E6=9D=A1=E4=BB=B6=E3=82=92?= =?UTF-8?q?=E8=A4=87=E6=95=B0=E3=82=B8=E3=83=A7=E3=83=96=E3=81=8B=E3=82=89?= =?UTF-8?q?=E4=BB=BB=E6=84=8F=E3=81=AE=E3=82=B8=E3=83=A7=E3=83=96=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LogPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/LogPanel.tsx b/frontend/src/components/LogPanel.tsx index 7d292dc..6fba2c4 100644 --- a/frontend/src/components/LogPanel.tsx +++ b/frontend/src/components/LogPanel.tsx @@ -222,8 +222,8 @@ export default function LogPanel({ logState, defaultCollapsed }: LogPanelProps) {/* Expanded content */} {expanded && (
- {/* Tab bar (show only if multiple jobs) */} - {jobKeys.length > 1 && ( + {/* Tab bar */} + {jobKeys.length > 0 && (
Date: Sat, 7 Mar 2026 03:40:59 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=E3=82=A4=E3=83=86=E3=83=AC?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AB=E3=83=87=E3=83=90?= =?UTF-8?q?=E3=82=A4=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=97=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/model/session.py | 6 +++ backend/app/router/sessions.py | 1 + backend/app/schema/session_schema.py | 1 + backend/app/usecase/session_usecase.py | 1 + ...5f6g7h8i9_add_device_type_to_iterations.py | 26 +++++++++++ frontend/src/pages/SessionDetail.tsx | 44 +++++++++++-------- frontend/src/services/api.ts | 1 + 7 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 backend/migration/versions/d4e5f6g7h8i9_add_device_type_to_iterations.py diff --git a/backend/app/model/session.py b/backend/app/model/session.py index dc553d9..5b93141 100644 --- a/backend/app/model/session.py +++ b/backend/app/model/session.py @@ -39,6 +39,11 @@ class ProposalStatus(enum.StrEnum): FAILED = "failed" +class DeviceType(enum.StrEnum): + DESKTOP = "desktop" + MOBILE = "mobile" + + def _utcnow() -> datetime: return datetime.now(UTC) @@ -84,6 +89,7 @@ class Iteration(Base): default=IterationStatus.PENDING, ) before_screenshot_key: Mapped[str | None] = mapped_column(String(500), nullable=True) + device_type: Mapped[DeviceType | None] = mapped_column(String(20), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) k8s_analyzer_job_name: Mapped[str | None] = mapped_column(String(200), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1) diff --git a/backend/app/router/sessions.py b/backend/app/router/sessions.py index c2e5718..a4d06a9 100644 --- a/backend/app/router/sessions.py +++ b/backend/app/router/sessions.py @@ -83,6 +83,7 @@ def _to_iteration_response(iteration: Iteration, session_id: UUID) -> IterationR if iteration.before_screenshot_key else None ), + device_type=iteration.device_type, error_message=iteration.error_message, proposals=proposals, created_at=iteration.created_at, diff --git a/backend/app/schema/session_schema.py b/backend/app/schema/session_schema.py index dc000f4..0d8f09d 100644 --- a/backend/app/schema/session_schema.py +++ b/backend/app/schema/session_schema.py @@ -50,6 +50,7 @@ class IterationResponse(BaseModel): selected_proposal_index: int | None = None status: str before_screenshot_url: str | None = None + device_type: str | None = None error_message: str | None = None proposals: list[ProposalResponse] = [] created_at: datetime diff --git a/backend/app/usecase/session_usecase.py b/backend/app/usecase/session_usecase.py index 04f31a4..0e4862b 100644 --- a/backend/app/usecase/session_usecase.py +++ b/backend/app/usecase/session_usecase.py @@ -333,6 +333,7 @@ async def _run_session_analysis( UUID(iteration_id), IterationStatus.ANALYZED, before_screenshot_key=result.get("before_screenshot_key"), + device_type=device_type, ) logger.info( "Session %s iter %d analysis done: %d proposals (device: %s)", diff --git a/backend/migration/versions/d4e5f6g7h8i9_add_device_type_to_iterations.py b/backend/migration/versions/d4e5f6g7h8i9_add_device_type_to_iterations.py new file mode 100644 index 0000000..e116915 --- /dev/null +++ b/backend/migration/versions/d4e5f6g7h8i9_add_device_type_to_iterations.py @@ -0,0 +1,26 @@ +"""add device_type to iterations + +Revision ID: d4e5f6g7h8i9 +Revises: c3d4e5f6g7h8 +Create Date: 2026-03-07 12:00:00.000000 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "d4e5f6g7h8i9" +down_revision: Union[str, None] = "c3d4e5f6g7h8" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column("iterations", sa.Column("device_type", sa.String(length=20), nullable=True)) + + +def downgrade() -> None: + op.drop_column("iterations", "device_type") diff --git a/frontend/src/pages/SessionDetail.tsx b/frontend/src/pages/SessionDetail.tsx index eb0dc20..c69d9fe 100644 --- a/frontend/src/pages/SessionDetail.tsx +++ b/frontend/src/pages/SessionDetail.tsx @@ -141,6 +141,8 @@ export default function SessionDetail() { const iterationStatus = latestIteration?.status ?? 'pending' const isInProgress = ['pending', 'analyzing', 'implementing'].includes(iterationStatus) + const isMobile = latestIteration?.device_type === 'mobile' + const cardMinWidth = isMobile ? '240px' : '400px' const completedProposals = latestIteration?.proposals.filter((p) => p.status === 'completed' && p.after_screenshot_url) ?? [] @@ -244,24 +246,6 @@ export default function SessionDetail() { {/* Completed: show proposals */} {iterationStatus === 'completed' && latestIteration && ( <> - {latestIteration.before_screenshot_url && ( -
-

Before

-
- Before -
-
- )} - {completedProposals.length > 0 && (

@@ -270,10 +254,32 @@ export default function SessionDetail() {
+ {latestIteration.before_screenshot_url && ( +
+
+ Before +
+
+

+ Before +

+
+
+ )} {completedProposals.map((proposal) => ( Date: Sat, 7 Mar 2026 03:41:30 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20Playwright=20MCP=E3=81=AE=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92=E5=9B=BA=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/worker-analyze.py | 16 ++++++++++------ docker/worker-implement.py | 16 ++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docker/worker-analyze.py b/docker/worker-analyze.py index 9342271..e4ae5ad 100644 --- a/docker/worker-analyze.py +++ b/docker/worker-analyze.py @@ -20,11 +20,11 @@ PLAYWRIGHT_MCP_SERVERS = { "playwright": { "command": "npx", - "args": ["@playwright/mcp@latest", "--headless", "--browser", "chromium"], + "args": ["@playwright/mcp@0.0.68", "--headless", "--browser", "chromium", "--viewport-size", "1280x800"], }, "playwright_mobile": { "command": "npx", - "args": ["@playwright/mcp@latest", "--headless", "--browser", "chromium", "--device", "iPhone 15"], + "args": ["@playwright/mcp@0.0.68", "--headless", "--browser", "chromium", "--device", "iPhone 15"], }, } @@ -68,8 +68,6 @@ def _emit_tool_detail(phase: str, tool_name: str, raw_input: str) -> None: elif tool_name == "Bash": cmd = params.get("command", "?") emit_log(phase, f"Running: {cmd[:100]}") - elif tool_name in ("Glob", "Grep"): - emit_log(phase, f"Searching: {params.get('pattern', '?')}") def get_s3_client(): @@ -153,7 +151,10 @@ async def _process_messages(client: ClaudeSDKClient, phase: str) -> None: if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): - emit_log(phase, block.text[:200], detail=block.text) + text = block.text.strip() + if text.startswith("Browser") or text.startswith("Searching"): + continue + emit_log(phase, text[:200], detail=block.text) elif isinstance(block, ToolUseBlock): _emit_tool_detail(phase, block.name, json.dumps(block.input)) @@ -369,7 +370,10 @@ async def generate_proposals( if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): - emit_log("analyzing", block.text[:200], detail=block.text) + text = block.text.strip() + if text.startswith("Browser") or text.startswith("Searching"): + continue + emit_log("analyzing", text[:200], detail=block.text) collected_text.append(block.text) elif isinstance(block, ToolUseBlock): _emit_tool_detail("analyzing", block.name, json.dumps(block.input)) diff --git a/docker/worker-implement.py b/docker/worker-implement.py index 7fb8b0b..d5633e0 100644 --- a/docker/worker-implement.py +++ b/docker/worker-implement.py @@ -20,11 +20,11 @@ PLAYWRIGHT_MCP_SERVERS = { "playwright": { "command": "npx", - "args": ["@playwright/mcp@latest", "--headless", "--browser", "chromium"], + "args": ["@playwright/mcp@0.0.68", "--headless", "--browser", "chromium", "--viewport-size", "1280x800"], }, "playwright_mobile": { "command": "npx", - "args": ["@playwright/mcp@latest", "--headless", "--browser", "chromium", "--device", "iPhone 15"], + "args": ["@playwright/mcp@0.0.68", "--headless", "--browser", "chromium", "--device", "iPhone 15"], }, } @@ -68,8 +68,6 @@ def _emit_tool_detail(phase: str, tool_name: str, raw_input: str) -> None: elif tool_name == "Bash": cmd = params.get("command", "?") emit_log(phase, f"Running: {cmd[:100]}") - elif tool_name in ("Glob", "Grep"): - emit_log(phase, f"Searching: {params.get('pattern', '?')}") def get_s3_client(): @@ -174,7 +172,10 @@ async def implement_changes(repo_dir: str, proposal_plan: str) -> None: if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): - emit_log("implementing", block.text[:200], detail=block.text) + text = block.text.strip() + if text.startswith("Browser") or text.startswith("Searching"): + continue + emit_log("implementing", text[:200], detail=block.text) elif isinstance(block, ToolUseBlock): _emit_tool_detail("implementing", block.name, json.dumps(block.input)) @@ -207,7 +208,10 @@ async def _process_messages(client: ClaudeSDKClient, phase: str) -> None: if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): - emit_log(phase, block.text[:200], detail=block.text) + text = block.text.strip() + if text.startswith("Browser") or text.startswith("Searching"): + continue + emit_log(phase, text[:200], detail=block.text) elif isinstance(block, ToolUseBlock): _emit_tool_detail(phase, block.name, json.dumps(block.input)) From 9f48a3403b3cff3019703c0b632f5dedf90659ec Mon Sep 17 00:00:00 2001 From: yu-sugimoto Date: Sat, 7 Mar 2026 03:49:13 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=E6=8F=90=E6=A1=88=E3=82=AB?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=A8=E3=82=BB=E3=83=83=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E8=A9=B3=E7=B4=B0=E3=81=AE=E3=82=BF=E3=82=A4=E3=83=88?= =?UTF-8?q?=E3=83=AB=E8=89=B2=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ProposalCard.tsx | 2 +- frontend/src/pages/SessionDetail.tsx | 29 ++++++++++++------------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/ProposalCard.tsx b/frontend/src/components/ProposalCard.tsx index 6eca2a0..6b4abbd 100644 --- a/frontend/src/components/ProposalCard.tsx +++ b/frontend/src/components/ProposalCard.tsx @@ -30,7 +30,7 @@ export default function ProposalCard({ proposal, selected, onToggle }: ProposalC style={{ width: '100%', display: 'block' }} />
-

+

#{proposal.proposal_index + 1}: {proposal.title}

diff --git a/frontend/src/pages/SessionDetail.tsx b/frontend/src/pages/SessionDetail.tsx index c69d9fe..67b52f6 100644 --- a/frontend/src/pages/SessionDetail.tsx +++ b/frontend/src/pages/SessionDetail.tsx @@ -259,22 +259,21 @@ export default function SessionDetail() { }} > {latestIteration.before_screenshot_url && ( -
-
- Before -
+
+ Before
-

+

Before

From 0b7b3aecd3eed7af7d31bb7aa237b05c16acb817 Mon Sep 17 00:00:00 2001 From: yu-sugimoto Date: Sat, 7 Mar 2026 04:05:07 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20PR=E4=BD=9C=E6=88=90=E3=83=9C?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=83=AD=E3=82=B8?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=82=92=E6=94=B9=E5=96=84=E3=81=97=E3=80=81?= =?UTF-8?q?=E7=B6=9A=E8=A1=8C=E5=85=A5=E5=8A=9B=E3=83=95=E3=82=A3=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/SessionDetail.tsx | 268 +++++++++++++-------------- 1 file changed, 126 insertions(+), 142 deletions(-) diff --git a/frontend/src/pages/SessionDetail.tsx b/frontend/src/pages/SessionDetail.tsx index 67b52f6..f78fb34 100644 --- a/frontend/src/pages/SessionDetail.tsx +++ b/frontend/src/pages/SessionDetail.tsx @@ -29,7 +29,6 @@ export default function SessionDetail() { const [prLoading, setPrLoading] = useState(false) const [prError, setPrError] = useState(null) const prPollRef = useRef | null>(null) - const [showContinueForm, setShowContinueForm] = useState(false) const [continueInstruction, setContinueInstruction] = useState('') const [continueLoading, setContinueLoading] = useState(false) const [continueError, setContinueError] = useState(null) @@ -37,7 +36,6 @@ export default function SessionDetail() { // Reset selection when session changes useEffect(() => { setSelectedIndex(null) - setShowContinueForm(false) setContinueInstruction('') }, [sessionId]) @@ -72,7 +70,6 @@ export default function SessionDetail() { await iterate(sessionId, selectedIndex, continueInstruction) refreshSessions() refetch() - setShowContinueForm(false) setContinueInstruction('') setSelectedIndex(null) } catch (e) { @@ -289,169 +286,156 @@ export default function SessionDetail() { ))}
- {selectedProposal && ( -
- {/* PR URL */} - {selectedProposal.pr_url && ( - - View PR - - )} - - {/* PR creating */} - {selectedProposal.pr_status === 'creating' && ( -
- Creating PR... -
- )} - - {/* PR failed */} - {selectedProposal.pr_status === 'failed' && ( -
-

- PR creation failed -

- -
- )} - - {/* Create PR button */} - {!selectedProposal.pr_status && !selectedProposal.pr_url && ( - - )} - - {prError && ( -

- {prError} -

- )} - - {/* Continue Refining */} -
- -
- - {showContinueForm && ( -
-