How can I prevent my script from timing out when writing formulas to a large file?

I’m trying to automate some changes in a file that has two versions: one with about 5,000 rows and another with around 80,000 rows.

When I run my script on the smaller file, it works fine. But when I run it on the larger file, it times out.

Originally, I used a for loop to write a formula into each row one by one. To improve performance, I switched to building an array of formulas first and then writing them all at once.

Here’s the relevant part of my code.

The argument sheet comes from spreadsheet.geSheetByName(sheetName).

The main sheet contains only data, number and letters, and this function tries to import information from other 3 sheets that I have on the file through VLOOKUP, and these other sheets contain only simple data as well, numbers and letters.

function insertNewColumns(sheet){
  Logger.log("Inserting new columns: Commited Projects, Skillsets and Country PPM!");

  var startRow = (SheetType.timeUnit === "YEAR") ? 20 : 21

  sheet.insertColumnAfter(2);
  var columnC = 3;
  sheet.getRange(startRow, columnC).setValue("Committed Projects");

  sheet.insertColumnAfter(7);
  var columnH = 8;
  sheet.getRange(startRow, columnH).setValue("Skillset");

  sheet.insertColumnAfter(13);
  var columnN = 14;
  sheet.getRange(startRow, columnN).setValue("Country PPM");

  var lastRow = sheet.getRange("A:A").getLastRow();
  var numRows = lastRow-startRow;
  var formulasC = [];
  var formulasH = [];
  var formulasN = [];

  for (var row = startRow+1; row <= lastRow; row++) {

    formulasC.push([`=IFERROR(VLOOKUP(value(A${row}), 'Committed Projects'!A:B, 2, 0), "XXXXX")`]);

    formulasH.push([`=IFERROR(VLOOKUP(G${row}, 'Skillset'!A:B, 2, 0), "XXXXX")`]);

    formulasN.push([`=IFERROR(VLOOKUP(M${row}, 'Country PPM'!A:B, 2, 0), "XXXXX")`]);
  }
  sheet.getRange(startRow + 1, columnC, numRows, 1).setFormulas(formulasC); // IT TIMES OUT HERE
  sheet.getRange(startRow + 1, columnH, numRows, 1).setFormulas(formulasH);
  sheet.getRange(startRow + 1, columnN, numRows, 1).setFormulas(formulasN);
  SpreadsheetApp.flush();
}

I first tried using a for loop to write each formula directly to the cell in every iteration. I expected this to work, but it was very slow for large files and timed out. To improve this, I changed my approach to build an array of all the formulas first and then write them to the sheet in one operation. I expected this to solve the timeout issue, but it still times out on the larger file with 80,000 rows.

How I can stop the script from timing out on the larger file?

Can I download all files from a selected directory using only drive.file permission?

I’m using the Google Drive Picker to let users upload files from their own Google Drive. I’m using the https://www.googleapis.com/auth/drive.file scope for permissions.

Users can manually select individual files using the Picker, and that part works fine. However, when I try to download those files using the URLs returned by the Picker (via a simple GET request), I get a 404 File not found error — and this happens for all file types.

My main question is: Is it possible to let users select a folder and then retrieve all the files inside that folder using only the drive.file permission?

I know this is possible with drive.readonly, but I’d prefer to avoid the extra security review required for that scope. From what I understand, drive.file only gives access to files explicitly selected by the user, which is what I want.

But when a user selects a folder, I’m unable to access its contents — I don’t know how (or if it’s even allowed) to list the files within that folder using only drive.file.

If this should work, here’s a simplified version of the code I’m using, including the download logic, Picker setup, and access token handling:

import useDrivePicker from "react-google-drive-picker";
import { useGoogleLogin } from "@react-oauth/google";

// Mapping for Google file types (Docs, Sheets, etc.) to export MIME types and extensions
const GOOGLE_FILES_MIMES = {
  "application/vnd.google-apps.document": {
    exportMimeType: "application/pdf",
    fileExtension: "pdf",
  },
};

const buildDownloadUrl = (file: CallbackDoc): string | null => {
  if (file.downloadUrl) return file.downloadUrl;

  if (file.mimeType.startsWith("application/vnd.google-apps")) {
    const exportMimeType = GOOGLE_FILES_MIMES[file.mimeType]?.exportMimeType;
    if (exportMimeType) {
      return `https://www.googleapis.com/drive/v3/files/${file.id}/export?mimeType=${exportMimeType}`;
    }
  }

  return `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`;
};

const downloadMultipleSelectedFiles = async (files: CallbackDoc[]) => {
  if (!accessToken) return;

  setIsUploading(true);
  setUploadingStarted(true);
  setTotalFiles(files.length);
  setProgress(0);

  const downloadedFiles = await Promise.all(
    files.map(async (file) => {
      const downloadUrl = buildDownloadUrl(file);
      if (!downloadUrl) return null;

      try {
        const blob = await downloadWithUrl(downloadUrl, accessToken);
        let fileName = file.name;

        const fileExtension = GOOGLE_FILES_MIMES[file.mimeType]?.fileExtension;
        if (fileExtension && !fileName.endsWith(`.${fileExtension}`)) {
          fileName += `.${fileExtension}`;
        }

        return { file: new File([blob], fileName), name: fileName };
      } catch (error) {
        console.error(`Failed to download ${file.name}:`, error);
        return null;
      } finally {
        setProgress((prev) => prev + 1);
      }
    })
  );

  const fileContainers = downloadedFiles
    .filter(Boolean)
    .map((f) => ({ file: f!.file, relativePath: f!.name }));

  if (fileContainers.length > 0) {
    addAllFilesToGroup("googleDrive", fileContainers);
    onFilesUploaded(fileContainers);
  }

  setIsUploading(false);
  setUploadingStarted(false);
  return fileContainers;
};

// Login using the drive.file scope
const login = useGoogleLogin({
  onSuccess: (response) => {
    setAccessToken(response.access_token);
    setTimeout(() => buttonRef.current?.click(), 1);
  },
  onError: (error) => console.error("Login error:", error),
  scope: "https://www.googleapis.com/auth/drive.file",
});

// Open the Google Picker
openPicker({
  clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
  developerKey: process.env.NEXT_PUBLIC_GOOGLE_DEVELOPER_KEY,
  viewId: "DOCS",
  showUploadView: true,
  showUploadFolders: true,
  supportDrives: true,
  multiselect: true,
  setIncludeFolders: false,
  setSelectFolderEnabled: false,
  setOrigin: window.location.origin,
  token: accessToken,
  callbackFunction: async (data) => {
    if (data.action !== "picked" || !data.docs.length || !accessToken) return;
    const fileContainers = await downloadMultipleSelectedFiles(data.docs);
    console.log("Downloaded:", fileContainers);
  },
});

It’s impossible to get the files because of missing access

{
  "error": {
    "code": 404,
    "message": "File not found: 1UVDy3bzMD5n0Ty2ovd6cOkX99vp7hMYx.",
    "errors": [
      {
        "message": "File not found: 1UVDy3bzMD5n0Ty2ovd6cOkX99vp7hMYx.",
        "domain": "global",
        "reason": "notFound",
        "location": "fileId",
        "locationType": "parameter"
      }
    ]
  }
}

How can I manually trigger garbage collection from an asynchronous javascript function?

I’m calling global.gc() in some unit tests, but for some reason it only seems to have any impact when called synchronously. If I call it asynchronously, I never seem to get a cleanup callback in my FinalizationRegistry.

Simplified demo case:

// Only works in Node >= v14.6.0 with --expose-gc
if (typeof gc !== 'function') {
    throw new Error('Run the test with --expose-gc flag');
}

let timeoutId;
const registry = new FinalizationRegistry(() => {
    clearTimeout(timeoutId);
    console.log("item was successfully garbage collected");
});
let element = {};
registry.register(element, 'item');

timeoutId = setTimeout(() => {
    console.error("item was not garbage collected in 3 seconds");
}, 3000);

element = null;
console.log("reference to item removed");

Promise.resolve().then(() => {
    console.log("trigger garbage collection...");
    gc();
});

JSBin link: https://jsbin.com/yobohelupe/edit?js,console (needs to be run with the –expose-gc flag)

I’ve tried both in nodejs 24 and chrome 138. Making the call to gc() synchronously works as I expect.

Please can someone help me understand what’s going wrong here? I’ve found the documentation about the gc() function much less comprehensive than other javascript functions.

[Aside: If it helps to understand my use case, I’m trying to write a test suite which checks that all my custom web components can be garbage collected, because I keep creating web components which cause big memory leaks. The use of FinalizationRegistry and global.gc() will only live in the tests, not the production code.]

How to remove a nested property from an object in Firestore

my firestore stores user data as below:

users (collection)
   uid-123 (document one per user)
     profile:
        name: 'My Name'
        options: {
                    x: 1,
                    y: 2,
                    z: 3
                 }

In a situation i want to remove y:2 from the profile.options. I am using like below:

async updateUserProfileMerged(uid: string, payload: any) {
      try{
          const userDocRef = doc(this.firestore, 'users/' + uid);
          return setDoc(userDocRef, Object.assign({},{"profile": payload}), { merge: true     });
        }catch(err){
          console.log("error in updateUserProfileMerged in data.service.ts:", err)
        }
    }

The passed payload in this case is:

 options: {
            x:1,
            z: 3
          }

This does not remove y as it is passing merge true. if i set merge to false then it will override the entire profile object. so how do i only override options?

How to show both calendar and type-in time input in one modal using react-native-modal-datetime-picker?

I’m using the react-native-modal-datetime-picker package in my React Native project and want to display a single modal that contains both:

A calendar to select the date

A text input (not a clock picker) to manually type in the time

However, the current behavior shows the date picker first, and only after pressing “OK”, it shows the time picker (clock-style). This results in two steps, which is not the UX I’m looking for.

import DateTimePickerModal from 'react-native-modal-datetime-picker';

<DateTimePickerModal
  isVisible={isDatePickerVisible1}
  mode={'datetime'}
  onConfirm={handleConfirm1}
  onCancel={hideDatePicker1}
  textColor="#00C47A"
  display={"inline"} // also tried 'default'
  is24Hour={!hourformat}
  minimumDate={duedatefield ? new Date() : undefined}
/>

Is this possible using react-native-modal-datetime-picker or patch or should I look for an alternative?

How to restart setInterval() with the same name on button?

There is a timer like this. The button should start and restart the timer. In the end, it restarts the timer, but the previous one continues, despite the same name… I read that the setInterval() function will work until the end and does not depend on other such functions, I tried clearInterval(), but it does not work. How can this be done?

function timer_start(){
  var ts = 60;
  let timer = setInterval(() => {
        if (ts > 0) {
            ts --;
            var h = ts/3600 ^ 0,
                m = (ts-h*3600)/60 ^ 0,
                s = ts-h*3600-m*60,
                time = (m<10?"0"+m:m)+":"+(s<10?"0"+s:s);
            $("#timer_").text(time);
        } else {
            clearInterval(timer);
        }
    }, 1000);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>


<div><strong>Timer</strong></div>
<div id="timer_">01:00</div>
<hr>
<button type="button" onClick="timer_start()">Timer Start</button>

Adaptive Card JS SDK: Add Properties to Default Types

I have an application that renderes AdaptiveCards in my own UI.
I have been using some default adaptive card examples from the new designer: https://adaptivecards.microsoft.com/designer

A lot of them use the targetWidth property.
When rendering them using the Microsoft Adaptive Card JS SDK it looks like it doesn’t support this property.

So I created my own custom Image Element that simply extends the default Image and overrides the existing default Image element according to the MS Docs

While my own renderer is now being called for images, the new property I added isn’t being processed:

static readonly targetWidthProperty = new StringProperty(AdaptiveCards.Versions.v1_0, "targetWidth");

@AdaptiveCards.property(CustomElement.targetWidthProperty)
targetWidth?: string;

If I register my component under a different name (e.g. CustomImage) and rename the Images in the json config to CustomImage by new property is processed properly.

It seems like the schema of default elements is somehow cached. If I try to override the Image type the populateSchema() isn’t called, if I use my own CustomImage it’s called.
I’ve tried everything, using my own SerializationContext etc. but can’t find a solution.

So how are we supposed to add missing features to the default components?

Why the toast.success is not working in my signup form

I’m trying to implement a signup form using the react-testify package to show success and error messages. While the toast. Error function works perfectly, the toast. Success function does not display the success message upon successful form submission.
Here’s the relevant code for my Signup component:

import axios from "axios";
import { useState } from "react";
import { toast, ToastContainer } from "react-toastify";
// import "../signup/signup.css";
import { Link, useNavigate } from "react-router-dom";
import "react-toastify/dist/ReactToastify.css";

const Signup = () => {
  const navigate = useNavigate();

  const [formData, setFormData] = useState({
    name: "",
    phone: "",
    email: "",
    password: "",
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevState) => ({
      ...prevState,
      [name]: value,
    }));
  };

  const handleSignup = async (e) => {
    e.preventDefault();

    if (
      !formData.name ||
      !formData.phone ||
      !formData.email ||
      !formData.password
    ) {
      toast.error("Please fill in all fields.", {
        position: "top-right",
        autoClose: 3000,
      });
      return;
    }

    try {
      const res = await axios.post("http://localhost:0000/example", formData);
      console.log("Response from server:", res.data);

      toast.success("Form Submitted Successfully", {
        position: "top-right",
        autoClose: 5000,
      });

      setFormData({
        name: "",
        phone: "",
        email: "",
        password: "",
      });

      setTimeout(() => {
        navigate("/signin");
      }, 5000);
    } catch (error) {
      toast.error("Something went wrong. Please try again.", {
        position: "top-right",
        autoClose: 5000,
      });
    }
  };

  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-100">
      <ToastContainer />
      <div className="bg-white shadow-lg rounded-lg w-full max-w-lg p-8">
        <h2 className="text-lg font-bold text-center mb-6">Create Account</h2>
        <form onSubmit={handleSignup}>
          <div className="mb-4">
            <input
              type="text"
              className="w-full border border-gray-300 rounded-md px-4 py-2"
              name="name"
              placeholder="Enter Full Name"
              value={formData.name}
              onChange={handleChange}
            />
          </div>
          <div className="mb-4">
            <input
              type="text"
              className="w-full border border-gray-300 rounded-md px-4 py-2"
              name="phone"
              placeholder="Enter Phone"
              value={formData.phone}
              onChange={handleChange}
            />
          </div>
          <div className="mb-4">
            <input
              type="email"
              className="w-full border border-gray-300 rounded-md px-4 py-2"
              placeholder="Enter Email"
              name="email"
              value={formData.email}
              onChange={handleChange}
            />
          </div>
          <div className="mb-6">
            <input
              type="password"
              className="w-full border border-gray-300 rounded-md px-4 py-2"
              placeholder="Enter Password"
              name="password"
              value={formData.password}
              onChange={handleChange}
            />
          </div>
          <div>
            <input
              type="submit"
              className="w-full bg-blue-600 text-white rounded-md py-2 cursor-pointer"
              value="Signup"
            />
          </div>
          <div className="text-center mt-4">
            <span>
              Already have an account?
              <Link to="/signin" className="text-blue-600 ps-1">
                Login Here
              </Link>
            </span>
          </div>
        </form>
      </div>
    </div>
  );
};

export default Signup;

Angular *ngIf not removing element even when condition becomes false DOM keeps adding duplicate elements

I’m working on an Angular app (v14+), and I’m facing a strange issue with *ngIf inside a component.

Inside my component template (post-card.component.html), I have this simple test markup:

<span>{{ post.showComments }}</span>
<span *ngIf="post.showComments">Amazing....!</span>

Output :
true Amazing....! Amazing....! Amazing....! Amazing....! Amazing....! Amazing....! Amazing....! Amazing....! Amazing....!

post is an @Input() coming from the parent posts.component.ts like this:

<app-post-card
  *ngFor="let post of posts; let i = index; trackBy: trackByPostId"
  [post]="post"
  [index]="i"
  [showComments]="post.showComments"
  ...
></app-post-card>

In the parent, I toggle post.showComments using immutable update:

this.posts = this.posts.map((p, i) => {
  if (i === index) {
    return { ...p, showComments: !p.showComments };
  }
  return p;
});

post.showComments is toggled correctly. I see the true/false value update inside the component.

ngOnChanges() logs the expected value transitions.

trackByPostId returns a stable and unique feedID string.

Ensured trackBy is correct and returns unique, stable ID

Verified that only one app-post-card is rendered per post (confirmed via logs)

Used ng-template + ViewContainerRef with manual clear() and destroy()

Logged ngOnInit and ngOnDestroy — component is not being re-instantiated

Confirmed no DOM manipulation or animation libraries are modifying the view

Expected behavior:
When post.showComments becomes false, Angular should destroy the *ngIf block and remove the from DOM. But instead, the DOM keeps accumulating duplicates.

Is there any scenario where *ngIf inside a child component doesn’t destroy its view even though the input becomes false? Is Angular’s view caching interfering due to deep nested components or incorrect diffing?

Any ideas or debugging strategies to isolate this would be super helpful.

How to control/record the order FileReader uploads files in?

I’ve created a basic JSON object as a request to ChatGPT then I loop through a bunch of images selected via an input tag and add them dynamically to the object for it to have a look at the images and write alt tag content for them.

Because the upload is async and not in any particular order in the filereader.onloadend event I count the total number of files uploaded and when it gets to last file it I POST it to ChatGPT’s api.

Problem is the alt tag content isn’t returned in the same order I uploaded it. Currently I have an array which I add the count to and then assign them based on the order of that array.

It does work, but only about 70% of the time.


            
                var nLoopIds = [];
                
                const files = document.getElementById("fileImages").files
                for (let i = 0; i < files.length; i++) 
                {
                    const fileReader = new FileReader();
                    
                    fileReader.onloadend = function(event) 
                    {       
                        // Add to data object
                        data.response_format.json_schema.schema.properties["alt_" + i] = 
                        {
                            "type": "string"
                        };
                        data.response_format.json_schema.schema.required.push("alt_" + i);
                        const b64 = event.target.result.split(",")[1]; // Extract base64 data
                        var image_url = 
                        {
                          "url": "data:image/jpeg;base64," + b64
                        }
                        var image = 
                        {
                          "type": "image_url",
                          "image_url": image_url
                        }
                        data.messages[1].content.push(image);
                        
                        // Add to show order uploaded 
                        nLoopIds.push(i);
                    
                        // Post to ChatGPT
                        if (nCount == files.length - 1)
                        {
                            // Send the POST request using fetch
                            fetch("https://api.openai.com/v1/chat/completions", {
                                method: "POST",
                                headers: {
                                    "Authorization": "Bearer api-key",
                                    "Content-Type": "application/json"
                                },
                                body: JSON.stringify(data)
                            })
                            .then((response) => response.json())
                            .then((data) => loadText(data, nLoopIds))
                            .catch((error) => alert(error));
                            
                        }
                        
                    }
                    
                    fileReader.readAsDataURL(files[i]); // Read selected file as Data URL
                } 

This is the code that receives the JSON response from ChatGPT.

        function loadText(data, nLoopIds)
        {
            var obj = JSON.parse(data["choices"][0]["message"]["content"]);
            
            document.getElementById("strTitle").value = obj["title"];
            document.getElementById("strShort").value = obj["short"];
            document.getElementById("strDescription").value = obj["description"];
            document.getElementById("strTheme").value = obj["theme"];
            document.getElementById("strTags").value = obj["tags"];
            
            for (let i = 0; i < nLoopIds.length; i++) 
            {
                document.getElementById("imgAlt-" + nLoopIds[i]).value = obj["alt_" + i];
            }
        }

Plus the original data object.

                var data = {
                    
                    model: "<?php echo $strModel; ?>",
                    //user: "<?php echo $strUser; ?>",
                    response_format: 
                    {
                        "type": "json_schema",
                        "json_schema": 
                        {
                            "name": "generate_project",
                            "strict": true,
                            "schema": 
                            {
                                "type": "object",
                                "properties": 
                                {
                                    "title": 
                                    {
                                        "type": "string"
                                    },
                                    "short": 
                                    {
                                        "type": "string"
                                    },
                                    "description": 
                                    {
                                        "type": "string"
                                    },
                                    "theme": 
                                    {
                                        "type": "string"
                                    },
                                    "tags": 
                                    {
                                        "type": "string"
                                    }
                                },
                                "required": ["title", "short", "description", "theme", "tags"],
                                "additionalProperties": false
                            }
                        }
                    },
                    messages: [
                    {
                        role: "system",
                        content: "<?php echo $strPromptBody; ?>"
                    },
                    {
                        role: "user",
                        content: [
                        {
                            "type": "text",
                            "text": document.getElementById("strPrompt").value
                        }]
                    }]
                };

How can I prevent the entire React app from crashing when an invalid component is rendered?

I have a React app where I have a main component EntireApp, which renders a child component Foo. The Foo component conditionally renders either Bar or Baz based on its iconId prop. However, when the iconId passed to Foo is invalid (for example, something like “foz” sent from the server), the entire application crashes, showing the “Application error: a client-side exception has occurred” error in the browser with a blank screen.

Here’s an MRE

const Bar = () => <div>Bar</div>;
const Baz = () => <div>Baz</div>;

const Foo = ({ iconId }) => {
  const components = [
    { icon: 'Bar', iconId: 'bar', component: Bar },
    { icon: 'Baz', iconId: 'baz', component: Baz },
  ];

  const iconData = components.find(c => c.iconId === iconId);

  /// @ts-ignore // intentionally ignore type to illustrate the issue
  const ComponentToRender:(() => React.JSX.Element) = iconData?.component;
  return <ComponentToRender />;
};

const EntireApp = () => {
  return (
    <div>
      <div>Something more important and healthy component</div>
      <Foo iconId="baz" /> {/* Invalid iconId that should not crash the entire app */}
    </div>
  );
};

How can I prevent the entire React app from crashing when an invalid component is rendered ?

I get

Application error: a client-side exception has occurred while loading
localhost (see the browser console for more information).

And a blank page in prod. I don’t really care about the icon breaking. How to tell React:

Hey, if one component is invalid, do not destroy my entire app. Just don’t render that broken component ?

I tried reactStrictMode: false, in next.config.js but this does nothing.

I also looked all of these, but they talk about the reason of the error, nothing about preventing an entire app crash. Next.js Application error: a client-side exception has occurred (see the browser console for more information)

Card Carousel Slider Scrolling

I have created a carousel slider with swipe function and also with side navigation. But with the side navigation I need to scroll 2 cards per click. And also with the pagination, even though I am with the last slide, the pagination is still on the second last active dot.

Carousel Slider Script

document.addEventListener('DOMContentLoaded', () => {
            const carousel = document.getElementById('promoCarousel');
            if (!carousel) return;

            const viewport = carousel.querySelector('.carousel-viewport');
            const nextButton = carousel.querySelector('.carousel-nav.next');
            const prevButton = carousel.querySelector('.carousel-nav.prev');
            const cards = viewport.querySelectorAll('.card');

            // --- JS-Powered Navigation for CSS Scroll Snap ---
            function updateNavButtons() {
                // Hide/show buttons based on scroll position
                // A small tolerance is added to account for subpixel rendering
                const tolerance = 2;
                prevButton.style.display = viewport.scrollLeft <= tolerance ? 'none' : 'flex';
                const isAtEnd = viewport.scrollLeft + viewport.clientWidth >= viewport.scrollWidth - tolerance;
                nextButton.style.display = isAtEnd ? 'none' : 'flex';
            }

            function scrollCarousel(direction) {
                // Find the width of the first card to determine the scroll amount
                const cardWidth = cards.length > 0 ? cards[0].offsetWidth : 0;
                const scrollAmount = (cardWidth + 12) * direction; // card width + margin
                viewport.scrollBy({ left: scrollAmount, behavior: 'smooth' });
            }

            nextButton.addEventListener('click', () => scrollCarousel(1));
            prevButton.addEventListener('click', () => scrollCarousel(-1));
            
            // Update nav buttons on scroll, load, and resize
            viewport.addEventListener('scroll', updateNavButtons);
            window.addEventListener('resize', updateNavButtons);
            updateNavButtons();

Pagination Script

const paginationContainer = document.getElementById('carouselPagination');
const cardsPerPage = () => {
    const width = window.innerWidth;
    if (width >= 992) return 3;
    if (width >= 768) return 2;
    return 1;
};

function createPagination() {
    paginationContainer.innerHTML = ''; // Clear previous dots

    cards.forEach((card, index) => {
        const dot = document.createElement('div');
        dot.classList.add('dot');
        if (index === 0) dot.classList.add('active');
        dot.dataset.index = index;
        paginationContainer.appendChild(dot);

        dot.addEventListener('click', () => {
            const cardWidth = card.offsetWidth + 0; // card width + margin
            const scrollAmount = cardWidth * index;
            viewport.scrollTo({ left: scrollAmount, behavior: 'smooth' });
        });
    });
}

function updateActiveDot() {
    const scrollLeft = viewport.scrollLeft;
    const cardWidth = cards[0].offsetWidth + 12;

    const index = Math.round(scrollLeft / cardWidth);
    const dots = paginationContainer.querySelectorAll('.dot');

    dots.forEach(dot => dot.classList.remove('active'));
    if (dots[index]) dots[index].classList.add('active');
}

// Recreate pagination on load and resize
createPagination();
window.addEventListener('resize', () => {
    createPagination();
    updateActiveDot();
});

viewport.addEventListener('scroll', updateActiveDot);

Tooltip from @radix-ui/react-tooltip not unmounting when using tailwind-animate

I am using @radix-ui/react-tooltip library in react to create a tooltip component. When I use tailwind-animate library, the tooltip popup does not disappear after the cursor moves away from the trigger button (the popup does not seem to get unmounted). If I translate the same animation code to raw css, it works fine. What could be the issue with tailwind-animate? The tailwind-animate version is 0.2.10 and tailwindcss version is 4.1.11.

Here is the wrapper that I am using as Tooltip content (appears as popup when the cursor hovers over the trigger button:

import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { twMerge } from 'tailwind-merge';
import { Caption, SmallerText } from '../Typography';
import { TooltipProps } from './types';
import { tooltipContentBaseStyles } from './styles';
import { FontWeight } from '../../../constants/stringConstants';

const Tooltip = TooltipPrimitive.Root;
const TooltipProvider = TooltipPrimitive.Provider;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipArrow = TooltipPrimitive.Arrow;

type TooltipContentProps = React.ComponentPropsWithoutRef<
  typeof TooltipPrimitive.Content
> &
  Omit<TooltipProps, 'supportingContent' | 'children'>;

const TooltipContent = React.forwardRef<
  React.ElementRef<typeof TooltipPrimitive.Content>,
  TooltipContentProps
>((props, ref) => {
  const {
    className,
    title,
    description,
    side,
    align,
    children,
    hasPointer,
    sideOffset = 4,
    alignOffset = -20,
    ...restProps
  } = props;

  const tooltipContentClassNames = twMerge(
    tooltipContentBaseStyles,
    'rounded-[8px] shadow-lg',
    'bg-white dark:bg-primary-gray-900',
    'text-black dark:text-white',
    className,
  );

  const tooltipContentContainerClassNames = twMerge(
    'max-w-[320px] flex flex-col plr-[2px] ptb-[8px]',
    'rounded-[8px]',
    'gap-[4px]',
  );

  const tooltipArrowClassNames = twMerge(
    'text z-20 fill-gray-700 text-gray-700',
    'fill-white dark:fill-primary-gray-900',
  );

  return (
    <TooltipPrimitive.Content
      ref={ref}
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      className={tooltipContentClassNames}
      side={side}
      align={align}
      {...restProps}
    >
      <section className={tooltipContentContainerClassNames}>
        <article className="mt-2 w-full space-y-1">
          <Caption text={title} weight={FontWeight.SEMI_BOLD} />
          <SmallerText text={description} />
        </article>
        {children}
      </section>
      {hasPointer && (
        <TooltipArrow
          className={tooltipArrowClassNames}
          height={6}
          width={12}
        />
      )}
    </TooltipPrimitive.Content>
  );
});

TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export {
  Tooltip,
  TooltipTrigger,
  TooltipContent,
  TooltipProvider,
  TooltipArrow,
};

the code for tooltipContentBaseStyles (the animation classes below are causing the issue, when i translate them to raw css and apply that css to tooltip content, it works fine):

export const tooltipContentBaseStyles = twMerge(
  'z-50 overflow-hidden',
  'px-3 py-1.5 text-sm',
  'animate-in fade-in-0 zoom-in-95',
  'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
  'data-[side=bottom]:slide-in-from-top-2',
  'data-[side=left]:slide-in-from-right-2',
  'data-[side=right]:slide-in-from-left-2',
  'data-[side=top]:slide-in-from-bottom-2',
);

Best practices for communicating between components in the same VS Code extension

I’m writing a VS Code extension that has several UI and non-UI components like tree view providers, status bar items, registries of internal state etc. All of them are created in the extension’s activate() function. Some of them need to communicate between themselves, getting some data, modifying state etc. It would be pretty straightforward if all of these components had a reference to each other but there seems to be no clear way to organize this:

  • exporting instances from activate() seems to be more suitable for inter-extension communication;
  • singleton instances (which I’m currently doing) are somewhat smelly and also are single per process, while in the future if I want to support multiple open workspaces I wil need one instance per workspace;
  • I could pass instances of components to constructors of other components in activate() which is probably the cleanest way to code this but cumbersome to extend, especially if two components need to call each other;
  • ExtensionContext.workspaceState and ExtensionContext.globalState are for persistence and likely don’t even support instances of arbitrary classes;
  • rummaging through ExtensionContext.subscriptions is very smelly to me.

So what are best practices of keeping some globally accessible class instances available to all code of an extension?

browser sequentially sending data to server with sleep

having large amount of browser data – json sent to the server side, so wanna to chunk the data and send to the server sequentially with sleep 1 seconds between each sending.

have tried two ways:
way 1:

  let url = "http://localhost:3000/api/v1/test";
  let chunkSize = 100;
  const chunks = data.reduce((acc, _, i) => {
    if (i % chunkSize === 0) acc.push([]);
    acc[acc.length - 1].push(array[i]);
    return acc;
  }, [] as T[][]);

  console.log('chunks',chunks);

  for (const chunk of chunks) {
    console.log( 'chunk',chunk.length);
     await fetch( url , {
                  method: 'POST',
                  body:  JSON.stringify( chunk ) ,
                  "Content-type": "application/json; charset=UTF-8"
                  }
    });
    await setTimeout(function(){},1000);    
    console.log( 'chunk',chunk.length);

however when testing, the process gets stuck when sending the first chunk, no error showing up, just stuck.

second way:

    let url = "http://localhost:3000/api/v1/test";
    let chunkSize = 100;
    const chunks = data.reduce((acc, _, i) => {
        if (i % chunkSize === 0) acc.push([]);
        acc[acc.length - 1].push(array[i]);
        return acc;
      }, [] as T[][]);

  return Promise.all(chunks.map(async chunk => {
     console.log( 'chunk',chunk.length);
     await setTimeout(function(){},1000);  // not working at all
     return await fetch( url , {
                  method: 'POST',
                  body:  JSON.stringify( chunk ) ,
                  headers: {
                  "Content-type": "application/json; charset=UTF-8"
                  }
    });

  }));

the problem with this above is await setTimeout(function(){},1000); never sleeps.