Bläddra i källkod

Refactoring AMS module

Martin Ziegler 5 månader sedan
förälder
incheckning
e44bc318ac

+ 1 - 0
backend/app/api/routes/printers.py

@@ -310,6 +310,7 @@ async def get_printer_status(printer_id: int, db: AsyncSession = Depends(get_db)
         ams_status_main=state.ams_status_main,
         ams_status_sub=state.ams_status_sub,
         mc_print_sub_stage=state.mc_print_sub_stage,
+        last_ams_update=state.last_ams_update,
     )
 
 

+ 2 - 0
backend/app/schemas/printer.py

@@ -143,3 +143,5 @@ class PrinterStatus(BaseModel):
     ams_status_sub: int = 0
     # mc_print_sub_stage - filament change step indicator used by OrcaSlicer/BambuStudio
     mc_print_sub_stage: int = 0
+    # Timestamp of last AMS data update (for RFID refresh detection)
+    last_ams_update: float = 0.0

+ 4 - 0
backend/app/services/bambu_mqtt.py

@@ -130,6 +130,8 @@ class PrinterState:
     ams_mapping: list = field(default_factory=list)
     # Per-AMS extruder map: {ams_id: extruder_id} where 0=right, 1=left
     ams_extruder_map: dict = field(default_factory=dict)
+    # Timestamp of last AMS data update (for RFID refresh detection)
+    last_ams_update: float = 0.0
 
 
 # Stage name mapping from BambuStudio DeviceManager.cpp
@@ -796,6 +798,8 @@ class BambuMQTTClient:
 
         # Store AMS data in raw_data so it's accessible via API
         self.state.raw_data["ams"] = ams_list
+        # Update timestamp for RFID refresh detection (frontend can detect "new data arrived")
+        self.state.last_ams_update = time.time()
         logger.debug(f"[{self.serial_number}] Stored AMS data with {len(ams_list)} units")
 
         # Extract ams_extruder_map from each AMS unit's info field

+ 2 - 0
frontend/src/api/client.ts

@@ -147,6 +147,8 @@ export interface PrinterStatus {
   ams_status_sub: number;
   // mc_print_sub_stage - filament change step indicator used by OrcaSlicer/BambuStudio
   mc_print_sub_stage: number;
+  // Timestamp of last AMS data update (for RFID refresh detection)
+  last_ams_update: number;
 }
 
 export interface PrinterCreate {

+ 28 - 166
frontend/src/components/control/AMSSectionDual.tsx

@@ -934,6 +934,7 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
   // Get ams_status values from printer status
   const amsStatusMain = status?.ams_status_main ?? 0;
   const trayNow = status?.tray_now ?? 255;
+  const lastAmsUpdate = status?.last_ams_update ?? 0;
 
   // AMS Operations hook - manages state machine for refresh/load/unload
   const amsOps = useAmsOperations({
@@ -941,6 +942,7 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
     amsUnits,
     amsStatusMain,
     trayNow,
+    lastAmsUpdate,
     onToast: showToast,
   });
 
@@ -967,60 +969,7 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
     }
   }, [status?.tray_now]);
 
-  // Watch for AMS data updates to clear the refresh spinner
-  // When the printer reports updated tray data after RFID read, clear the spinner
-  const prevAmsDataRef = useRef<string>('');
-  useEffect(() => {
-    if (!refreshingSlotState) return;
-
-    // Find the refreshing slot's current data
-    const { amsId, trayId, startTime } = refreshingSlotState;
-    const unit = amsUnits.find(u => u.id === amsId);
-    const tray = unit?.tray?.find(t => t.id === trayId);
-
-    if (!tray) return;
-
-    // Create a signature of the tray data to detect changes
-    const traySignature = JSON.stringify({
-      tag_uid: tray.tag_uid,
-      tray_uuid: tray.tray_uuid,
-      tray_id_name: tray.tray_id_name,
-      tray_type: tray.tray_type,
-      tray_color: tray.tray_color,
-    });
-
-    // If we have previous data and it changed, the refresh is complete
-    // Also require at least 500ms to have passed (to avoid false positives from initial render)
-    const elapsed = Date.now() - startTime;
-    if (prevAmsDataRef.current && prevAmsDataRef.current !== traySignature && elapsed > 500) {
-      console.log(`[AMSSectionDual] RFID refresh complete for AMS ${amsId} tray ${trayId} (took ${elapsed}ms)`);
-      setRefreshingSlotState(null);
-    }
 
-    // Update the ref for next comparison
-    prevAmsDataRef.current = traySignature;
-  }, [refreshingSlotState, amsUnits]);
-
-  const loadMutation = useMutation({
-    mutationFn: ({ trayId, extruderId }: { trayId: number; extruderId?: number }) =>
-      api.amsLoadFilament(printerId, trayId, extruderId),
-    onSuccess: (data, { trayId, extruderId }) => {
-      console.log(`[AMSSectionDual] Load filament success (tray ${trayId}, extruder ${extruderId}):`, data);
-    },
-    onError: (error, { trayId, extruderId }) => {
-      console.error(`[AMSSectionDual] Load filament error (tray ${trayId}, extruder ${extruderId}):`, error);
-    },
-  });
-
-  const unloadMutation = useMutation({
-    mutationFn: () => api.amsUnloadFilament(printerId),
-    onSuccess: (data) => {
-      console.log(`[AMSSectionDual] Unload filament success:`, data);
-    },
-    onError: (error) => {
-      console.error(`[AMSSectionDual] Unload filament error:`, error);
-    },
-  });
 
   // Handle tray selection
   const handleTraySelect = (trayId: number | null) => {
@@ -1053,132 +1002,43 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
     console.log(`[AMSSectionDual] handleLoad called, selectedTray: ${selectedTray}`);
     if (selectedTray !== null) {
       const extruderId = getExtruderIdForTray(selectedTray);
-      console.log(`[AMSSectionDual] Calling loadMutation.mutate(tray: ${selectedTray}, extruder: ${extruderId})`);
-      // Set ref synchronously FIRST (refs update immediately, before MQTT can respond)
-      intendedOperationRef.current = 'load';
-      // Show filament change card immediately
-      setUserFilamentChange({ isLoading: true, targetTrayId: selectedTray });
-      loadMutation.mutate({ trayId: selectedTray, extruderId });
+      console.log(`[AMSSectionDual] Starting load via hook: tray ${selectedTray}, extruder ${extruderId}`);
+      amsOps.startLoad(selectedTray, extruderId);
     }
   };
 
   const handleUnload = () => {
-    console.log(`[AMSSectionDual] handleUnload called, printerId: ${printerId}, trayNow: ${status?.tray_now}`);
-    // Set ref synchronously FIRST (refs update immediately, before MQTT can respond)
-    intendedOperationRef.current = 'unload';
-    // Show filament change card immediately (no target tray for unload)
-    setUserFilamentChange({ isLoading: false, targetTrayId: null });
-    console.log(`[AMSSectionDual] Calling unloadMutation.mutate()`);
-    unloadMutation.mutate();
+    console.log(`[AMSSectionDual] handleUnload called, printerId: ${printerId}, trayNow: ${trayNow}`);
+    amsOps.startUnload();
   };
 
   // Callback for FilamentChangeCard to close itself
   const handleFilamentChangeComplete = () => {
-    console.log(`[AMSSectionDual] FilamentChangeCard completed, closing card`);
-    intendedOperationRef.current = null; // Clear the synchronous ref
-    setUserFilamentChange(null);
+    console.log(`[AMSSectionDual] FilamentChangeCard completed, resetting operation`);
+    amsOps.reset();
   };
 
-  const isLoading = loadMutation.isPending || unloadMutation.isPending;
-
   // Handlers for modals and actions
   const handleHumidityClick = (humidity: number, temp: number) => {
     setHumidityModal({ humidity, temp });
   };
 
-  const refreshMutation = useMutation({
-    mutationFn: ({ amsId, trayId }: { amsId: number; trayId: number }) =>
-      api.refreshAmsTray(printerId, amsId, trayId),
-    onSuccess: (data, variables) => {
-      console.log(`[AMSSectionDual] Tray refresh response (AMS ${variables.amsId}, Tray ${variables.trayId}):`, data);
-      if (data.success) {
-        showToast(data.message || 'RFID refresh started', 'success');
-      } else {
-        showToast(data.message || 'Failed to refresh tray', 'error');
-      }
-    },
-    onError: (error, variables) => {
-      console.error(`[AMSSectionDual] Tray refresh error (AMS ${variables.amsId}, Tray ${variables.trayId}):`, error);
-      showToast('Failed to refresh tray', 'error');
-    },
-  });
-
   const handleSlotRefresh = (amsId: number, slotId: number) => {
-    // Trigger RFID re-read for the specific tray
-    console.log(`[AMSSectionDual] Slot refresh triggered: AMS ${amsId}, Slot ${slotId}, printerId: ${printerId}`);
-    // Reset the previous data ref so we can detect the next change
-    prevAmsDataRef.current = '';
-    // Show spinner immediately - will be cleared when AMS data updates from MQTT
-    const startTime = Date.now();
-    setRefreshingSlotState({ amsId, trayId: slotId, startTime });
-    refreshMutation.mutate({ amsId, trayId: slotId });
-    // Fallback timeout (15s) in case data doesn't change (e.g., same spool re-read)
-    setTimeout(() => {
-      setRefreshingSlotState(prev => {
-        if (prev && prev.startTime === startTime) {
-          console.log(`[AMSSectionDual] RFID refresh timeout for AMS ${amsId} tray ${slotId}`);
-          return null;
-        }
-        return prev;
-      });
-    }, 15000);
+    console.log(`[AMSSectionDual] Slot refresh triggered: AMS ${amsId}, Slot ${slotId}`);
+    amsOps.startRefresh(amsId, slotId);
   };
 
   const handleEyeClick = (tray: AMSTray, slotLabel: string, amsId: number) => {
     setMaterialsModal({ tray, slotLabel, amsId });
   };
 
-  // Determine if we're in a filament change state (from MQTT ams_status)
-  // ams_status_main: 0=idle, 1=filament_change, 2=rfid_identifying, 3=assist, 4=calibration
-  // mc_print_sub_stage: step indicator used by BambuStudio/OrcaSlicer for filament change progress
-  const amsStatusMain = status?.ams_status_main ?? 0;
+  // Show FilamentChangeCard when operation is in progress (LOADING or UNLOADING state)
   const isMqttFilamentChangeActive = amsStatusMain === 1;
-
-  // Auto-close card when operation completes
-  // Track when we transition from filament change active to idle (ams_status_main 1 -> 0)
-  const prevAmsStatusMainRef = useRef(amsStatusMain);
-  // Track if we've seen ams_status_main = 1 since the user clicked load/unload
-  // This prevents premature card closure on brief status glitches
-  const operationStartedRef = useRef(false);
-  useEffect(() => {
-    const wasActive = prevAmsStatusMainRef.current === 1;
-
-    if (isMqttFilamentChangeActive) {
-      // MQTT is now reporting filament change - operation has started
-      operationStartedRef.current = true;
-      // Clear user-triggered state, card will continue showing because isMqttFilamentChangeActive is true
-      setUserFilamentChange(null);
-    } else if (wasActive && !isMqttFilamentChangeActive && operationStartedRef.current) {
-      // Transition from active (1) to idle (0), AND we've confirmed operation started
-      // Close the card by clearing user state and the synchronous ref
-      console.log(`[AMSSectionDual] ams_status_main transitioned 1->0, operation was started, closing card`);
-      intendedOperationRef.current = null;
-      operationStartedRef.current = false;
-      setUserFilamentChange(null);
-    }
-
-    // Update previous status for next comparison
-    prevAmsStatusMainRef.current = amsStatusMain;
-  }, [isMqttFilamentChangeActive, amsStatusMain]);
-
-  // Show FilamentChangeCard when either MQTT reports active ams_status OR user just clicked load/unload
-  const showFilamentChangeCard = isMqttFilamentChangeActive || userFilamentChange !== null;
+  const showFilamentChangeCard = amsOps.state === 'LOADING' || amsOps.state === 'UNLOADING' || isMqttFilamentChangeActive;
 
   // Get the loaded tray info for wire coloring
   // Wire coloring should show the path from the currently loaded filament to the extruder
   // But ONLY if the currently displayed AMS panel is the one with the loaded filament
-  const trayNow = status?.tray_now ?? 255;
-
-  // Determine if loading or unloading for the card display
-  // Priority: 1) Synchronous ref (set immediately on click), 2) React state, 3) MQTT signals
-  // The ref prevents race conditions where MQTT updates arrive before React state updates
-  const amsStatusSub = status?.ams_status_sub ?? 0;
-  const SUB_RETRACT = 4; // Only happens during unload
-  const isFilamentLoading =
-    intendedOperationRef.current === 'load' ? true :
-    intendedOperationRef.current === 'unload' ? false :
-    userFilamentChange !== null ? userFilamentChange.isLoading :
-    !(amsStatusSub === SUB_RETRACT || trayNow === 255); // Unload if retracting or tray_now is 255
   const getLoadedTrayInfo = (): {
     leftActiveSlot: number | null;
     rightActiveSlot: number | null;
@@ -1235,8 +1095,10 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
 
   const { leftActiveSlot, rightActiveSlot, leftFilamentColor, rightFilamentColor } = getLoadedTrayInfo();
 
-  // Use state-based refreshing slot for spinner visibility (minimum 1.5s display time)
-  const refreshingSlot = refreshingSlotState;
+  // Create refreshingSlot object from hook state for spinner visibility
+  const refreshingSlot = amsOps.state === 'REFRESHING' && amsOps.context?.refreshTarget
+    ? { amsId: amsOps.context.refreshTarget.amsId, trayId: amsOps.context.refreshTarget.trayId }
+    : null;
 
   return (
     <div className="bg-bambu-dark-tertiary rounded-[10px] p-3">
@@ -1304,33 +1166,33 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
         <div className="flex-1" />
 
         <div className="flex items-center gap-2">
-          {/* Unload button: disabled if not connected, printing, mutation pending, or no filament loaded */}
+          {/* Unload button: disabled if not connected, printing, operation in progress, or no filament loaded */}
           <button
             onClick={handleUnload}
-            disabled={!isConnected || isPrinting || isLoading || trayNow === 255}
+            disabled={!isConnected || isPrinting || amsOps.isOperationInProgress || trayNow === 255}
             className={`px-7 py-2.5 rounded-lg text-sm transition-colors border ${
-              !isConnected || isPrinting || isLoading || trayNow === 255
+              !isConnected || isPrinting || amsOps.isOperationInProgress || trayNow === 255
                 ? 'bg-bambu-gray-dark text-gray-500 border-bambu-gray-dark cursor-not-allowed'
                 : 'bg-bambu-dark-secondary text-white border-bambu-dark-tertiary hover:bg-bambu-dark'
             }`}
           >
-            {unloadMutation.isPending ? (
+            {amsOps.state === 'UNLOADING' ? (
               <Loader2 className="w-4 h-4 animate-spin" />
             ) : (
               'Unload'
             )}
           </button>
-          {/* Load button: disabled if not connected, printing, mutation pending, no tray selected, or selected tray is already loaded */}
+          {/* Load button: disabled if not connected, printing, operation in progress, no tray selected, or selected tray is already loaded */}
           <button
             onClick={handleLoad}
-            disabled={!isConnected || isPrinting || selectedTray === null || isLoading || selectedTray === trayNow}
+            disabled={!isConnected || isPrinting || selectedTray === null || amsOps.isOperationInProgress || selectedTray === trayNow}
             className={`px-7 py-2.5 rounded-lg text-sm transition-colors border ${
-              !isConnected || isPrinting || selectedTray === null || isLoading || selectedTray === trayNow
+              !isConnected || isPrinting || selectedTray === null || amsOps.isOperationInProgress || selectedTray === trayNow
                 ? 'bg-bambu-gray-dark text-gray-500 border-bambu-gray-dark cursor-not-allowed'
                 : 'bg-bambu-dark-secondary text-white border-bambu-dark-tertiary hover:bg-bambu-dark'
             }`}
           >
-            {loadMutation.isPending ? (
+            {amsOps.state === 'LOADING' ? (
               <Loader2 className="w-4 h-4 animate-spin" />
             ) : (
               'Load'
@@ -1340,20 +1202,20 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
       </div>
 
       {/* Error messages */}
-      {(loadMutation.error || unloadMutation.error) && (
+      {(amsOps.loadError || amsOps.unloadError) && (
         <p className="mt-2 text-sm text-red-500 text-center">
-          {(loadMutation.error || unloadMutation.error)?.message}
+          {(amsOps.loadError || amsOps.unloadError)?.message}
         </p>
       )}
 
       {/* Filament Change Progress Card - appears during load/unload operations */}
       {showFilamentChangeCard && (
         <FilamentChangeCard
-          isLoading={isFilamentLoading}
+          isLoading={amsOps.isLoadOperation}
           amsStatusMain={amsStatusMain}
           amsStatusSub={status?.ams_status_sub ?? 0}
           trayNow={trayNow}
-          targetTrayId={userFilamentChange?.targetTrayId ?? null}
+          targetTrayId={amsOps.loadTargetTrayId}
           onComplete={handleFilamentChangeComplete}
         />
       )}

+ 42 - 26
frontend/src/components/control/useAmsOperations.ts

@@ -43,6 +43,7 @@ interface UseAmsOperationsProps {
   amsUnits: AMSUnit[];
   amsStatusMain: number;
   trayNow: number;
+  lastAmsUpdate: number;  // Backend timestamp for detecting AMS data updates
   onToast: (message: string, type: 'success' | 'error') => void;
 }
 
@@ -82,6 +83,7 @@ export function useAmsOperations({
   amsUnits,
   amsStatusMain,
   trayNow,
+  lastAmsUpdate,
   onToast,
 }: UseAmsOperationsProps): UseAmsOperationsReturn {
   const [state, setState] = useState<OperationState>('IDLE');
@@ -89,7 +91,10 @@ export function useAmsOperations({
 
   // Track previous values for transition detection
   const prevAmsStatusMainRef = useRef(amsStatusMain);
-  const prevTrayDataRef = useRef<string>('');
+  // Track initial tray data signature for detecting changes during refresh
+  const refreshInitialDataRef = useRef<string>('');
+  // Track initial lastAmsUpdate value at start of refresh (to detect when new data arrives)
+  const refreshInitialAmsUpdateRef = useRef<number>(0);
 
   // Timeout ref for cleanup
   const timeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -107,19 +112,10 @@ export function useAmsOperations({
     clearOperationTimeout();
     setState('IDLE');
     setContext(null);
-    prevTrayDataRef.current = '';
+    refreshInitialDataRef.current = '';
+    refreshInitialAmsUpdateRef.current = 0;
   }, [clearOperationTimeout]);
 
-  // Set up timeout for current operation
-  const startOperationTimeout = useCallback((timeoutMs: number) => {
-    clearOperationTimeout();
-    const startTime = context?.startTime ?? Date.now();
-    timeoutRef.current = setTimeout(() => {
-      console.log(`[useAmsOperations] Operation timed out after ${timeoutMs}ms`);
-      reset();
-    }, timeoutMs);
-  }, [clearOperationTimeout, reset, context?.startTime]);
-
   // === Mutations ===
 
   const refreshMutation = useMutation({
@@ -173,22 +169,24 @@ export function useAmsOperations({
       return;
     }
 
-    console.log(`[useAmsOperations] Starting refresh: AMS ${amsId}, Tray ${trayId}`);
-
-    // Capture current tray data signature for change detection
+    // Capture current tray data to detect changes
     const unit = amsUnits.find(u => u.id === amsId);
     const tray = unit?.tray?.find(t => t.id === trayId);
     if (tray) {
-      prevTrayDataRef.current = JSON.stringify({
+      refreshInitialDataRef.current = JSON.stringify({
         tag_uid: tray.tag_uid,
         tray_uuid: tray.tray_uuid,
-        tray_id_name: tray.tray_id_name,
         tray_type: tray.tray_type,
         tray_color: tray.tray_color,
       });
     }
 
+    // Capture initial lastAmsUpdate to detect when NEW data arrives
+    refreshInitialAmsUpdateRef.current = lastAmsUpdate;
+
     const startTime = Date.now();
+    console.log(`[useAmsOperations] Starting refresh: AMS ${amsId}, Tray ${trayId}, startTime=${startTime}, initialAmsUpdate=${lastAmsUpdate}`);
+
     setState('REFRESHING');
     setContext({ refreshTarget: { amsId, trayId }, startTime });
 
@@ -199,7 +197,7 @@ export function useAmsOperations({
     }, REFRESH_TIMEOUT_MS);
 
     refreshMutation.mutate({ amsId, trayId });
-  }, [state, amsUnits, reset, refreshMutation]);
+  }, [state, amsUnits, lastAmsUpdate, reset, refreshMutation]);
 
   const startLoad = useCallback((trayId: number, extruderId?: number) => {
     if (state !== 'IDLE') {
@@ -245,31 +243,49 @@ export function useAmsOperations({
 
   // === Completion Detection ===
 
-  // Detect REFRESH completion via tray data change
+  // Detect REFRESH completion by waiting for new AMS data to arrive
+  // RFID read takes 5-10 seconds. We detect completion when:
+  // 1. Data changed (new/different spool detected) - complete after minimum 1s
+  // 2. lastAmsUpdate timestamp changed from initial AND elapsed > 5 seconds
+  //    This means a new AMS data packet arrived after the RFID read should be done
   useEffect(() => {
     if (state !== 'REFRESHING' || !context?.refreshTarget) return;
 
     const { amsId, trayId } = context.refreshTarget;
+    const elapsed = Date.now() - context.startTime;
+
+    // Get current tray data
     const unit = amsUnits.find(u => u.id === amsId);
     const tray = unit?.tray?.find(t => t.id === trayId);
-
     if (!tray) return;
 
-    const currentSignature = JSON.stringify({
+    const currentData = JSON.stringify({
       tag_uid: tray.tag_uid,
       tray_uuid: tray.tray_uuid,
-      tray_id_name: tray.tray_id_name,
       tray_type: tray.tray_type,
       tray_color: tray.tray_color,
     });
 
-    // Require minimum 500ms to avoid false positives from initial render
-    const elapsed = Date.now() - context.startTime;
-    if (prevTrayDataRef.current && prevTrayDataRef.current !== currentSignature && elapsed > 500) {
+    // Check if data changed (new spool detected)
+    const dataChanged = refreshInitialDataRef.current && currentData !== refreshInitialDataRef.current;
+
+    // Check if lastAmsUpdate changed from when we started
+    const amsUpdateChanged = lastAmsUpdate !== refreshInitialAmsUpdateRef.current;
+
+    // Primary completion: data changed (new spool detected) - complete quickly
+    if (dataChanged && elapsed > 1000) {
       console.log(`[useAmsOperations] Refresh complete: data changed for AMS ${amsId} tray ${trayId} (took ${elapsed}ms)`);
       reset();
+      return;
+    }
+
+    // Secondary completion: new AMS update received after minimum wait time
+    // Wait 8 seconds to ensure RFID read has time to complete before considering updates
+    if (amsUpdateChanged && elapsed > 8000) {
+      console.log(`[useAmsOperations] Refresh complete: new AMS update after ${elapsed}ms for AMS ${amsId} tray ${trayId}`);
+      reset();
     }
-  }, [state, context, amsUnits, reset]);
+  }, [state, context, amsUnits, lastAmsUpdate, reset]);
 
   // Detect LOAD/UNLOAD completion via ams_status_main transition 1 → 0
   useEffect(() => {

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
static/assets/index-BHvBWY5n.js


+ 1 - 1
static/index.html

@@ -7,7 +7,7 @@
     <link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
     <link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
     <link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
-    <script type="module" crossorigin src="/assets/index-CLNJVLog.js"></script>
+    <script type="module" crossorigin src="/assets/index-BHvBWY5n.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BpSfhfce.css">
   </head>
   <body>

Vissa filer visades inte eftersom för många filer har ändrats