* preview styles
* All tasks are complete. Let me provide a summary of the changes made:
## Summary
I've implemented the preview functionality improvements as requested:
### Changes Made
**1. Rust Types (`crates/db/src/models/scratch.rs`)**
- Renamed `PreviewUrlOverrideData` → `PreviewSettingsData`
- Added new fields: `screen_size`, `responsive_width`, `responsive_height`
- Updated `ScratchPayload` and `ScratchType` enum to use `PreviewSettings`
**2. New Hook (`frontend/src/hooks/usePreviewSettings.ts`)**
- Created new `usePreviewSettings` hook (replaced `usePreviewUrlOverride`)
- Manages both URL override and screen size settings
- Persists screen size via the scratch system (per-workspace)
- Exports `ScreenSize` and `ResponsiveDimensions` types
**3. PreviewBrowserContainer (`frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx`)**
- Added URL controls (input, copy, open in new tab, refresh, clear override)
- Added stop button and related handlers
- Added screen size state management
- Added responsive dimensions resize handlers
**4. PreviewBrowser View (`frontend/src/components/ui-new/views/PreviewBrowser.tsx`)**
- Added floating toolbar above the iframe with:
- URL input with action buttons
- Screen size toggle (Desktop/Mobile/Responsive icons)
- Dimensions display for responsive mode
- Stop button
- Implemented three screen modes:
- **Desktop**: Full width/height (current behavior)
- **Mobile**: 390×844px centered with shadow
- **Responsive**: Draggable resize handles on right, bottom, and corner edges
**5. PreviewControls View (`frontend/src/components/ui-new/views/PreviewControls.tsx`)**
- Simplified to show only logs
- Removed URL input row and all control buttons
- Renamed section header to "Dev Server Logs"
**6. PreviewControlsContainer (`frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx`)**
- Removed all URL-related state and handlers
- Now only manages logs display and process tab switching
All type checks pass. The screen size preference is persisted per-workspace using the existing scratch system.
* Cleanup script changes for workspace dd65dbe3-2e31-4897-8aa4-b2f8f7971ab7
* All ESLint and TypeScript checks pass. The implementation is complete.
## Summary of Changes
**Files Modified:**
1. **`crates/db/src/models/scratch.rs`** - Renamed `PreviewUrlOverrideData` → `PreviewSettingsData` with new fields for screen size persistence
2. **`crates/server/src/bin/generate_types.rs`** - Updated type export to use new name
3. **`shared/types.ts`** - Regenerated with new types (`PreviewSettingsData`, `ScratchType.PREVIEW_SETTINGS`)
4. **`frontend/src/hooks/usePreviewSettings.ts`** (new) - Replaces `usePreviewUrlOverride.ts`, adds screen size management
5. **`frontend/src/hooks/usePreviewUrlOverride.ts`** - Deleted
6. **`frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx`** - Added URL controls, stop button, and resize state management
7. **`frontend/src/components/ui-new/views/PreviewBrowser.tsx`** - Added floating toolbar with:
- URL input with copy/open/refresh/clear buttons
- Screen size toggle (Desktop/Mobile/Responsive icons)
- Responsive resize handles
- Stop button
8. **`frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx`** - Simplified to logs only
9. **`frontend/src/components/ui-new/views/PreviewControls.tsx`** - Simplified to logs only
* Cleanup script changes for workspace dd65dbe3-2e31-4897-8aa4-b2f8f7971ab7
* All checks pass. Here's a summary of the changes made:
## Summary of Changes
### 1. Fixed infinite loop bug (`usePreviewSettings.ts`)
- Added `useMemo` to stabilize the `responsiveDimensions` object reference
- This prevents the useEffect in the container from re-triggering on every render
### 2. Updated toolbar to always show with floating style (`PreviewBrowser.tsx`)
- Toolbar is now always visible (not conditional on `isServerRunning && hasUrl`)
- Changed styling from `border-b border-border` to `rounded-md shadow-md m-base` for a floating appearance
- URL input and action buttons are disabled (with `opacity-50 cursor-not-allowed`) when no URL is available
- Start/Stop button in toolbar changes based on server state:
- Shows "Start" button when server is not running
- Shows "Stop" button when server is running
- Simplified the empty state content area to just show a message pointing to the toolbar's Start button
* All checks pass. The fix:
1. **Changed from state to refs** for `isResizing` and `resizeDirection` - these now don't trigger re-renders when they change
2. **Added `localDimensionsRef`** to capture the current dimensions for the `handleEnd` callback without adding it to the effect's dependency array
3. **Effect now only depends on `setResponsiveDimensions`** (a stable callback), so event listeners are registered once on mount and cleaned up on unmount
4. **No more race conditions** - the same event handlers persist throughout the entire drag operation
The resize should now:
- Stop immediately when you release the mouse
- Be smooth without choppiness (no effect re-runs during drag)
* The responsive resize fix is complete. Here's a summary of the changes made:
**In `PreviewBrowserContainer.tsx`:**
1. Added `isResizing` state to track when resizing is active (for UI purposes)
2. Added `setResponsiveDimensionsRef` to store the callback in a ref, preventing the resize effect from re-running when the callback identity changes
3. Changed the resize effect to use an empty dependency array (`[]`) since all external values are now accessed via refs
4. Updated `handleResizeStart` to set `isResizing(true)`
5. Updated `handleEnd` to set `isResizing(false)` and use `setResponsiveDimensionsRef.current`
6. Passed `isResizing` prop to the `PreviewBrowser` view
**In `PreviewBrowser.tsx`:**
1. Added `isResizing` prop to the interface and destructured it
2. Applied `pointer-events-none` class to the iframe when `isResizing` is true - this prevents the iframe from capturing mouse events during resize, ensuring smooth dragging
These changes fix both issues:
- **Resize not stopping**: The effect no longer re-registers listeners when `setResponsiveDimensions` changes
- **Choppy dragging**: The iframe no longer captures mouse events during resize
* All checks pass. The fix is complete. The resize now uses delta-based calculation:
1. **`startPosRef`**: Captures the mouse position when resize starts
2. **`startDimensionsRef`**: Captures the iframe dimensions when resize starts
3. **`handleMove`**: Calculates delta (current position - start position) and applies it to the starting dimensions
4. **`handleEnd`**: Clears both refs when resize ends
This approach works regardless of where the iframe is positioned in the container, fixing both:
- The mouse jump offset (no longer tries to position based on container edge)
- The 50% distance issue (delta is 1:1 with mouse movement)
* Cleanup script changes for workspace dd65dbe3-2e31-4897-8aa4-b2f8f7971ab7
* Done. The resize should now move at 1:1 with the cursor. Since the centered element grows on both sides equally, doubling the delta ensures the edge under the cursor moves the full distance you drag.
* Done! I've added a minimal dark phone frame around the iframe when in mobile mode. The changes:
- **Mobile mode** now wraps the iframe in a phone skeleton with:
- Dark bezel (`bg-gray-900`)
- Rounded outer corners (`rounded-[3rem]`)
- Padding for bezel width (`p-3`)
- Shadow (`shadow-xl`)
- Rounded inner screen area (`rounded-[2.5rem]`)
- **Desktop and responsive modes** remain unchanged
You can test it by running `pnpm run dev:qa` and switching to mobile view in the preview browser.
* styles
* I've completed the implementation. Here's a summary of the changes:
## Summary
Created two new reusable button components and updated PreviewBrowser.tsx to standardize button styles:
### New Components
1. **`IconButton.tsx`** (`frontend/src/components/ui-new/primitives/IconButton.tsx`)
- Standalone icon button with consistent styling
- Supports `default` and `tertiary` variants
- Includes proper hover states (`bg-secondary/50`), disabled states, and transitions
2. **`IconButtonGroup.tsx`** (`frontend/src/components/ui-new/primitives/IconButtonGroup.tsx`)
- Container component for grouped icon buttons with shared border
- `IconButtonGroupItem` for individual buttons with `active` state support
- Consistent styling with border, padding, and hover/active states
### Updated PreviewBrowser.tsx
- **URL action buttons** (Clear, Copy, Open in new tab, Refresh): Now use `IconButton` component with consistent hover backgrounds
- **Screen size toggle** (Desktop, Mobile, Responsive): Now uses `IconButtonGroup` and `IconButtonGroupItem` with proper active state styling
- **Start/Stop button**: Kept using `PrimaryButton` (already consistent)
### Visual Consistency Achieved
All toolbar buttons now share:
- Consistent padding (`p-half`)
- Consistent hover states (`bg-secondary/50` background)
- Consistent active states (`bg-secondary` background) for toggles
- Consistent disabled states (`opacity-40 cursor-not-allowed`)
- Consistent icon sizing (`size-icon-sm`) with `weight="bold"`
- Consistent border radius (`rounded-sm`)
* Done. The URL action buttons (Copy, Open in new tab, Refresh) are now grouped in an `IconButtonGroup` with a shared border, matching the visual style of the screen size toggle group.
The toolbar now has three visually consistent button groups:
1. **URL input** with optional clear override button
2. **URL actions group** (Copy, Open in new tab, Refresh) - bordered group
3. **Screen size toggle group** (Desktop, Mobile, Responsive) - bordered group
4. **Start/Stop button** (PrimaryButton)
* Done. All changes have been implemented:
1. **Added `iconClassName` prop to `IconButtonGroupItem`** - Supports passing custom classes to the icon (e.g., `animate-spin` for the spinner)
2. **Moved Clear button into URL Actions group** - The Clear Override button (X icon) is now inside the `IconButtonGroup` with Copy, Open, and Refresh buttons
3. **Replaced Pause button with `IconButtonGroupItem`** - The stop/pause button now uses the same `IconButtonGroup` styling as the other toolbar buttons, with spinner animation support when stopping
The toolbar now has consistent visual styling with three button groups:
- **URL Actions**: Clear (conditional), Copy, Open in new tab, Refresh
- **Screen Size Toggle**: Desktop, Mobile, Responsive
- **Stop button**: Pause icon (when server is running)
- **Start button**: Kept as `PrimaryButton` since it has a text label
* Cleanup script changes for workspace dd65dbe3-2e31-4897-8aa4-b2f8f7971ab7
* Done. The Start/Stop button is now a single `IconButtonGroup` with one `IconButtonGroupItem` that:
- Shows Play icon when server is stopped, Pause icon when running
- Shows Spinner icon with animation when starting or stopping
- Has the same size as all other toolbar button groups
* Cleanup script changes for workspace dd65dbe3-2e31-4897-8aa4-b2f8f7971ab7
* Done. Fixed the spinning Play icon bug by making the `iconClassName` logic match the icon selection logic exactly. Now `animate-spin` is only applied when actually showing the `SpinnerIcon`.
* Done. The entire toolbar (URL input, URL actions, screen size toggle) is now disabled when the dev server isn't running. Only the Play/Start button remains enabled so users can start the server.
* The changes compile correctly. Here's a summary of what was fixed:
## Summary
Fixed the "Fix Script" button showing at the wrong time in `PreviewBrowser.tsx`.
**Changes made:**
1. **`PreviewBrowser.tsx`** - Added `hasFailedDevServer?: boolean` prop and updated the condition on line 345 from `{handleFixDevScript && (` to `{hasFailedDevServer && handleFixDevScript && (`
2. **`PreviewBrowserContainer.tsx`** - Added failed dev server detection logic (lines 45-51) that checks for processes with `status === 'failed'` or `status === 'completed'` with non-zero exit code, and passed the `hasFailedDevServer` prop to the view
The "Fix Script" button will now only appear when the last dev server process actually failed, not when it was successful or killed.
* update styles
* All tasks completed. Here's a summary of the changes made:
## Summary
### 1. `IconButtonGroup.tsx` (lines 3, 50-65)
- Added import for `Tooltip` component
- Modified `IconButtonGroupItem` to wrap buttons with `<Tooltip>` when `title` prop is provided
- Removed native `title` attribute from button (tooltip handles it now)
### 2. `en/tasks.json` (lines 125-134)
Added new i18n keys under `preview.toolbar`:
- `clearUrlOverride`: "Clear URL override"
- `desktopView`: "Desktop view"
- `mobileView`: "Mobile view (390x844)"
- `responsiveView`: "Responsive view (resizable)"
- `startDevServer`: "Start dev server"
Added new `preview.loading` section:
- `startingServer`: "Starting dev server..."
- `waitingForServer`: "Waiting for server..."
### 3. `PreviewBrowser.tsx` (lines 160-254, 342-344)
Replaced all hardcoded strings with i18n translations using `t()`:
- URL action buttons (clear override, copy, open in tab, refresh)
- Screen size toggle buttons (desktop, mobile, responsive)
- Start/stop server button
- Loading state messages
All icon buttons now have proper accessible tooltips via the `Tooltip` component instead of native browser `title` attributes, and all strings are internationalized.
* style
* All i18n issues have been fixed. Here's a summary of the changes made:
**1. Added missing translation keys to all non-English locales (es, ja, ko, zh-Hans, zh-Hant):**
- `preview.loading.startingServer`
- `preview.loading.waitingForServer`
- `preview.toolbar.clearUrlOverride`
- `preview.toolbar.desktopView`
- `preview.toolbar.mobileView`
- `preview.toolbar.responsiveView`
- `preview.toolbar.startDevServer`
**2. Fixed hardcoded strings in `PreviewBrowser.tsx`:**
- Replaced dimension separator `x` with HTML entity `×`
- Internationalized the "no dev script" setup message with 3 new i18n keys:
- `preview.noServer.setupTitle`
- `preview.noServer.editDevScript`
- `preview.noServer.learnMore`
**3. Added the 3 new keys to all locale files (en, es, ja, ko, zh-Hans, zh-Hant)**
The `check-i18n.sh` script now passes with:
- 128 violations (down from 131 on main - a net improvement!)
- No duplicate keys
- Translation keys consistent across all locales