Reviewing Kodular Blocks: `Evaluate JS` with `WebViewStringChange` for Play Integrity Flow

I have implemented a system in my Kodular app to securely load PDFs from a private Supabase bucket, using the Play Integrity API for verification. I would be grateful for a review of my block logic to ensure it’s correct.

My Process:

  1. The app calls WebViewer1.Evaluate JS to execute a function in a local HTML file.
  2. The JavaScript gets a Play Integrity token and fetches a signed URL from my Cloudflare Worker.
  3. The Worker returns a JSON string (e.g., {"status":"success", ...}).
  4. The JavaScript uses window.AppInventor.setWebViewString() to send this JSON string back to the app.
  5. I expect to handle this response in the When Web_Viewer1.WebViewStringChange event to parse the JSON and load the PDF.

Is my logic of triggering Evaluate JS and then handling the response in WebViewStringChange correct? I want to confirm if this is the proper way to implement this flow before I move to live testing.

Thank you for your expertise.

HtmlService include() function breaks ES6 syntax in included files – template processing issue

I’m experiencing syntax errors when trying to modularize a Google Apps Script HTML project using the standard include() pattern with HtmlService.createHtmlOutputFromFile().getContent(). The issue appears to be related to how the HTML service processes included files as templates rather than raw content.

Problem Description
I have a working Google Apps Script web app that uses modern ES6+ JavaScript syntax (arrow functions, const/let, template literals, destructuring, etc.). The code works perfectly when written directly in the main index.html file, but breaks when I try to extract it to separate modules using the recommended include pattern.

Current Setup
Code.gs:

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function doGet() {
  return HtmlService.createTemplateFromFile("index").evaluate()
    .setTitle("My App")
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

index.html:

<!DOCTYPE html>
<html>
<head>
  <title>My App</title>
</head>
<body>
  <!-- This works fine -->
  <?!= include('my-module') ?>
</body>
</html>

The Problem
When I create a separate file my-module containing ES6 syntax:

my-module:

 <script>
// This breaks when included
const myFunction = () => {
  console.log("Arrow function");
};

const myObj = { a: 1, b: 2 };
const { a, b } = myObj;  // Destructuring

const template = `Template literal ${a}`;
</script>

I get syntax errors like:

Uncaught SyntaxError: Failed to execute ‘write’ on ‘Document’: Unexpected token ‘;’

What I’ve Discovered

  • ES6 works in main file: The same code works perfectly when written directly in index.html
  • ES5 works in modules: Using var, function declarations, and avoiding arrow functions works in included files
  • Template processing suspected: The errors suggest the HTML service is processing the included files as Apps Script templates, interpreting {} and => as template syntax
  • Recent issue: This wasn’t a problem when the project was smaller and everything was in one file

What I’ve Tried

  • Using <?= include('file') ?> (printing scriptlet)
  • Using <?!= include('file') ?> (force-printing scriptlet)
  • Different file extensions
  • Wrapping content in different HTML tags

Questions

  • Is this expected behavior? Does HtmlService.createHtmlOutputFromFile().getContent() always process files as templates?

  • Is there a workaround to include raw JavaScript files without template processing?

  • Why does ES6 work in the main file but not in included files, even though Apps Script supposedly supports ES6 now?

  • Are there alternative patterns for code modularization in Google Apps Script HTML projects that don’t have this limitation?

The project has grown large enough that keeping everything in one file breaks my development workflow, but the syntax restrictions make modularization painful.

Why is it in when I’m returning a while loop, followed by a do while loop, followed by a for loop, the 1st one returns while the other 2 don’t? [duplicate]

let x=1;

while (x!=5){
  x+=1;
  return console.log(x);
  };

do {
 x+=1;
 return console.log(x);
 } while (x<5);

for (y=0; y<5; y+=1){
  return console.log(y);
};

// In the console, outputs only 1

Once I removed return from the while statement the do while statement ran.

Note this is not within a function or method.

Google Apps Script Bar Chart Missing Legend Titles

I’m using Google Apps Script to generate a bar chart that visualizes year-over-year changes across multiple columns. The script successfully plots the values and correctly sets the X-axis for the “Year.” However, the chart legend is missing the expected labels—it’s showing only the color swatches without any titles (e.g., “Column 1”, “Column 2”, etc.).

The code runs without any errors, and the chart appears otherwise accurate. How can I modify the script to ensure that legend titles are displayed correctly for each data series?

Google Sheets Visual:
Google Sheets Visual

JS Code (Apps Script):

function createYoYBarCharts() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const lastRow = sheet.getLastRow();

  const titlesToReplace = ["YoY $ Change", "YoY % Change"];

  // === Remove ONLY the charts with matching titles
  const existingCharts = sheet.getCharts();
  existingCharts.forEach(chart => {
    const options = chart.getOptions();
    const title = options && options.get("title");
    if (titlesToReplace.includes(title)) {
      sheet.removeChart(chart);
    }
  });

  // === Helper to build chart using full rectangular range
  function buildChart(title, fullRangeA1, anchorCell) {
    const chart = sheet.newChart()
      .asColumnChart()
      .setTitle(title)
      .addRange(sheet.getRange(fullRangeA1)) // full rectangular block
      .setPosition(anchorCell.getRow(), anchorCell.getColumn(), 0, 0)
      .setOption("legend", { position: "top" })
      .setOption("hAxis", { title: "Year" })
      .setOption("vAxis", { title: title.includes('%') ? 'Change (%)' : 'Change ($)' })
      .setOption("isStacked", true)
      .setOption("useFirstRowAsHeaders", true)
      .setOption("useFirstColumnAsDomain", true)
      .build();

    sheet.insertChart(chart);
  }

  // === Calculate number of data rows (non-empty years in J7:J)
  const dataStartRow = 7;
  const dataHeaderRow = 6;
  const yearCol = sheet.getRange("J7:J").getValues();
  const numDataRows = yearCol.filter(row => row[0] !== "").length;
  const dataEndRow = dataStartRow + numDataRows - 1;

  // === 1. YoY $ Change Chart
  const dollarRange = `J${dataHeaderRow}:O${dataEndRow}`;
  const anchor1 = sheet.getRange(`J${dataEndRow + 2}`);
  buildChart("YoY $ Change", dollarRange, anchor1);

  // === 2. YoY % Change Chart
  const percentRange = `Q${dataHeaderRow}:V${dataEndRow}`;
  const anchor2 = sheet.getRange(`Q${dataEndRow + 2}`);
  buildChart("YoY % Change", percentRange, anchor2);
}

3D animation of a book opening – unexpected rotation

I know I’m missing something with the rotation of the inside cover when the book is “clicked on” to open, but I am not seeing it.

The expectation is that when the book is clicked on, the following happens:

  1. The cover of the book mirrors across the Y-axis
  2. The first page is shown on the right with an border that resembles the background of the back cover.
  3. The inside cover is shown as a solid cover that matches the background color of the front cover.
  4. The pages then flip across the Y-axis

All in all I am trying to get a 3D Animation of a book opening when it is clicked on by the user.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Opening Book with GSAP</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f0f4f8;
            overflow: hidden;
        }

        .font-serif {
            font-family: 'Playfair Display', serif;
        }

        /* The scene is the 3D space for the book */
        .scene {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100vh;
            perspective: 2500px;
        }
        
        /* The wrapper handles positioning and can be clicked */
        .book-wrapper {
             position: relative;
             cursor: pointer;
        }

        /* The book container holds all the 3D pieces */
        .book {
            width: 350px;
            height: 500px;
            position: relative;
            transform-style: preserve-3d;
        }
        
        /* The front cover, which will flip open */
        .front-cover {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            transform-origin: left center; 
            transform-style: preserve-3d;
            z-index: 10;
        }
        
        .cover-face {
            position: absolute;
            width: 100%;
            height: 100%;
            backface-visibility: hidden;
            border-radius: 0.5rem;
            background-color: #4a3a32;
        }

        .cover-face-front {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 2rem;
            position: relative;
            overflow: hidden;
        }
        
        /* The inside of the cover matches the outside */
        .cover-face-back {
            background-color: #4a3a32; 
            transform: rotateY(-180deg);
        }

        /* Crease styles */
        .cover-face-front::before,
        .cover-face-front::after {
            content: '';
            position: absolute;
            top: 0;
            height: 100%;
            width: 2px;
            background-color: rgba(0, 0, 0, 0.2);
            box-shadow: 1px 0 5px rgba(0,0,0,0.35);
        }

        .cover-face-front::before {
            left: 31px;
        }
        
        .cover-face-front::after {
            left: 35px;
        }
        
        /* NEW: This is the actual back cover board */
        .back-cover {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            background-color: #4a3a32;
            border-radius: 0.5rem;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            z-index: 1;
        }

        /* The static page block that sits on top of the back cover */
        .pages {
            position: absolute;
            width: calc(100% - 1rem);
            height: calc(100% - 1rem);
            top: 0.5rem;
            left: 0.5rem;
            background-color: #f3f0e9;
            border-radius: 0.25rem;
            z-index: 5;
        }
        
        /* Styles for the flipping pages */
        .flipping-page {
            position: absolute;
            width: calc(100% - 1rem);
            height: calc(100% - 1rem);
            top: 0.5rem;
            left: 0.5rem;
            background-color: #fdfaf2;
            transform-origin: left center;
            border-radius: 0.25rem;
            box-shadow: 2px 0 5px rgba(0,0,0,0.1) inset;
            border-right: 1px solid #e0d9cd;
            z-index: 6;
        }

        /* Decorative elements */
        .cover-design {
            border: 4px double #d4af37;
            width: 100%;
            height: 100%;
            border-radius: 0.25rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            text-align: center;
            padding: 1rem;
            color: #d4af37;
        }
        .cover-title {
            font-family: 'Playfair Display', serif;
            font-size: 2.75rem;
            font-weight: 700;
            letter-spacing: 1px;
            text-shadow: 1px 1px 3px rgba(0,0,0,0.4);
        }
        .cover-author {
            margin-top: 1.5rem;
            font-size: 1.125rem;
            font-style: italic;
            border-top: 1px solid rgba(212, 175, 55, 0.5);
            padding-top: 1.5rem;
        }

    </style>
</head>
<body>

    <div class="scene">
        <!-- The new wrapper handles positioning -->
        <div id="book-wrapper" class="book-wrapper">
            <!-- The book itself only handles 3D animations -->
            <div id="book" class="book">
                <!-- NEW: Added a dedicated back cover element -->
                <div class="back-cover"></div>
                <div class="pages"></div>
                <div class="flipping-page" id="page-3"></div>
                <div class="flipping-page" id="page-2"></div>
                <div class="flipping-page" id="page-1"></div>
                <div class="front-cover">
                    <div class="cover-face cover-face-front">
                         <div class="cover-design">
                            <h1 class="cover-title">About the Author</h1>
                            <p class="cover-author"></p>
                        </div>
                    </div>
                    <div class="cover-face cover-face-back"></div>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        // Select the elements to animate
        const bookWrapper = document.getElementById('book-wrapper');
        const frontCover = document.querySelector('.front-cover');
        const flippingPages = gsap.utils.toArray('.flipping-page');

        // Set the default transform origin for the cover and pages
        gsap.set([frontCover, flippingPages], { transformOrigin: "left center" });

        // Create a GSAP timeline, paused by default
        const timeline = gsap.timeline({ paused: true });

        // Add animations to the timeline
        timeline
            // 1. Move the entire book wrapper to the right to center the spine
            .to(bookWrapper, {
                x: 175,
                duration: 1.2,
                ease: "power2.inOut"
            })
            // 2. Flip the cover open at the same time
            .to(frontCover, {
                rotationY: -180,
                duration: 1.2,
                ease: "power2.inOut"
            }, "<") // "<" starts at the same time as the previous animation
            // 3. Flip the pages with a stagger effect, starting slightly after the cover begins to open
            .to(flippingPages, {
                rotationY: -180,
                duration: 0.8,
                ease: "power1.inOut",
                stagger: 0.1
            }, "<0.2"); // "<0.2" starts 0.2s after the previous animation's start

        // Event listener to control the timeline
        bookWrapper.addEventListener('click', () => {
            if (timeline.reversed() || timeline.progress() === 0) {
                timeline.play();
            } else {
                timeline.reverse();
            }
        });
    </script>

</body>
</html>

Images: Before and after, respectively.
Closed book

Open book

How to retrieve element from on popstate event?

I am working on Angular 16 and have want to push event related data to backend system. For example when user clicks on button then will pass some information about the event.

I have two pages FirstPage and SecondPage and using Angular router to navigate between them. When FirstPage rendered it register all the events using javascript. Now user clicks on given link and land to SecondPage, URL changed to FirstPage to SecondPage.

But if user clicks the back button of browser, then initially I was expecting the document.load or window.load event will fire but it’s not. Now, If I am trying to get the element on the popstate event it giving me null. How can I get the element?

window.addEventListener("popstate", (event) => registerEvents(event));

window.addEventListener("load", (event) => registerEvents(event));

function registerEvents(e){
    console.log(e);
    let substanceBtn = document.getElementById("substance");
    console.log(substanceBtn)
    if(null != substanceBtn){
        console.log('1');
        substanceBtn.addEventListener("click", function(e){
            console.log(e.target);
        });
    }
}

First Page:
<nav class="navbar navbar-expand-lg navbar-light bg-light bg-primary" style="background-color: #e3f2fd !important;">
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
                <a class="nav-link" href="#" id="test" routerLink="/secondPage">Second Page</a>
            </li>
        </ul>
    </div>
</nav>
<router-outlet></router-outlet>

Second Page:

<div><h1>Substances</h1></div>

<div class="row">
    <div class="col-sm-1">Second Page</div>
</div>

Swap vs concatenation in javascript

In this example I am swapping first and last characters of a given string .
Which approach is more faster ? provided that the string length is 3 characters long .


function concatenation(string){

    let newString = string[2]+string[1]+string[0];
    console.log(newString);
}

function swap(string) {

    let array = string.split('');
    let last = array[2];
    let temp = array[0];
    array[0]=last;
    array[2] =temp;
    console.log(array.join(''));
}


swap("ABC");
concatenation("ABC");

How can I create a function that takes any number of tables as arguments? (Excel Script Lab)

I have this function in Excel Script Lab:

Libraries

https://appsforoffice.microsoft.com/lib/1/hosted/office.js
@types/office-js

[email protected]/dist/css/fabric.min.css
[email protected]/dist/css/fabric.components.min.css

[email protected]/client/core.min.js
@types/core-js

[email protected]
@types/[email protected]

Script:

/**
 * Create a list.
 * @customfunction
 * @param name string
 * @param args {string[]}
 */
function datatablex(display: string, ...args: string[]): any {
  const entity = {
    type: "Entity",
    text: display,
    properties: {
      Nombre: {
        type: "String",
        basicValue: display,
        propertyMetadata: {
          excludeFrom: {
            cardView: true
          }
        }
      }
    }
  };

  for (let i = 0; i < args[0].length; i += 2) {
    const name = args[0][i + 0];
    const value = args[0][i + 1];

    entity.properties[name] = {
      type: "String",
      basicValue: value
    };
  }
  return entity;
}

This function allows me to create a DataType: Entity with the parameters given in the function as arguments. Currently, this function takes a string which will be the displayed text in the cell, and then any number of arguments. These extra arguments always come in couples. The first is the name of the value and the second is the value itself. This repeats many times if you add many more parameters. Below is an image showing an example:

Current Example

My problem is that the extra arguments are currently strings and instead I want them to be tables (two dimensions each). I tried making changes in the function:

  • Replace the string[] with string[][] and even string[][][]
  • Replace the property “String” type to “Array” and basicValue to elements.
  • Replace the args[0][i + 0] stuff with more [].
    However, I couldn’t make it work. Sometimes it gives #Value! and others “Calc! depending on what I change. Below is an image explaining the imputs I want:

Example imputs

Below is an example of the output I want (I created it manually, not using the function cause obviously it doesn’t work)

Example output

Example when clicking one of the tables.

Example ouput inside

Refresh marker google maps on apache server

Can’t refresh marker only. Without refreshing whole google map. Location is sent every 10 seconds from esp32 to apache server. I just can’t refresh markers only. The only way to refresh marker position is to refresh whole map0 which is not my goal. I’d like to refresh only markers without reloading whole map or website.

Please HELP. =)

<?php foreach($rows as $row){ ?>


    var location = new google.maps.LatLng(<?php echo $row['lat']; ?>, <?php echo $row['lng']; ?>);
    function refreshMarker(){

// if marker exists and has a .setMap method, hide it
  function SetMarker(location, map) {
    //Remove previous Marker.
    if (marker == null) {
        marker.setMap(null);
    }
}
    //marker.setMap(map);
    var marker = new google.maps.Marker({
        position: location,
        icon: betw,
        title:"<?php echo $row['lat']; ?>, <?php echo $row['lng']; ?> , <?php echo $row['time']; ?> , <?php echo $row['altitude']; ?> m, <?php echo $row['speed']; ?> km/h, <?php echo $row['temperature']; ?> °C",
        map: map
    });
    
 setTimeout(refreshMarker, 1000);}
 refreshMarker();

            
<?php } ?>  

🔍 Need Suggestions for Beginner-Friendly Open Source Repositories

I’m a beginner in open source contributions and looking for beginner-friendly repositories where I can make meaningful contributions.
So far, I’ve contributed to freeCodeCamp and The Odin Project. Now, I’m hoping to work on projects where I can contribute more actively and improve my skills through real collaboration.

Languages I’m comfortable with:

  • JavaScript

  • TypeScript

If you know any good repositories (with good documentation, beginner-friendly issues, or active maintainers), I’d love your suggestions.

TradingView Lightweight Chart crosshair tooltip time

type here

I am creating a algo trading platform which uses tradingView lightweight chart. I have now run into a issue wherein all the time shown on x axis is correctly being displayed in IST and the tooltip correctly shows date as per IST but shows the time in UTC.

chart.subscribeCrosshairMove(param => {
  const div = document.getElementById('crosshairTimeDisplay');
  if (!param.time) {
    div.textContent = '';
    return;
  }

  const utcDate = new Date(param.time * 1000);
  const istTime = new Date(utcDate.getTime() + (5.5 * 3600 * 1000));
  const day = String(istTime.getDate()).padStart(2, '0');
  const month = istTime.toLocaleString('en-IN', { month: 'short' });
  const year = istTime.getFullYear();
  const hours = String(istTime.getHours()).padStart(2, '0');
  const minutes = String(istTime.getMinutes()).padStart(2, '0');
  const seconds = String(istTime.getSeconds()).padStart(2, '0');

  div.textContent = `${day}-${month}-${year} ${hours}:${minutes}:${seconds}`;
});

and

chart.subscribeCrosshairMove(param => {
  const div = document.getElementById('crosshairTimeDisplay');
  if (!param.time) {
    div.textContent = '';
    return;
  }

  const date = new Date(param.time * 1000);
  const options = {
    timeZone: 'Asia/Kolkata',
    year: 'numeric', month: 'short', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
    hour12: false
  };
  const istString = date.toLocaleString('en-IN', options);

  div.textContent = istString;
});

and

chart.subscribeCrosshairMove(param => {
  const div = document.getElementById('crosshairTimeDisplay');
  if (!param.time) {
    div.textContent = '';
    return;
  }

  const date = new Date(param.time * 1000);
  const formatter = new Intl.DateTimeFormat('en-IN', {
    timeZone: 'Asia/Kolkata',
    year: 'numeric',
    month: 'short',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false
  });

  div.textContent = formatter.format(date);
});

Puppeteer-extra, TimeoutError doesn’t seem to exist

This is a followup question to this one, which resolves a missing definition of TimeoutError in my puppeteer code. Thing is, I want to use puppeteer-extra, and despite being a lightweight wrapper for puppeteer it seems to be causing a difficulty in getting hold of TimeoutError. Here’s an SSCCE that should demonstrate the problem; I get TypeError: Right-hand side of 'instanceof' is not an object. My package.json states version 23.7.1 (I have no idea what version of puppeteer-extra I’m running as I don’t know where to look).

const puppeteer = require("puppeteer-extra");

let browser;
(async () => {
  const stealthPlugin = require('puppeteer-extra-plugin-stealth');
  puppeteer.use(stealthPlugin());

  browser = await puppeteer.launch();
  const [page] = await browser.pages();

  try {
    await page.goto("https://www.example.com", {timeout: 1}); // short timeout to force a throw
  }
  catch (err) {
    if (err instanceof puppeteer.TimeoutError) {
      console.log("caught timeout error", err);
    }
    else {
      console.log("caught non-timeout error", err);
    }
  }
})()
  .catch(err => console.log(err))
  .finally(() => browser?.close());
  

(I’m using console.log() instead of console.error() because my client code can’t get hold of anything in the error stream)

Grateful thanks for any help resolving this!

How to avoid stale closures inside useEffect with setInterval in React?

I’m trying to use setInterval inside a useEffect hook in a React functional component to perform a repeated task (like updating a counter or fetching data periodically). However, the callback inside setInterval seems to use stale state — it’s not accessing the most recent value of my state variable.

Even though the state updates correctly, the interval callback always logs the initial state instead of the updated one. This leads to unexpected behavior in my app.

import React, { useState, useEffect } from 'react';

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      console.log('Current count:', count); // This always logs 0
      setCount(prev => prev + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <div>{count}</div>;
}

In this, console.log(count) always logs 0, even though the UI shows the count increasing.

What I tried

  • Moving setInterval outside the useEffect (not possible because I need lifecycle management).

  • Including count in the dependency array — but that causes multiple intervals to be created.

  • Using useRef to store the latest state — seems promising, but not sure how to implement it correctly.

What I want to know

  • Why does this stale closure issue happen in useEffect?

  • What’s the correct and efficient way to ensure that the interval callback always has access to the latest state?

  • Is useRef the right approach? Are there other React best practices to handle this?