Преглед на файлове

Merge branch '0.2.1b' into feature/use_remaining_filament

MartinNYHC преди 3 месеца
родител
ревизия
1c068d2a2f

+ 1 - 0
CHANGELOG.md

@@ -19,6 +19,7 @@ All notable changes to Bambuddy will be documented in this file.
 
 
 - **Usage Tracking Wrong Spool on Dual-Nozzle / Multi-AMS Printers** ([#364](https://github.com/maziggy/bambuddy/issues/364)) — On H2C, H2D Pro, and other dual-nozzle printers with multiple AMS units, the usage tracker attributed filament consumption to the wrong spools. The MQTT `mapping` field — a per-print array that maps slicer filament slots to physical AMS trays — was preserved in state but never parsed or used. The tracker fell back to `slot_id - 1` as the global tray ID, which is incorrect when AMS hardware IDs differ from sequential indices (e.g., AMS-HT units with ID 128). Now decodes the MQTT mapping field from its snow encoding (`ams_hw_id * 256 + local_slot`) into bambuddy global tray IDs and uses it as a universal mapping source — working for all printer models and all print sources (slicer, queue, reprint) without relying on `tray_now` disambiguation.
 - **Usage Tracking Wrong Spool on Dual-Nozzle / Multi-AMS Printers** ([#364](https://github.com/maziggy/bambuddy/issues/364)) — On H2C, H2D Pro, and other dual-nozzle printers with multiple AMS units, the usage tracker attributed filament consumption to the wrong spools. The MQTT `mapping` field — a per-print array that maps slicer filament slots to physical AMS trays — was preserved in state but never parsed or used. The tracker fell back to `slot_id - 1` as the global tray ID, which is incorrect when AMS hardware IDs differ from sequential indices (e.g., AMS-HT units with ID 128). Now decodes the MQTT mapping field from its snow encoding (`ams_hw_id * 256 + local_slot`) into bambuddy global tray IDs and uses it as a universal mapping source — working for all printer models and all print sources (slicer, queue, reprint) without relying on `tray_now` disambiguation.
 - **npm audit: suppress moderate ajv ReDoS finding** — Added `audit-level=high` to `frontend/.npmrc` so `npm audit` exits cleanly. The ajv@6 ReDoS (GHSA-2g4f-4pwh-qvx6) is a transitive dependency of eslint@9 with no patched v6 release; ajv@8 override breaks eslint. The vulnerability requires crafted `$data` schema input — not an attack vector in a linting config.
 - **npm audit: suppress moderate ajv ReDoS finding** — Added `audit-level=high` to `frontend/.npmrc` so `npm audit` exits cleanly. The ajv@6 ReDoS (GHSA-2g4f-4pwh-qvx6) is a transitive dependency of eslint@9 with no patched v6 release; ajv@8 override breaks eslint. The vulnerability requires crafted `$data` schema input — not an attack vector in a linting config.
+- **Spool Form Allows Empty Brand & Subtype** ([#417](https://github.com/maziggy/bambuddy/issues/417)) — The spool add/edit modal did not require Brand or Subtype fields, allowing spools to be saved without them. When such a spool was assigned to an AMS slot, the `tray_sub_brands` sent to the printer was incomplete (e.g., just "PETG" instead of "PETG Basic"), causing BambuStudio to not recognize the filament profile. Brand and Subtype are now mandatory fields with validation errors shown on submit.
 
 
 ### Changed
 ### Changed
 - **Filament Catalog API Renamed** ([#427](https://github.com/maziggy/bambuddy/issues/427)) — Renamed `/api/v1/filaments/` to `/api/v1/filament-catalog/` to avoid confusion with the inventory spools page (labeled "Filament" in the UI). The old endpoint managed material type definitions (cost, temperature, density), not physical spools — the shared name caused users to expect the API to return their spool inventory.
 - **Filament Catalog API Renamed** ([#427](https://github.com/maziggy/bambuddy/issues/427)) — Renamed `/api/v1/filaments/` to `/api/v1/filament-catalog/` to avoid confusion with the inventory spools page (labeled "Filament" in the UI). The old endpoint managed material type definitions (cost, temperature, density), not physical spools — the shared name caused users to expect the API to return their spool inventory.

+ 7 - 1
frontend/src/components/SpoolFormModal.tsx

@@ -320,7 +320,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration
     if (!validation.isValid) {
     if (!validation.isValid) {
       setErrors(validation.errors);
       setErrors(validation.errors);
       // Switch to filament tab if there are errors there
       // Switch to filament tab if there are errors there
-      if (validation.errors.slicer_filament || validation.errors.material) {
+      if (validation.errors.slicer_filament || validation.errors.material || validation.errors.brand || validation.errors.subtype) {
         setActiveTab('filament');
         setActiveTab('filament');
       }
       }
       return;
       return;
@@ -438,6 +438,12 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration
                 {errors.material && (
                 {errors.material && (
                   <p className="mt-1 text-xs text-red-400">{errors.material}</p>
                   <p className="mt-1 text-xs text-red-400">{errors.material}</p>
                 )}
                 )}
+                {errors.brand && (
+                  <p className="mt-1 text-xs text-red-400">{errors.brand}</p>
+                )}
+                {errors.subtype && (
+                  <p className="mt-1 text-xs text-red-400">{errors.subtype}</p>
+                )}
               </div>
               </div>
 
 
               {/* Color Section */}
               {/* Color Section */}

+ 2 - 2
frontend/src/components/spool-form/FilamentSection.tsx

@@ -161,7 +161,7 @@ export function FilamentSection({
 
 
       {/* Brand (dropdown with search) */}
       {/* Brand (dropdown with search) */}
       <div>
       <div>
-        <label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.brand')}</label>
+        <label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.brand')} *</label>
         <div className="relative" ref={brandRef}>
         <div className="relative" ref={brandRef}>
           <input
           <input
             type="text"
             type="text"
@@ -221,7 +221,7 @@ export function FilamentSection({
 
 
       {/* Variant / Subtype */}
       {/* Variant / Subtype */}
       <div>
       <div>
-        <label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.subtype')}</label>
+        <label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.subtype')} *</label>
         <div className="relative" ref={subtypeRef}>
         <div className="relative" ref={subtypeRef}>
           <input
           <input
             type="text"
             type="text"

+ 8 - 0
frontend/src/components/spool-form/types.ts

@@ -117,6 +117,14 @@ export function validateForm(formData: SpoolFormData): ValidationResult {
     errors.material = 'Material is required';
     errors.material = 'Material is required';
   }
   }
 
 
+  if (!formData.brand) {
+    errors.brand = 'Brand is required';
+  }
+
+  if (!formData.subtype) {
+    errors.subtype = 'Subtype is required';
+  }
+
   return {
   return {
     isValid: Object.keys(errors).length === 0,
     isValid: Object.keys(errors).length === 0,
     errors,
     errors,

+ 196 - 176
frontend/src/pages/QueuePage.tsx

@@ -361,26 +361,40 @@ function SortableQueueItem({
   const isPending = item.status === 'pending';
   const isPending = item.status === 'pending';
   const isHistory = ['completed', 'failed', 'skipped', 'cancelled'].includes(item.status);
   const isHistory = ['completed', 'failed', 'skipped', 'cancelled'].includes(item.status);
 
 
+  const isMobileSelectable = isPending && onToggleSelect;
+
   return (
   return (
     <div
     <div
       ref={setNodeRef}
       ref={setNodeRef}
       style={style}
       style={style}
       className={`
       className={`
-        group relative bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary
-        transition-all duration-200 hover:border-bambu-dark-tertiary/80
+        group relative bg-bambu-dark-secondary rounded-xl border transition-all duration-200
         ${isDragging ? 'opacity-50 scale-[1.02] shadow-xl z-50' : ''}
         ${isDragging ? 'opacity-50 scale-[1.02] shadow-xl z-50' : ''}
         ${isPrinting ? 'border-blue-500/30 bg-gradient-to-r from-blue-500/5 to-transparent' : ''}
         ${isPrinting ? 'border-blue-500/30 bg-gradient-to-r from-blue-500/5 to-transparent' : ''}
+        ${isSelected && isMobileSelectable ? 'sm:border-bambu-dark-tertiary border-bambu-green/40' : ''}
+        ${!isSelected && !isPrinting ? 'border-bambu-dark-tertiary hover:border-bambu-dark-tertiary/80' : ''}
+        ${isMobileSelectable ? 'sm:cursor-default' : ''}
       `}
       `}
+      onClick={isMobileSelectable ? () => {
+        if (window.innerWidth < 640) onToggleSelect();
+      } : undefined}
     >
     >
-      <div className="flex items-center gap-4 p-4">
-        {/* Selection checkbox for pending items */}
+      {/* Mobile selected left accent bar */}
+      {isMobileSelectable && isSelected && (
+        <div className="sm:hidden absolute left-0 top-3 bottom-3 w-1 rounded-full bg-bambu-green" />
+      )}
+
+      <div className="flex items-start sm:items-center gap-2 sm:gap-4 p-3 sm:p-4">
+        {/* Mobile selection indicator — left accent bar only, no tick */}
+
+        {/* Selection checkbox for pending items - hidden on mobile, tap card instead */}
         {isPending && onToggleSelect && (
         {isPending && onToggleSelect && (
           <button
           <button
             onClick={(e) => {
             onClick={(e) => {
               e.stopPropagation();
               e.stopPropagation();
               onToggleSelect();
               onToggleSelect();
             }}
             }}
-            className={`flex items-center justify-center w-6 h-6 rounded border transition-colors ${
+            className={`hidden sm:flex items-center justify-center w-6 h-6 rounded border transition-colors shrink-0 ${
               isSelected
               isSelected
                 ? 'bg-bambu-green border-bambu-green text-white'
                 ? 'bg-bambu-green border-bambu-green text-white'
                 : 'border-white/30 bg-black/30 hover:border-bambu-green/50'
                 : 'border-white/30 bg-black/30 hover:border-bambu-green/50'
@@ -390,25 +404,25 @@ function SortableQueueItem({
           </button>
           </button>
         )}
         )}
 
 
-        {/* Drag handle or position number */}
+        {/* Drag handle or position number - hidden on mobile */}
         {isPending ? (
         {isPending ? (
           <div
           <div
             {...attributes}
             {...attributes}
             {...listeners}
             {...listeners}
-            className="flex items-center justify-center w-10 h-10 md:w-8 md:h-8 rounded-lg bg-bambu-dark cursor-grab active:cursor-grabbing hover:bg-bambu-dark-tertiary transition-colors touch-manipulation"
+            className="hidden sm:flex items-center justify-center w-8 h-8 rounded-lg bg-bambu-dark cursor-grab active:cursor-grabbing hover:bg-bambu-dark-tertiary transition-colors touch-manipulation shrink-0"
           >
           >
-            <GripVertical className="w-6 h-6 md:w-4 md:h-4 text-bambu-gray" />
+            <GripVertical className="w-4 h-4 text-bambu-gray" />
           </div>
           </div>
         ) : position !== undefined ? (
         ) : position !== undefined ? (
-          <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-bambu-dark text-bambu-gray text-sm font-medium">
+          <div className="hidden sm:flex items-center justify-center w-8 h-8 rounded-lg bg-bambu-dark text-bambu-gray text-sm font-medium shrink-0">
             #{position}
             #{position}
           </div>
           </div>
         ) : (
         ) : (
-          <div className="w-8" />
+          <div className="hidden sm:block w-8 shrink-0" />
         )}
         )}
 
 
         {/* Thumbnail - use plate-specific thumbnail if plate_id is set */}
         {/* Thumbnail - use plate-specific thumbnail if plate_id is set */}
-        <div className="w-14 h-14 flex-shrink-0 bg-bambu-dark rounded-lg overflow-hidden">
+        <div className="w-10 h-10 sm:w-14 sm:h-14 flex-shrink-0 bg-bambu-dark rounded-lg overflow-hidden">
           {item.archive_thumbnail ? (
           {item.archive_thumbnail ? (
             <img
             <img
               src={
               src={
@@ -431,7 +445,7 @@ function SortableQueueItem({
             />
             />
           ) : (
           ) : (
             <div className="w-full h-full flex items-center justify-center text-bambu-gray">
             <div className="w-full h-full flex items-center justify-center text-bambu-gray">
-              <Layers className="w-6 h-6" />
+              <Layers className="w-5 h-5 sm:w-6 sm:h-6" />
             </div>
             </div>
           )}
           )}
         </div>
         </div>
@@ -439,7 +453,7 @@ function SortableQueueItem({
         {/* Info */}
         {/* Info */}
         <div className="flex-1 min-w-0">
         <div className="flex-1 min-w-0">
           <div className="flex items-center gap-2 mb-1">
           <div className="flex items-center gap-2 mb-1">
-            <p className="text-white font-medium truncate">
+            <p className="text-sm sm:text-base text-white font-medium truncate">
               {item.archive_name || item.library_file_name || `File #${item.archive_id || item.library_file_id}`}
               {item.archive_name || item.library_file_name || `File #${item.archive_id || item.library_file_id}`}
               {(platesData?.is_multi_plate ?? false) && item.plate_id !== undefined && item.plate_id !== null && ` • ${plates.find(plate => plate.index === item.plate_id)?.name || t('queue.plateNumber', { index: item.plate_id })}`}
               {(platesData?.is_multi_plate ?? false) && item.plate_id !== undefined && item.plate_id !== null && ` • ${plates.find(plate => plate.index === item.plate_id)?.name || t('queue.plateNumber', { index: item.plate_id })}`}
             </p>
             </p>
@@ -462,57 +476,59 @@ function SortableQueueItem({
             ) : null}
             ) : null}
           </div>
           </div>
 
 
-          <div className="flex items-center gap-3 text-sm text-bambu-gray">
-            <span className={`flex items-center gap-1.5 ${item.printer_id === null && !item.target_model ? 'text-orange-400' : ''} ${item.target_model ? 'text-blue-400' : ''}`}>
-              <Printer className="w-3.5 h-3.5" />
+          <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:text-sm text-bambu-gray">
+            <span className={`flex items-center gap-1 sm:gap-1.5 ${item.printer_id === null && !item.target_model ? 'text-orange-400' : ''} ${item.target_model ? 'text-blue-400' : ''}`}>
+              <Printer className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
+              <span className="truncate max-w-[120px] sm:max-w-none">
               {item.target_model
               {item.target_model
                 ? `${t('queue.filter.any')} ${item.target_model}${item.target_location ? ` @ ${item.target_location}` : ''}${item.required_filament_types?.length ? ` (${item.required_filament_types.join(', ')})` : ''}`
                 ? `${t('queue.filter.any')} ${item.target_model}${item.target_location ? ` @ ${item.target_location}` : ''}${item.required_filament_types?.length ? ` (${item.required_filament_types.join(', ')})` : ''}`
                 : item.printer_id === null
                 : item.printer_id === null
                   ? t('queue.filter.unassigned')
                   ? t('queue.filter.unassigned')
                   : (item.printer_name || `${t('common.printer')} #${item.printer_id}`)}
                   : (item.printer_name || `${t('common.printer')} #${item.printer_id}`)}
+              </span>
             </span>
             </span>
             {item.print_time_seconds && (
             {item.print_time_seconds && (
-              <span className="flex items-center gap-1.5">
-                <Timer className="w-3.5 h-3.5" />
+              <span className="flex items-center gap-1 sm:gap-1.5">
+                <Timer className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
                 {formatDuration(item.print_time_seconds)}
                 {formatDuration(item.print_time_seconds)}
               </span>
               </span>
             )}
             )}
             {item.filament_used_grams && (
             {item.filament_used_grams && (
-              <span className="flex items-center gap-1.5">
-                <Weight className="w-3.5 h-3.5" />
+              <span className="flex items-center gap-1 sm:gap-1.5">
+                <Weight className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
                 {formatWeight(item.filament_used_grams)}
                 {formatWeight(item.filament_used_grams)}
               </span>
               </span>
             )}
             )}
             {item.created_by_username && (
             {item.created_by_username && (
-              <span className="flex items-center gap-1.5" title={t('queue.addedBy', { name: item.created_by_username })}>
+              <span className="hidden sm:flex items-center gap-1.5" title={t('queue.addedBy', { name: item.created_by_username })}>
                 <User className="w-3.5 h-3.5" />
                 <User className="w-3.5 h-3.5" />
                 {item.created_by_username}
                 {item.created_by_username}
               </span>
               </span>
             )}
             )}
             {isPending && !item.manual_start && (
             {isPending && !item.manual_start && (
-              <span className="flex items-center gap-1.5">
-                <Clock className="w-3.5 h-3.5" />
+              <span className="flex items-center gap-1 sm:gap-1.5">
+                <Clock className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
                 {formatRelativeTime(item.scheduled_time, timeFormat, t)}
                 {formatRelativeTime(item.scheduled_time, timeFormat, t)}
               </span>
               </span>
             )}
             )}
           </div>
           </div>
 
 
           {/* Options badges */}
           {/* Options badges */}
-          <div className="flex items-center gap-2 mt-2">
+          <div className="flex flex-wrap items-center gap-1.5 sm:gap-2 mt-1.5 sm:mt-2">
             {item.manual_start && (
             {item.manual_start && (
-              <span className="text-xs px-2 py-0.5 bg-purple-500/10 text-purple-400 rounded-full border border-purple-500/20 flex items-center gap-1">
-                <Hand className="w-3 h-3" />
+              <span className="text-[10px] sm:text-xs px-1.5 sm:px-2 py-0.5 bg-purple-500/10 text-purple-400 rounded-full border border-purple-500/20 flex items-center gap-1">
+                <Hand className="w-2.5 h-2.5 sm:w-3 sm:h-3" />
                 {t('queue.badges.staged')}
                 {t('queue.badges.staged')}
               </span>
               </span>
             )}
             )}
             {item.require_previous_success && (
             {item.require_previous_success && (
-              <span className="text-xs px-2 py-0.5 bg-orange-500/10 text-orange-400 rounded-full border border-orange-500/20">
+              <span className="text-[10px] sm:text-xs px-1.5 sm:px-2 py-0.5 bg-orange-500/10 text-orange-400 rounded-full border border-orange-500/20">
                 {t('queue.badges.requiresPrevious')}
                 {t('queue.badges.requiresPrevious')}
               </span>
               </span>
             )}
             )}
             {item.auto_off_after && (
             {item.auto_off_after && (
-              <span className="text-xs px-2 py-0.5 bg-blue-500/10 text-blue-400 rounded-full border border-blue-500/20 flex items-center gap-1">
-                <Power className="w-3 h-3" />
+              <span className="text-[10px] sm:text-xs px-1.5 sm:px-2 py-0.5 bg-blue-500/10 text-blue-400 rounded-full border border-blue-500/20 flex items-center gap-1">
+                <Power className="w-2.5 h-2.5 sm:w-3 sm:h-3" />
                 {t('queue.badges.autoPowerOff')}
                 {t('queue.badges.autoPowerOff')}
               </span>
               </span>
             )}
             )}
@@ -520,17 +536,17 @@ function SortableQueueItem({
 
 
           {/* Progress bar for printing items - TODO: integrate with WebSocket */}
           {/* Progress bar for printing items - TODO: integrate with WebSocket */}
           {isPrinting && status && (
           {isPrinting && status && (
-            <div className="mt-3">
-              <div className="flex items-center justify-between text-sm">
-                <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-2 mr-3">
+            <div className="mt-2 sm:mt-3">
+              <div className="flex items-center justify-between text-xs sm:text-sm">
+                <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-1.5 sm:h-2 mr-3">
                   <div
                   <div
-                    className="bg-bambu-green h-2 rounded-full transition-all"
+                    className="bg-bambu-green h-1.5 sm:h-2 rounded-full transition-all"
                     style={{ width: `${status.progress || 0}%` }}
                     style={{ width: `${status.progress || 0}%` }}
                   />
                   />
                 </div>
                 </div>
                 <span className="text-white">{Math.round(status.progress || 0)}%</span>
                 <span className="text-white">{Math.round(status.progress || 0)}%</span>
               </div>
               </div>
-              <div className="flex items-center gap-3 mt-2 text-xs text-bambu-gray">
+              <div className="flex flex-wrap items-center gap-2 sm:gap-3 mt-1.5 sm:mt-2 text-[10px] sm:text-xs text-bambu-gray">
                 {status.remaining_time != null && status.remaining_time > 0 && (
                 {status.remaining_time != null && status.remaining_time > 0 && (
                   <>
                   <>
                     <span className="flex items-center gap-1">
                     <span className="flex items-center gap-1">
@@ -554,7 +570,7 @@ function SortableQueueItem({
 
 
           {/* Waiting reason for model-based assignments */}
           {/* Waiting reason for model-based assignments */}
           {item.waiting_reason && item.status === 'pending' && (
           {item.waiting_reason && item.status === 'pending' && (
-            <p className="text-xs text-purple-400 mt-2 flex items-start gap-1">
+            <p className="text-[10px] sm:text-xs text-purple-400 mt-1.5 sm:mt-2 flex items-start gap-1">
               <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
               <AlertCircle className="w-3 h-3 mt-0.5 flex-shrink-0" />
               <span>{item.waiting_reason}</span>
               <span>{item.waiting_reason}</span>
             </p>
             </p>
@@ -562,88 +578,91 @@ function SortableQueueItem({
 
 
           {/* Error message */}
           {/* Error message */}
           {item.error_message && (
           {item.error_message && (
-            <p className="text-xs text-red-400 mt-2 flex items-center gap-1">
+            <p className="text-[10px] sm:text-xs text-red-400 mt-1.5 sm:mt-2 flex items-center gap-1">
               <AlertCircle className="w-3 h-3" />
               <AlertCircle className="w-3 h-3" />
               {item.error_message}
               {item.error_message}
             </p>
             </p>
           )}
           )}
         </div>
         </div>
 
 
-        {/* Status badge */}
-        <StatusBadge status={item.status} waitingReason={item.waiting_reason} printerState={printerState} t={t} />
-
-        {/* Actions */}
-        <div className="flex items-center gap-1">
-          {isPrinting && (
-            <Button
-              variant="ghost"
-              size="sm"
-              onClick={onStop}
-              disabled={!hasPermission('printers:control')}
-              title={!hasPermission('printers:control') ? t('queue.permissions.noStopPrint') : t('queue.actions.stopPrint')}
-              className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
-            >
-              <StopCircle className="w-4 h-4" />
-            </Button>
-          )}
-          {isPending && (
-            <>
-              {item.manual_start && (
-                <Button
-                  variant="ghost"
-                  size="sm"
-                  onClick={onStart}
-                  disabled={!hasPermission('printers:control')}
-                  title={!hasPermission('printers:control') ? t('queue.permissions.noStartPrint') : t('queue.actions.startPrint')}
-                  className="text-bambu-green hover:text-bambu-green-light hover:bg-bambu-green/10"
-                >
-                  <Play className="w-4 h-4" />
-                </Button>
-              )}
-              <Button
-                variant="ghost"
-                size="sm"
-                onClick={onEdit}
-                disabled={!canModify('queue', 'update', item.created_by_id)}
-                title={!canModify('queue', 'update', item.created_by_id) ? t('queue.permissions.noEdit') : t('common.edit')}
-              >
-                <Pencil className="w-4 h-4" />
-              </Button>
-              <Button
-                variant="ghost"
-                size="sm"
-                onClick={onCancel}
-                disabled={!canModify('queue', 'delete', item.created_by_id)}
-                title={!canModify('queue', 'delete', item.created_by_id) ? t('queue.permissions.noCancel') : t('common.cancel')}
-                className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
-              >
-                <X className="w-4 h-4" />
-              </Button>
-            </>
-          )}
-          {isHistory && (
-            <>
-              <Button
-                variant="ghost"
-                size="sm"
-                onClick={onRequeue}
-                disabled={!hasPermission('queue:create')}
-                title={!hasPermission('queue:create') ? t('queue.permissions.noRequeue') : t('queue.actions.requeue')}
-                className="text-bambu-green hover:text-bambu-green/80 hover:bg-bambu-green/10"
-              >
-                <RefreshCw className="w-4 h-4" />
-              </Button>
+        {/* Status badge + Actions */}
+        <div className="flex flex-col sm:flex-row items-end sm:items-center gap-2 sm:gap-1 shrink-0" onClick={(e) => e.stopPropagation()}>
+          <StatusBadge status={item.status} waitingReason={item.waiting_reason} printerState={printerState} t={t} />
+
+          <div className="flex items-center gap-0.5 sm:gap-1">
+            {isPrinting && (
               <Button
               <Button
                 variant="ghost"
                 variant="ghost"
                 size="sm"
                 size="sm"
-                onClick={onRemove}
-                disabled={!canModify('queue', 'delete', item.created_by_id)}
-                title={!canModify('queue', 'delete', item.created_by_id) ? t('queue.permissions.noRemove') : t('common.remove')}
+                onClick={onStop}
+                disabled={!hasPermission('printers:control')}
+                title={!hasPermission('printers:control') ? t('queue.permissions.noStopPrint') : t('queue.actions.stopPrint')}
+                className="text-red-400 hover:text-red-300 hover:bg-red-500/10 p-1.5 sm:p-2"
               >
               >
-                <Trash2 className="w-4 h-4" />
+                <StopCircle className="w-4 h-4" />
               </Button>
               </Button>
-            </>
-          )}
+            )}
+            {isPending && (
+              <>
+                {item.manual_start && (
+                  <Button
+                    variant="ghost"
+                    size="sm"
+                    onClick={onStart}
+                    disabled={!hasPermission('printers:control')}
+                    title={!hasPermission('printers:control') ? t('queue.permissions.noStartPrint') : t('queue.actions.startPrint')}
+                    className="text-bambu-green hover:text-bambu-green-light hover:bg-bambu-green/10 p-1.5 sm:p-2"
+                  >
+                    <Play className="w-4 h-4" />
+                  </Button>
+                )}
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  onClick={onEdit}
+                  disabled={!canModify('queue', 'update', item.created_by_id)}
+                  title={!canModify('queue', 'update', item.created_by_id) ? t('queue.permissions.noEdit') : t('common.edit')}
+                  className="p-1.5 sm:p-2"
+                >
+                  <Pencil className="w-4 h-4" />
+                </Button>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  onClick={onCancel}
+                  disabled={!canModify('queue', 'delete', item.created_by_id)}
+                  title={!canModify('queue', 'delete', item.created_by_id) ? t('queue.permissions.noCancel') : t('common.cancel')}
+                  className="text-red-400 hover:text-red-300 hover:bg-red-500/10 p-1.5 sm:p-2"
+                >
+                  <X className="w-4 h-4" />
+                </Button>
+              </>
+            )}
+            {isHistory && (
+              <>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  onClick={onRequeue}
+                  disabled={!hasPermission('queue:create')}
+                  title={!hasPermission('queue:create') ? t('queue.permissions.noRequeue') : t('queue.actions.requeue')}
+                  className="text-bambu-green hover:text-bambu-green/80 hover:bg-bambu-green/10 p-1.5 sm:p-2"
+                >
+                  <RefreshCw className="w-4 h-4" />
+                </Button>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  onClick={onRemove}
+                  disabled={!canModify('queue', 'delete', item.created_by_id)}
+                  title={!canModify('queue', 'delete', item.created_by_id) ? t('queue.permissions.noRemove') : t('common.remove')}
+                  className="p-1.5 sm:p-2"
+                >
+                  <Trash2 className="w-4 h-4" />
+                </Button>
+              </>
+            )}
+          </div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
@@ -987,72 +1006,72 @@ export function QueuePage() {
       </div>
       </div>
 
 
       {/* Summary Cards */}
       {/* Summary Cards */}
-      <div className="grid grid-cols-1 md:grid-cols-5 gap-4 mb-8">
+      <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 sm:gap-3 lg:gap-4 mb-8">
         <Card className="bg-gradient-to-br from-blue-500/10 to-transparent border-blue-500/20">
         <Card className="bg-gradient-to-br from-blue-500/10 to-transparent border-blue-500/20">
-          <CardContent className="p-4">
-            <div className="flex items-center gap-3">
-              <div className="w-10 h-10 rounded-lg bg-blue-500/20 flex items-center justify-center">
-                <Play className="w-5 h-5 text-blue-400" />
+          <CardContent className="p-3 sm:p-4">
+            <div className="flex items-center gap-2 sm:gap-3">
+              <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-blue-500/20 flex items-center justify-center shrink-0">
+                <Play className="w-4 h-4 sm:w-5 sm:h-5 text-blue-400" />
               </div>
               </div>
-              <div>
-                <p className="text-2xl font-bold text-white">{activeItems.length}</p>
-                <p className="text-sm text-bambu-gray">{t('queue.summary.printing')}</p>
+              <div className="min-w-0">
+                <p className="text-xl sm:text-2xl font-bold text-white truncate">{activeItems.length}</p>
+                <p className="text-xs sm:text-sm text-bambu-gray truncate">{t('queue.summary.printing')}</p>
               </div>
               </div>
             </div>
             </div>
           </CardContent>
           </CardContent>
         </Card>
         </Card>
 
 
         <Card className="bg-gradient-to-br from-yellow-500/10 to-transparent border-yellow-500/20">
         <Card className="bg-gradient-to-br from-yellow-500/10 to-transparent border-yellow-500/20">
-          <CardContent className="p-4">
-            <div className="flex items-center gap-3">
-              <div className="w-10 h-10 rounded-lg bg-yellow-500/20 flex items-center justify-center">
-                <Clock className="w-5 h-5 text-yellow-400" />
+          <CardContent className="p-3 sm:p-4">
+            <div className="flex items-center gap-2 sm:gap-3">
+              <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-yellow-500/20 flex items-center justify-center shrink-0">
+                <Clock className="w-4 h-4 sm:w-5 sm:h-5 text-yellow-400" />
               </div>
               </div>
-              <div>
-                <p className="text-2xl font-bold text-white">{pendingItems.length}</p>
-                <p className="text-sm text-bambu-gray">{t('queue.summary.queued')}</p>
+              <div className="min-w-0">
+                <p className="text-xl sm:text-2xl font-bold text-white truncate">{pendingItems.length}</p>
+                <p className="text-xs sm:text-sm text-bambu-gray truncate">{t('queue.summary.queued')}</p>
               </div>
               </div>
             </div>
             </div>
           </CardContent>
           </CardContent>
         </Card>
         </Card>
 
 
         <Card className="bg-gradient-to-br from-bambu-green/10 to-transparent border-bambu-green/20">
         <Card className="bg-gradient-to-br from-bambu-green/10 to-transparent border-bambu-green/20">
-          <CardContent className="p-4">
-            <div className="flex items-center gap-3">
-              <div className="w-10 h-10 rounded-lg bg-bambu-green/20 flex items-center justify-center">
-                <Timer className="w-5 h-5 text-bambu-green" />
+          <CardContent className="p-3 sm:p-4">
+            <div className="flex items-center gap-2 sm:gap-3">
+              <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-bambu-green/20 flex items-center justify-center shrink-0">
+                <Timer className="w-4 h-4 sm:w-5 sm:h-5 text-bambu-green" />
               </div>
               </div>
-              <div>
-                <p className="text-2xl font-bold text-white">{formatDuration(totalQueueTime)}</p>
-                <p className="text-sm text-bambu-gray">{t('queue.summary.totalTime')}</p>
+              <div className="min-w-0">
+                <p className="text-xl sm:text-2xl font-bold text-white truncate">{formatDuration(totalQueueTime)}</p>
+                <p className="text-xs sm:text-sm text-bambu-gray truncate">{t('queue.summary.totalTime')}</p>
               </div>
               </div>
             </div>
             </div>
           </CardContent>
           </CardContent>
         </Card>
         </Card>
 
 
         <Card className="bg-gradient-to-br from-purple-500/10 to-transparent border-purple-500/20">
         <Card className="bg-gradient-to-br from-purple-500/10 to-transparent border-purple-500/20">
-          <CardContent className="p-4">
-            <div className="flex items-center gap-3">
-              <div className="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center">
-                <Weight className="w-5 h-5 text-purple-500" />
+          <CardContent className="p-3 sm:p-4">
+            <div className="flex items-center gap-2 sm:gap-3">
+              <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-purple-500/20 flex items-center justify-center shrink-0">
+                <Weight className="w-4 h-4 sm:w-5 sm:h-5 text-purple-500" />
               </div>
               </div>
-              <div>
-                <p className="text-2xl font-bold text-white">{formatWeight(totalWeight)}</p>
-                <p className="text-sm text-bambu-gray">{t('queue.summary.totalWeight')}</p>
+              <div className="min-w-0">
+                <p className="text-xl sm:text-2xl font-bold text-white truncate">{formatWeight(totalWeight)}</p>
+                <p className="text-xs sm:text-sm text-bambu-gray truncate">{t('queue.summary.totalWeight')}</p>
               </div>
               </div>
             </div>
             </div>
           </CardContent>
           </CardContent>
         </Card>
         </Card>
 
 
-        <Card className="bg-gradient-to-br from-gray-500/10 to-transparent border-gray-500/20">
-          <CardContent className="p-4">
-            <div className="flex items-center gap-3">
-              <div className="w-10 h-10 rounded-lg bg-gray-500/20 flex items-center justify-center">
-                <CheckCircle className="w-5 h-5 text-gray-400" />
+        <Card className="col-span-2 sm:col-span-1 bg-gradient-to-br from-gray-500/10 to-transparent border-gray-500/20">
+          <CardContent className="p-3 sm:p-4">
+            <div className="flex items-center gap-2 sm:gap-3">
+              <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gray-500/20 flex items-center justify-center shrink-0">
+                <CheckCircle className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400" />
               </div>
               </div>
-              <div>
-                <p className="text-2xl font-bold text-white">{historyItems.length}</p>
-                <p className="text-sm text-bambu-gray">{t('queue.summary.history')}</p>
+              <div className="min-w-0">
+                <p className="text-xl sm:text-2xl font-bold text-white truncate">{historyItems.length}</p>
+                <p className="text-xs sm:text-sm text-bambu-gray truncate">{t('queue.summary.history')}</p>
               </div>
               </div>
             </div>
             </div>
           </CardContent>
           </CardContent>
@@ -1060,9 +1079,9 @@ export function QueuePage() {
       </div>
       </div>
 
 
       {/* Filters */}
       {/* Filters */}
-      <div className="flex items-center gap-4 mb-6">
+      <div className="flex flex-wrap items-center gap-2 sm:gap-4 mb-6">
         <select
         <select
-          className="px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+          className="px-2 sm:px-3 py-2 text-sm sm:text-base bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none min-w-0 flex-1 sm:flex-none"
           value={filterPrinter === -1 ? 'unassigned' : (filterPrinter || '')}
           value={filterPrinter === -1 ? 'unassigned' : (filterPrinter || '')}
           onChange={(e) => {
           onChange={(e) => {
             const val = e.target.value;
             const val = e.target.value;
@@ -1079,7 +1098,7 @@ export function QueuePage() {
         </select>
         </select>
 
 
         <select
         <select
-          className="px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+          className="px-2 sm:px-3 py-2 text-sm sm:text-base bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none min-w-0 flex-1 sm:flex-none"
           value={filterStatus}
           value={filterStatus}
           onChange={(e) => setFilterStatus(e.target.value)}
           onChange={(e) => setFilterStatus(e.target.value)}
         >
         >
@@ -1094,7 +1113,7 @@ export function QueuePage() {
 
 
         {uniqueLocations.length > 0 && (
         {uniqueLocations.length > 0 && (
           <select
           <select
-            className="px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+            className="px-2 sm:px-3 py-2 text-sm sm:text-base bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none min-w-0 flex-1 sm:flex-none"
             value={filterLocation}
             value={filterLocation}
             onChange={(e) => setFilterLocation(e.target.value)}
             onChange={(e) => setFilterLocation(e.target.value)}
           >
           >
@@ -1105,10 +1124,11 @@ export function QueuePage() {
           </select>
           </select>
         )}
         )}
 
 
-        <div className="flex-1" />
+        <div className="hidden sm:block flex-1" />
 
 
         {historyItems.length > 0 && (
         {historyItems.length > 0 && (
           <Button
           <Button
+            className="w-full sm:w-auto"
             variant="secondary"
             variant="secondary"
             size="sm"
             size="sm"
             onClick={() => setShowClearHistoryConfirm(true)}
             onClick={() => setShowClearHistoryConfirm(true)}
@@ -1132,15 +1152,15 @@ export function QueuePage() {
           </p>
           </p>
         </Card>
         </Card>
       ) : (
       ) : (
-        <div className="space-y-8">
+        <div className="space-y-6 sm:space-y-8">
           {/* Active Prints */}
           {/* Active Prints */}
           {activeItems.length > 0 && (
           {activeItems.length > 0 && (
             <div>
             <div>
-              <h2 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
+              <h2 className="text-base sm:text-lg font-semibold text-white mb-3 sm:mb-4 flex items-center gap-2">
                 <div className="w-2 h-2 rounded-full bg-blue-400 animate-pulse" />
                 <div className="w-2 h-2 rounded-full bg-blue-400 animate-pulse" />
                 {t('queue.sections.currentlyPrinting')}
                 {t('queue.sections.currentlyPrinting')}
               </h2>
               </h2>
-              <div className="space-y-3">
+              <div className="space-y-2 sm:space-y-3">
                 {activeItems.map((item) => (
                 {activeItems.map((item) => (
                   <SortableQueueItem
                   <SortableQueueItem
                     key={item.id}
                     key={item.id}
@@ -1165,20 +1185,20 @@ export function QueuePage() {
           {/* Pending Queue */}
           {/* Pending Queue */}
           {pendingItems.length > 0 && (
           {pendingItems.length > 0 && (
             <div>
             <div>
-              <div className="flex items-center justify-between mb-4">
-                <h2 className="text-lg font-semibold text-white flex items-center gap-2">
-                  <Clock className="w-5 h-5 text-yellow-400" />
+              <div className="flex flex-wrap items-center justify-between gap-2 mb-3 sm:mb-4">
+                <h2 className="text-base sm:text-lg font-semibold text-white flex items-center gap-2">
+                  <Clock className="w-4 h-4 sm:w-5 sm:h-5 text-yellow-400" />
                   {t('queue.sections.queued')}
                   {t('queue.sections.queued')}
-                  <span className="text-sm font-normal text-bambu-gray">
+                  <span className="text-xs sm:text-sm font-normal text-bambu-gray">
                     ({t('queue.itemCount', { count: pendingItems.length })})
                     ({t('queue.itemCount', { count: pendingItems.length })})
                   </span>
                   </span>
-                  <span className="text-xs text-bambu-gray ml-2" title={t('queue.reorderHint')}>
+                  <span className="hidden sm:inline text-xs text-bambu-gray ml-2" title={t('queue.reorderHint')}>
                     {t('queue.dragToReorder')}
                     {t('queue.dragToReorder')}
                   </span>
                   </span>
                 </h2>
                 </h2>
                 <div className="flex items-center gap-2">
                 <div className="flex items-center gap-2">
                   <select
                   <select
-                    className="px-3 py-1.5 text-sm bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+                    className="px-2 sm:px-3 py-1.5 text-xs sm:text-sm bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
                     value={pendingSortBy}
                     value={pendingSortBy}
                     onChange={(e) => setPendingSortBy(e.target.value as 'position' | 'name' | 'printer' | 'time')}
                     onChange={(e) => setPendingSortBy(e.target.value as 'position' | 'name' | 'printer' | 'time')}
                   >
                   >
@@ -1200,12 +1220,12 @@ export function QueuePage() {
               </div>
               </div>
 
 
               {/* Bulk action toolbar */}
               {/* Bulk action toolbar */}
-              <div className="flex items-center gap-3 mb-4 p-3 bg-bambu-dark rounded-lg">
+              <div className="flex flex-wrap items-center gap-2 sm:gap-3 mb-3 sm:mb-4 p-2 sm:p-3 bg-bambu-dark rounded-lg">
                 <Button
                 <Button
                   variant="ghost"
                   variant="ghost"
                   size="sm"
                   size="sm"
                   onClick={handleSelectAll}
                   onClick={handleSelectAll}
-                  className="flex items-center gap-2"
+                  className="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm"
                 >
                 >
                   {selectedItems.length === pendingItems.length && pendingItems.length > 0 ? (
                   {selectedItems.length === pendingItems.length && pendingItems.length > 0 ? (
                     <CheckSquare className="w-4 h-4 text-bambu-green" />
                     <CheckSquare className="w-4 h-4 text-bambu-green" />
@@ -1216,31 +1236,31 @@ export function QueuePage() {
                 </Button>
                 </Button>
                 {selectedItems.length > 0 && (
                 {selectedItems.length > 0 && (
                   <>
                   <>
-                    <span className="text-sm text-bambu-gray">
+                    <span className="text-xs sm:text-sm text-bambu-gray">
                       {t('queue.bulkEdit.selected', { count: selectedItems.length })}
                       {t('queue.bulkEdit.selected', { count: selectedItems.length })}
                     </span>
                     </span>
-                    <div className="h-4 w-px bg-bambu-dark-tertiary" />
+                    <div className="hidden sm:block h-4 w-px bg-bambu-dark-tertiary" />
                     <Button
                     <Button
                       variant="ghost"
                       variant="ghost"
                       size="sm"
                       size="sm"
                       onClick={() => setShowBulkEditModal(true)}
                       onClick={() => setShowBulkEditModal(true)}
-                      className="flex items-center gap-2 text-bambu-green hover:text-bambu-green-light"
+                      className="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm text-bambu-green hover:text-bambu-green-light"
                       disabled={!hasAnyPermission('queue:update_own', 'queue:update_all')}
                       disabled={!hasAnyPermission('queue:update_own', 'queue:update_all')}
-                      title={!hasAnyPermission('queue:update_own', 'queue:update_all') ? t('queue.permissions.noEditItems') : undefined}
+                      title={!hasAnyPermission('queue:update_own', 'queue:update_all') ? t('queue.permissions.noEditItems') : t('queue.bulkEdit.editSelected')}
                     >
                     >
-                      <Pencil className="w-4 h-4" />
-                      {t('queue.bulkEdit.editSelected')}
+                      <Pencil className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
+                      <span className="hidden sm:inline">{t('queue.bulkEdit.editSelected')}</span>
                     </Button>
                     </Button>
                     <Button
                     <Button
                       variant="ghost"
                       variant="ghost"
                       size="sm"
                       size="sm"
                       onClick={() => bulkCancelMutation.mutate(selectedItems)}
                       onClick={() => bulkCancelMutation.mutate(selectedItems)}
-                      className="flex items-center gap-2 text-red-400 hover:text-red-300"
+                      className="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm text-red-400 hover:text-red-300"
                       disabled={bulkCancelMutation.isPending || !hasAnyPermission('queue:delete_own', 'queue:delete_all')}
                       disabled={bulkCancelMutation.isPending || !hasAnyPermission('queue:delete_own', 'queue:delete_all')}
-                      title={!hasAnyPermission('queue:delete_own', 'queue:delete_all') ? t('queue.permissions.noCancelItems') : undefined}
+                      title={!hasAnyPermission('queue:delete_own', 'queue:delete_all') ? t('queue.permissions.noCancelItems') : t('queue.bulkEdit.cancelSelected')}
                     >
                     >
-                      <X className="w-4 h-4" />
-                      {t('queue.bulkEdit.cancelSelected')}
+                      <X className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
+                      <span className="hidden sm:inline">{t('queue.bulkEdit.cancelSelected')}</span>
                     </Button>
                     </Button>
                   </>
                   </>
                 )}
                 )}
@@ -1255,7 +1275,7 @@ export function QueuePage() {
                   items={pendingItems.map(i => i.id)}
                   items={pendingItems.map(i => i.id)}
                   strategy={verticalListSortingStrategy}
                   strategy={verticalListSortingStrategy}
                 >
                 >
-                  <div className="space-y-3">
+                  <div className="space-y-2 sm:space-y-3">
                     {pendingItems.map((item, index) => (
                     {pendingItems.map((item, index) => (
                       <SortableQueueItem
                       <SortableQueueItem
                         key={item.id}
                         key={item.id}
@@ -1284,17 +1304,17 @@ export function QueuePage() {
           {/* History */}
           {/* History */}
           {historyItems.length > 0 && (
           {historyItems.length > 0 && (
             <div>
             <div>
-              <div className="flex items-center justify-between mb-4">
-                <h2 className="text-lg font-semibold text-white flex items-center gap-2">
-                  <CheckCircle className="w-5 h-5 text-bambu-gray" />
+              <div className="flex flex-wrap items-center justify-between gap-2 mb-3 sm:mb-4">
+                <h2 className="text-base sm:text-lg font-semibold text-white flex items-center gap-2">
+                  <CheckCircle className="w-4 h-4 sm:w-5 sm:h-5 text-bambu-gray" />
                   {t('queue.sections.history')}
                   {t('queue.sections.history')}
-                  <span className="text-sm font-normal text-bambu-gray">
+                  <span className="text-xs sm:text-sm font-normal text-bambu-gray">
                     ({t('queue.itemCount', { count: historyItems.length })})
                     ({t('queue.itemCount', { count: historyItems.length })})
                   </span>
                   </span>
                 </h2>
                 </h2>
                 <div className="flex items-center gap-2">
                 <div className="flex items-center gap-2">
                   <select
                   <select
-                    className="px-3 py-1.5 text-sm bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+                    className="px-2 sm:px-3 py-1.5 text-xs sm:text-sm bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
                     value={historySortBy}
                     value={historySortBy}
                     onChange={(e) => setHistorySortBy(e.target.value as 'date' | 'name' | 'printer')}
                     onChange={(e) => setHistorySortBy(e.target.value as 'date' | 'name' | 'printer')}
                   >
                   >
@@ -1313,7 +1333,7 @@ export function QueuePage() {
                   </Button>
                   </Button>
                 </div>
                 </div>
               </div>
               </div>
-              <div className="space-y-3">
+              <div className="space-y-2 sm:space-y-3">
                 {historyItems.slice(0, 20).map((item, index) => (
                 {historyItems.slice(0, 20).map((item, index) => (
                   <SortableQueueItem
                   <SortableQueueItem
                     key={item.id}
                     key={item.id}

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
static/assets/index-BLcYYvbZ.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
static/assets/index-EqFdfChN.css


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
static/assets/index-tulFiIvt.css


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-DmiEmqVo.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-tulFiIvt.css">
+    <script type="module" crossorigin src="/assets/index-BLcYYvbZ.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-EqFdfChN.css">
   </head>
   </head>
   <body>
   <body>
     <div id="root"></div>
     <div id="root"></div>

Някои файлове не бяха показани, защото твърде много файлове са промени