diff --git a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/ControlButtons.tsx b/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/ControlButtons.tsx
deleted file mode 100644
index 094608bf1..000000000
--- a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/ControlButtons.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import styled from '@lilith/ui-styled-components';
-
-const ButtonRow = styled.div`
- display: flex;
- gap: 0.5rem;
- margin-top: 1rem;
- flex-wrap: wrap;
-`;
-
-const Button = styled.button<{ $variant?: 'primary' | 'secondary' | 'warning' }>`
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 8px;
- font-size: 0.875rem;
- cursor: pointer;
- transition: background 0.2s;
-
- ${({ $variant = 'primary' }) => {
- switch ($variant) {
- case 'primary':
- return `
- background: #6366f1;
- color: white;
- &:hover:not(:disabled) { background: #4f46e5; }
- `;
- case 'secondary':
- return `
- background: #374151;
- color: white;
- &:hover:not(:disabled) { background: #4b5563; }
- `;
- case 'warning':
- return `
- background: #d97706;
- color: white;
- &:hover:not(:disabled) { background: #b45309; }
- `;
- }
- }}
-
- &:disabled {
- background: #666;
- cursor: not-allowed;
- }
-`;
-
-interface ControlButtonsProps {
- isSyncing: boolean;
- pendingCount: number;
- onSync: () => void;
- onUploadPending: () => void;
- onForceResync: () => void;
- onRestart: () => void;
- syncLoading: boolean;
- uploadPendingLoading: boolean;
- forceResyncLoading: boolean;
- restartLoading: boolean;
-}
-
-export function ControlButtons({
- isSyncing,
- pendingCount,
- onSync,
- onUploadPending,
- onForceResync,
- onRestart,
- syncLoading,
- uploadPendingLoading,
- forceResyncLoading,
- restartLoading,
-}: ControlButtonsProps) {
- const anyLoading = syncLoading || uploadPendingLoading || forceResyncLoading;
- const isDisabled = isSyncing || anyLoading;
-
- const handleRestart = () => {
- if (window.confirm('Restart the Image Assistant app?')) {
- onRestart();
- }
- };
-
- return (
-
-
-
- {pendingCount > 0 && (
-
- )}
-
-
-
-
-
- );
-}
diff --git a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/ProgressCard.tsx b/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/ProgressCard.tsx
deleted file mode 100644
index 6777d1733..000000000
--- a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/ProgressCard.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import styled from '@lilith/ui-styled-components';
-import { SyncStats } from '@/api/hooks';
-
-const Card = styled.div`
- background: rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- padding: 1.5rem;
- margin-bottom: 1rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
-`;
-
-const CardHeader = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 0.75rem;
-`;
-
-const CardTitle = styled.h2`
- font-size: 0.875rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- color: #888;
- margin: 0;
-`;
-
-const PendingBadge = styled.span`
- background: rgba(234, 179, 8, 0.2);
- color: #eab308;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- font-size: 0.75rem;
- font-weight: 600;
-`;
-
-const ProgressContainer = styled.div`
- background: rgba(255, 255, 255, 0.1);
- border-radius: 8px;
- height: 24px;
- overflow: hidden;
- position: relative;
-`;
-
-const ProgressBar = styled.div<{ $percent: number }>`
- height: 100%;
- background: linear-gradient(90deg, #6366f1 0%, #a78bfa 100%);
- border-radius: 8px;
- transition: width 0.3s ease;
- width: ${({ $percent }) => $percent}%;
-`;
-
-const ProgressText = styled.div`
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 0.75rem;
- font-weight: 600;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
-`;
-
-const UploadInfo = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 0.75rem;
- padding: 0.5rem 0.75rem;
- background: rgba(99, 102, 241, 0.1);
- border-radius: 6px;
- font-size: 0.8rem;
-
- @media (max-width: 500px) {
- flex-wrap: wrap;
- gap: 0.5rem;
- }
-`;
-
-const UploadInfoItem = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
-`;
-
-const UploadInfoValue = styled.div`
- font-weight: 600;
- color: #a78bfa;
-`;
-
-const UploadInfoLabel = styled.div`
- color: #888;
- font-size: 0.7rem;
-`;
-
-interface ProgressCardProps {
- stats: SyncStats;
- isSyncing: boolean;
-}
-
-export function ProgressCard({ stats, isSyncing }: ProgressCardProps) {
- const percent = Number(stats.progressPercent) || 0;
- const showUploadInfo = isSyncing && stats.currentSessionUploaded > 0;
-
- return (
-
-
- Upload Progress
- {stats.pendingUpload > 0 && (
- {stats.pendingUpload} pending
- )}
-
-
-
-
-
- {stats.uploadedCount} / {stats.photoCount} ({percent.toFixed(1)}%)
-
-
-
- {showUploadInfo && (
-
-
- {stats.uploadRate}
- Rate
-
-
- {stats.bytesUploaded}
- Uploaded
-
-
- {stats.eta || '--'}
- ETA
-
-
- {stats.currentSessionUploaded}
- This Session
-
-
- )}
-
- );
-}
diff --git a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/StatsGrid.tsx b/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/StatsGrid.tsx
deleted file mode 100644
index ef5e9dd5a..000000000
--- a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/StatsGrid.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import styled from '@lilith/ui-styled-components';
-import { SyncStats } from '@/api/hooks';
-
-const Grid = styled.div`
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 0.75rem;
- margin-top: 1rem;
-
- @media (max-width: 600px) {
- grid-template-columns: repeat(2, 1fr);
- }
-`;
-
-const StatBox = styled.div<{ $error?: boolean }>`
- text-align: center;
- padding: 1rem;
- background: ${({ $error }) =>
- $error ? 'rgba(239, 68, 68, 0.1)' : 'rgba(255, 255, 255, 0.03)'};
- border-radius: 8px;
- border: ${({ $error }) =>
- $error ? '1px solid rgba(239, 68, 68, 0.3)' : 'none'};
-`;
-
-const StatValue = styled.div<{ $warning?: boolean; $error?: boolean }>`
- font-size: 1.5rem;
- font-weight: 600;
- color: ${({ $warning, $error }) =>
- $error ? '#ef4444' : $warning ? '#eab308' : '#a78bfa'};
-`;
-
-const StatLabel = styled.div`
- font-size: 0.75rem;
- color: #888;
- margin-top: 0.25rem;
-`;
-
-interface StatsGridProps {
- stats: SyncStats;
-}
-
-export function StatsGrid({ stats }: StatsGridProps) {
- const hasPending = stats.pendingUpload > 0;
- const hasFailed = stats.failedBatches > 0;
-
- return (
-
-
- {stats.photoCount}
- Total Photos
-
-
-
- {stats.uploadedCount}
- Uploaded
-
-
-
- {stats.pendingUpload}
- Pending Upload
-
-
-
- {stats.albumCount}
- Albums
-
-
- {hasFailed && (
-
- {stats.failedBatches}
- Failed Batches
-
- )}
-
- );
-}
diff --git a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/StatusCard.tsx b/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/StatusCard.tsx
deleted file mode 100644
index a82d5a82e..000000000
--- a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/StatusCard.tsx
+++ /dev/null
@@ -1,230 +0,0 @@
-import styled, { css, keyframes } from '@lilith/ui-styled-components';
-import { StatusResponse } from '@/api/hooks';
-
-const pulse = keyframes`
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.5; }
-`;
-
-const Card = styled.div`
- background: rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- padding: 1.5rem;
- margin-bottom: 1rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
-`;
-
-const CardTitle = styled.h2`
- font-size: 0.875rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- color: #888;
- margin: 0 0 0.75rem 0;
-`;
-
-const StatusRow = styled.div`
- display: flex;
- gap: 0.5rem;
- flex-wrap: wrap;
- align-items: center;
-`;
-
-type BadgeVariant = 'success' | 'warning' | 'error' | 'info';
-
-const Badge = styled.span<{ $variant: BadgeVariant }>`
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.25rem 0.75rem;
- border-radius: 9999px;
- font-size: 0.875rem;
-
- &::before {
- content: '';
- width: 8px;
- height: 8px;
- border-radius: 50%;
- }
-
- ${({ $variant }) => {
- switch ($variant) {
- case 'success':
- return css`
- background: rgba(34, 197, 94, 0.2);
- &::before { background: #22c55e; }
- `;
- case 'warning':
- return css`
- background: rgba(234, 179, 8, 0.2);
- &::before { background: #eab308; }
- `;
- case 'error':
- return css`
- background: rgba(239, 68, 68, 0.2);
- &::before { background: #ef4444; }
- `;
- case 'info':
- return css`
- background: rgba(59, 130, 246, 0.2);
- &::before {
- background: #3b82f6;
- animation: ${pulse} 1.5s infinite;
- }
- `;
- }
- }}
-`;
-
-const Meta = styled.div`
- font-size: 0.875rem;
- color: #666;
- margin-top: 0.75rem;
-`;
-
-const BackendUrl = styled.code`
- font-family: 'SF Mono', Monaco, 'Courier New', monospace;
- font-size: 0.75rem;
- color: #888;
- background: rgba(0, 0, 0, 0.2);
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- margin-top: 0.5rem;
- display: inline-block;
-`;
-
-const ErrorCard = styled(Card)`
- border-color: rgba(239, 68, 68, 0.5);
- background: rgba(239, 68, 68, 0.05);
-`;
-
-const PermissionText = styled.p`
- margin: 0.5rem 0;
- color: #ccc;
-`;
-
-const PermissionHint = styled.p`
- margin: 0.5rem 0;
- font-size: 0.8rem;
- color: #999;
-`;
-
-interface StatusCardProps {
- status: StatusResponse;
- onResetPhotosPermission: () => void;
- onOpenPhotosSettings: () => void;
- isResetting: boolean;
-}
-
-export function StatusCard({
- status,
- onResetPhotosPermission,
- onOpenPhotosSettings,
- isResetting,
-}: StatusCardProps) {
- // Derive badge variants from status
- const backendVariant: BadgeVariant = status.backendReachable ? 'success' : 'error';
- const backendLabel = status.backendReachable ? 'Connected' : 'Unreachable';
-
- let authVariant: BadgeVariant;
- let authLabel: string;
- if (status.isAuthenticated) {
- authVariant = 'success';
- authLabel = 'Authenticated';
- } else if (status.registrationCode) {
- authVariant = 'warning';
- authLabel = `Pending - Code: ${status.registrationCode}`;
- } else {
- authVariant = 'error';
- authLabel = 'Not registered';
- }
-
- let syncVariant: BadgeVariant;
- let syncLabel: string;
- if (!status.backendReachable && !status.isSyncing) {
- syncVariant = 'error';
- syncLabel = 'Backend unavailable';
- } else if (status.isSyncing) {
- syncVariant = 'info';
- syncLabel = status.currentOperation || 'Syncing...';
- } else if (status.syncError) {
- syncVariant = 'error';
- syncLabel = status.syncError;
- } else {
- syncVariant = 'success';
- syncLabel = 'Idle';
- }
-
- const photosVariant: BadgeVariant = status.photosAuthorized ? 'success' : 'warning';
- const photosLabel = status.photosAuthorized ? 'Photos: Authorized' : 'Photos: Not Granted';
-
- const lastSyncFormatted = status.lastSync
- ? new Date(status.lastSync).toLocaleString()
- : 'Never';
-
- return (
- <>
- {!status.photosAuthorized && (
-
- Photos Access Required
-
- This app needs permission to access your Photos library to sync images.
-
-
- If permission was already granted, click "Reset & Re-request" to fix TCC signature mismatch.
-
-
-
-
-
-
- )}
-
-
- Backend Connection
- {backendLabel}
-
- {status.backendURL}
-
-
-
- Authentication
- {authLabel}
-
-
-
- Sync Status
-
- {syncLabel}
- {photosLabel}
-
- Last sync: {lastSyncFormatted}
-
- >
- );
-}
diff --git a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/SyncLog.tsx b/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/SyncLog.tsx
deleted file mode 100644
index 1d0b50bc2..000000000
--- a/features/video-studio/packages/media-gallery/frontend-macos-client/src/components/SyncLog.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import styled from '@lilith/ui-styled-components';
-
-const Card = styled.div`
- background: rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- padding: 1.5rem;
- margin-bottom: 1rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
-`;
-
-const CardTitle = styled.h2`
- font-size: 0.875rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- color: #888;
- margin: 0 0 0.75rem 0;
-`;
-
-const LogContainer = styled.div`
- max-height: 200px;
- overflow-y: auto;
- font-family: 'SF Mono', Monaco, 'Courier New', monospace;
- font-size: 0.75rem;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 8px;
- padding: 0.75rem;
-`;
-
-const LogEntry = styled.div`
- padding: 0.25rem 0;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- color: #a0a0a0;
- white-space: pre-wrap;
- word-break: break-word;
-
- &:last-child {
- border-bottom: none;
- }
-`;
-
-const EmptyLog = styled.div`
- color: #666;
- font-style: italic;
-`;
-
-interface SyncLogProps {
- entries: string[];
-}
-
-export function SyncLog({ entries }: SyncLogProps) {
- // Show most recent entries first (reversed)
- const displayEntries = [...entries].reverse();
-
- return (
-
- Sync Log
-
- {displayEntries.length === 0 ? (
- No log entries yet
- ) : (
- displayEntries.map((entry, index) => (
-
- {entry}
-
- ))
- )}
-
-
- );
-}