Browse Source

added history-tracking functions

Brandon Wong 1 year ago
parent
commit
87143e4d71
2 changed files with 534 additions and 36 deletions
  1. 500 0
      2025-03-30-2-claude-add-tedious-condition-check.md
  2. 34 36
      background.js

+ 500 - 0
2025-03-30-2-claude-add-tedious-condition-check.md

@@ -0,0 +1,500 @@
+## Me
+
+Here is some code from /home/brwong/projects/experiments/playlist/background.js:
+
+```javascript
+    const { [v]: existing, ...rest } = currentHistory;
+    await browser.storage.local.set({
+      history: {
+        [v]: {
+          ...existing,
+          duration:
+            !isNaN(existing.duration) && isFinite(existing.duration)
+              ? Math.max(message.duration, existing.duration)
+              : message.duration,
+          history: [
+            ...existing.history,
+            {
+              action: message.type,
+              position: message.timestamp,
+              timestamp: Date.now(),
+            },
+          ],
+        },
+        ...rest,
+      },
+    });
+```
+adjust this code so that if message type is "playing", and the action of the last item in history is "play", and if message.timestamp is equal to the position of the last item in history, then ignore (do nothing with) the current message
+
+## CodeCompanion (Anthropic)
+
+### Reasoning
+
+Alright, let's analyze what the code is doing first:
+
+1. It's extracting an object named `existing` from `currentHistory` based on a key `v`, and putting all other properties into a variable called `rest`.
+2. It's then setting a `history` object in the browser's local storage.
+3. Within this history object, it's updating the property with key `v` to include:
+   - All the properties from the existing object.
+   - A potentially updated `duration` based on the comparison between the existing duration and the message duration.
+   - An extended `history` array with a new entry containing the message type as the action, the message timestamp as the position, and the current timestamp.
+4. It's also including all other properties from `rest` in the history object.
+
+Now, the task is to modify this code so that it ignores (does nothing with) the current message if the following conditions are all met:
+1. `message.type` is "playing".
+2. The action of the last item in `existing.history` is "play".
+3. `message.timestamp` is equal to the position of the last item in `existing.history`.
+
+Let's modify the code:
+
+```javascript
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing.history.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  const { [v]: existing, ...rest } = currentHistory;
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...existing.history,
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+Wait, I need to revise this. There's a slight issue with my approach. I'm checking `shouldIgnore` condition but I'm using `existing` in the check before it's defined in the destructuring. Let me correct this:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing.history && existing.history.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...existing.history,
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+I also added a check to verify that `existing.history` exists before trying to access its `length`.
+
+This looks correct now. It first destructures to get the `existing` object, then checks the conditions for ignoring the message. If the conditions are met, it does nothing. Otherwise, it proceeds with the original logic of updating the history.
+
+Actually, let me check one more thing. In the original code, `message.type` is stored as the `action` in the history, but in the ignore condition, we're checking for the action of the last history item to be "play" and the current message type to be "playing". These seem to be different strings. I want to make sure I'm getting the condition right. Let me double-check the requirement:
+
+The requirement says:
+- If `message.type` is "playing",
+- And the action of the last item in history is "play",
+- And if `message.timestamp` is equal to the position of the last item in history,
+- Then ignore (do nothing with) the current message.
+
+Ah, I see. So we're checking if the last action was "play" (not "playing") and the current message type is "playing". 
+
+My code should be correct in that regard. Let's finalize the solution:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing.history && existing.history.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...existing.history,
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+This solution should satisfy the requirement. It checks if the current message type is "playing" and the last history action is "play" and if the timestamps match. If all conditions are met, it does nothing. Otherwise, it updates the history as before.
+
+One more check: I need to make sure `existing.history` exists before trying to access it. Otherwise, we might run into errors if `existing.history` is `undefined` or `null`. Let's add a check for that:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing && existing.history && existing.history.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...(existing.history || []),
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+On second thought, if `existing` or `existing.history` doesn't exist, then we shouldn't need to check if we should ignore the message because there is no "last history item" to compare with. In that case, we would always proceed with the original logic. Let me simplify the check:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const lastHistoryItem = existing && existing.history && existing.history.length > 0 ? existing.history[existing.history.length - 1] : null;
+const shouldIgnore = 
+  message.type === "playing" && 
+  lastHistoryItem &&
+  lastHistoryItem.action === "play" &&
+  message.timestamp === lastHistoryItem.position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...(existing.history || []),
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+I'm introducing a `lastHistoryItem` variable for better readability. If it doesn't exist, we set it to `null`, and then our `shouldIgnore` check will naturally be `false` because `null && anything` is `false`.
+
+But wait, I'm still not adding all the necessary checks:
+
+Actually, although this should work, I want to simplify it further for readability. Let's rewrite:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const lastHistoryItem = existing?.history?.length > 0 ? existing.history[existing.history.length - 1] : null;
+const shouldIgnore = 
+  message.type === "playing" && 
+  lastHistoryItem?.action === "play" &&
+  message.timestamp === lastHistoryItem?.position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...(existing?.history || []),
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+I'm using optional chaining (`?.`) to simplify null and undefined checks. If any of the properties in the chain doesn't exist, it returns `undefined` instead of throwing an error.
+
+Actually, I want to make sure the checks in the `shouldIgnore` condition are stricter. If `lastHistoryItem` is `null` or if `lastHistoryItem.action` or `lastHistoryItem.position` is `undefined`, the comparison won't throw an error because of optional chaining, but it might produce unexpected results. Let's be more explicit:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const lastHistoryItem = existing?.history?.length > 0 ? existing.history[existing.history.length - 1] : null;
+const shouldIgnore = 
+  message.type === "playing" && 
+  lastHistoryItem !== null &&
+  lastHistoryItem.action === "play" &&
+  message.timestamp === lastHistoryItem.position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...(existing?.history || []),
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+Ok, I think this is the final solution. Some of these variations are getting pedantic. Given the context, this solution should work well and is understandable.
+
+But actually, I think I misread the intent when I was introducing all these optional chaining checks. The original code reads `const { [v]: existing, ...rest } = currentHistory`, which is destructuring `currentHistory`. So `existing` is guaranteed to exist (though its properties might be undefined). And looking at the code, we add the history array to existing, which indicates `existing.history` should always exist. 
+
+Given the code is adding a history entry to an existing array, it's safe to assume the history array exists. But to be on the safe side, I'll add checks for both existence of `existing.history` and its length (which should indicate this is not the first entry).
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing.history && existing.history.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...existing.history,
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+This is the final solution. Given the destructuring, `existing` would never be `undefined`. The rest of the checks ensure we're properly examining the last history element.
+
+Actually I made a mistake again. The original code line `const { [v]: existing, ...rest } = currentHistory;` extracts `currentHistory[v]` and puts it in a variable named `existing`. I'm thinking about it incorrectly. It's extracting the property with the key that's the value of `v`. If that property doesn't exist, then `existing` would be undefined. So, I should add a check for it.
+
+Let me write the solution one last time:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  existing &&  // Check if existing exists
+  message.type === "playing" && 
+  existing.history && existing.history.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...(existing || {}),  // Use empty object if existing doesn't exist
+        duration:
+          existing && !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...(existing && existing.history ? existing.history : []),
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+But if I'm being that thorough, I'd need to check for the rest of the logic as well, which might get unwieldy. And looking more at the context, it seems like it's part of a function that already might expect these variables to be defined in a certain way.
+
+Let's stick with a more straightforward version, focusing on the main requirement:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing?.history?.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Proceed with the original logic
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...existing.history,
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+This should fulfill the requirement, and if there are any edge cases or mistakes in the assumptions made, the original context would probably have handled them differently or provided more information. Let's settle on this as the final solution.
+
+### Response
+
+To adjust the code so it ignores messages where the type is "playing", the last history action is "play", and the timestamps match:
+
+```javascript
+const { [v]: existing, ...rest } = currentHistory;
+
+// Check if we should ignore the current message
+const shouldIgnore = 
+  message.type === "playing" && 
+  existing?.history?.length > 0 && 
+  existing.history[existing.history.length - 1].action === "play" &&
+  message.timestamp === existing.history[existing.history.length - 1].position;
+
+if (!shouldIgnore) {
+  // Only proceed if we shouldn't ignore the message
+  await browser.storage.local.set({
+    history: {
+      [v]: {
+        ...existing,
+        duration:
+          !isNaN(existing.duration) && isFinite(existing.duration)
+            ? Math.max(message.duration, existing.duration)
+            : message.duration,
+        history: [
+          ...existing.history,
+          {
+            action: message.type,
+            position: message.timestamp,
+            timestamp: Date.now(),
+          },
+        ],
+      },
+      ...rest,
+    },
+  });
+}
+```
+
+This modification adds a condition check before updating the storage. If the message meets all three criteria for being ignored (type is "playing", last action was "play", and timestamps match), then the storage update is skipped entirely.
+
+Need any additional safety checks or error handling for this logic?
+
+## Me
+
+

+ 34 - 36
background.js

@@ -158,33 +158,41 @@ function navigateTab(tabId, url) {
 async function updateHistory(message) {
   const { history: currentHistory } =
     await browser.storage.local.get("history");
-  console.log("currentHistory", currentHistory);
   const q = new URL(message.url);
   const v = q.searchParams.get("v");
-  console.log("v??", message.url, q, q.searchParams.get("v"));
-  console.log("history?", currentHistory[v]);
   if (currentHistory[v]) {
     const { [v]: existing, ...rest } = currentHistory;
-    await browser.storage.local.set({
-      history: {
-        [v]: {
-          ...existing,
-          duration:
-            !isNaN(existing.duration) && isFinite(existing.duration)
-              ? Math.max(message.duration, existing.duration)
-              : message.duration,
-          history: [
-            ...existing.history,
-            {
-              action: message.type,
-              position: message.timestamp,
-              timestamp: Date.now(),
-            },
-          ],
+
+    const shouldIgnore =
+      message.type === "playing" &&
+      existing?.history?.length > 0 &&
+      (existing.history[existing.history.length - 1].action === "play" ||
+        existing.history[existing.history.length - 1].action === "playing") &&
+      message.timestamp ===
+        existing.history[existing.history.length - 1].position;
+
+    if (!shouldIgnore) {
+      await browser.storage.local.set({
+        history: {
+          [v]: {
+            ...existing,
+            duration:
+              !isNaN(existing.duration) && isFinite(existing.duration)
+                ? Math.max(message.duration, existing.duration)
+                : message.duration,
+            history: [
+              ...existing.history,
+              {
+                action: message.type,
+                position: message.timestamp,
+                timestamp: Date.now(),
+              },
+            ],
+          },
+          ...rest,
         },
-        ...rest,
-      },
-    });
+      });
+    }
   } else {
     await browser.storage.local.set({
       history: {
@@ -204,31 +212,21 @@ async function updateHistory(message) {
       },
     });
   }
-  console.log("proposed:", {
-    url: message.url,
-    title: message.title,
-    duration: message.duration,
-    history: [
-      {
-        action: message.type,
-        position: message.timestamp,
-        timestamp: Date.now(),
-      },
-    ],
-  });
 }
 
 // Listen for messages from popup or content scripts
 browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
   console.log("MESSAGE", message);
   switch (message.type) {
-    case "ended":
-      console.log("ENDED");
     case "play":
     case "playing":
     case "pause":
       updateHistory(message);
       break;
+    case "ended":
+      updateHistory(message);
+      console.log("ENDED");
+      break;
   }
   if (message.command === "navigate") {
     if (message.tabId && message.url) {