How to Replace a Box Player Entity with a GLB Model in Noa.js + Babylon.js Game

I’m currently working on a game built using Noa.js and Babylon.js. In the existing entity.js file from the project template, the player is created using a simple box mesh.

I’m trying to replace this box with a .glb model to serve as the main player character. I’ve successfully loaded the GLB model into the scene, but it spawns separately and doesn’t inherit the behavior or properties of the existing player (like movement, controls, or collision handling).

What’s the correct way to replace the current box mesh used for the player entity with a GLB model, while ensuring it functions as the main player (movement, collision detection, and controls) within the Noa.js engine?

I’ll attach my current implementation for reference in the comments — would really appreciate if someone could review it and help me identify where I’m going wrong.

Thanks in advance!

Here is my code:

import * as BABYLON from '@babylonjs/core';
import '@babylonjs/loaders';

import { noa } from './engine';
import { setMeshShadows } from './shadows';
import { blockIDs } from './registration';
import blastImg from './assets/blast.png';

let sheepMesh = null;
let gameActive = true;
let explosionSound = new Audio('./assets/explosion.mp3');

const npcSheepEntities = [];

/**
 * Convert 3D mesh position to 2D screen coordinates.
 */
function toScreenPosition(mesh, scene) {
  if (!mesh || !scene || !scene.activeCamera) {
    return { x: window.innerWidth / 2, y: window.innerHeight / 2 };
  }

  const worldMatrix = mesh.getWorldMatrix();
  const transformMatrix = scene.getTransformMatrix();
  const camera = scene.activeCamera;

  const engine = scene.getEngine();
  const viewport = camera.viewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight());

  const projected = BABYLON.Vector3.Project(
    mesh.position,
    worldMatrix,
    transformMatrix,
    viewport
  );

  return { x: projected.x, y: projected.y };
}

/**
 * Display a blast effect at given screen position.
 */
function showBlastEffect(screenX = window.innerWidth / 2, screenY = window.innerHeight / 2) {
  return new Promise((resolve) => {
    const blastElement = document.createElement('img');
    blastElement.src = blastImg;
    blastElement.style.position = 'fixed';
    blastElement.style.top = `${screenY}px`;
    blastElement.style.left = `${screenX}px`;
    blastElement.style.transform = 'translate(-50%, -50%)';
    blastElement.style.pointerEvents = 'none';
    blastElement.style.zIndex = '9999';
    blastElement.style.width = '300px';
    blastElement.style.height = '300px';
    blastElement.style.opacity = '1';
    blastElement.style.transition = 'opacity 1s ease-out';

    document.body.appendChild(blastElement);

    setTimeout(() => { blastElement.style.opacity = '0'; }, 300);
    setTimeout(() => {
      document.body.removeChild(blastElement);
      resolve();
    }, 1100);
  });
}

/**
 * Load a GLB model and attach it to a given entity.
 * Mesh follows entity position in game loop.
 */
async function loadAndAttachGLBToEntity(entityId, modelPath, modelFilename, options = {}) {
  const scene = noa.rendering.getScene();
  const dat = noa.entities.getPositionData(entityId);
  const { width: w, height: h, position: pos } = dat;

  const {
    scaleMultiplier = 1,
    yOffset = 0
  } = options;

  return new Promise((resolve, reject) => {
    BABYLON.SceneLoader.ImportMesh(
      null,
      modelPath,
      modelFilename,
      scene,
      (meshes) => {
        const visualMesh = meshes.find(m => m.getTotalVertices && m.getTotalVertices() > 0) || meshes[0];
        if (!visualMesh) return reject(new Error('No visible mesh found in GLB model.'));

        visualMesh.position.set(pos[0], pos[1] + h / 2 + yOffset, pos[2]);
        visualMesh.scaling = new BABYLON.Vector3(w, h, w).scale(scaleMultiplier);
        visualMesh.material = noa.rendering.makeStandardMaterial();
        visualMesh.visibility = 1;

        noa.entities.addComponent(entityId, noa.entities.names.mesh, {
          mesh: visualMesh,
          offset: [0, h / 2 + yOffset, 0],
        });

        setMeshShadows(visualMesh, true);

        if (entityId === noa.playerEntity) {
          sheepMesh = visualMesh;
        }

        noa.on('beforeRender', () => {
          if (!gameActive) return;
          const data = noa.entities.getPositionData(entityId);
          if (!data) return;
          visualMesh.position.set(
            data.position[0],
            data.position[1] + yOffset,
            data.position[2]
          );
          if (entityId === noa.playerEntity) {
            checkFenceCollision(data.position[0], data.position[1], data.position[2]);
          }
        });

        resolve(visualMesh);
      },
      null,
      (scene, message, exception) => {
        console.error('Error loading GLB model:', message, exception);
        reject(exception);
      }
    );
  });
}

/**
 * Collision detection between player and fence blocks.
 */
function checkFenceCollision(x, y, z) {
  if (!gameActive) return;
  const blockBelow = noa.getBlock(x, y, z);
  const blockAtFeet = noa.getBlock(x, y + 0.5, z);
  if (blockBelow === blockIDs.fence || blockAtFeet === blockIDs.fence) {
    endGame();
  }
}

/**
 * Update sheep counter in UI.
 */
function updateSheepCount() {
  const sheepCountElement = document.querySelector('#sheep-counter .counter-value');
  if (sheepCountElement) {
    sheepCountElement.textContent = npcSheepEntities.length.toString();
  }
}

/**
 * End the game — hide sheep mesh, play sound and show blast.
 */
async function endGame() {
  if (!gameActive) return;
  gameActive = false;

  const scene = noa.rendering.getScene();
  let screenPos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
  if (sheepMesh) {
    screenPos = toScreenPosition(sheepMesh, scene);
    sheepMesh.setEnabled(false);
  }

  explosionSound.play();
  await showBlastEffect(screenPos.x, screenPos.y);

  document.getElementById('end-game-screen').style.display = 'flex';
  document.getElementById('game-ui').style.display = 'none';
}

/**
 * Spawn a single NPC sheep entity.
 */
async function spawnNPCSheep(startX, startY, startZ) {
  const eid = noa.entities.add([startX, startY, startZ], 0.5, 1.2);
  npcSheepEntities.push(eid);

  await loadAndAttachGLBToEntity(eid, '/castle/', 'sheep.glb', {
    scaleMultiplier: 0.4,
    yOffset: 0,
  });

  updateSheepCount();

  // Give sheep circular wandering movement
  const centerX = startX + (Math.random() - 0.5) * 100;
  const centerZ = startZ + (Math.random() - 0.5) * 100;
  const radius = 5 + Math.random() * 15;
  const angularSpeed = 0.0005 + Math.random() * 0.001;
  const angleOffset = Math.random() * 2 * Math.PI;

  noa.on('beforeRender', () => {
    if (!gameActive) return;
    const time = performance.now();
    const angle = angleOffset + angularSpeed * time;
    const x = centerX + Math.cos(angle) * radius;
    const z = centerZ + Math.sin(angle) * radius;
    const currentY = noa.entities.getPosition(eid)[1];
    noa.entities.setPosition(eid, [x, currentY, z]);
  });
}

/**
 * Spawn a herd of NPC sheeps around player.
 */
async function spawnNPCSheepHerd(centerX, centerY, centerZ) {
  for (let i = 0; i < 5; i++) {
    const randX = centerX + (Math.random() - 0.5) * 160;
    const randZ = centerZ + (Math.random() - 0.5) * 160;
    await spawnNPCSheep(randX, centerY, randZ);
  }
}

/**
 * Initialize player entity and NPC herd after game tick.
 */
noa.once('tick', async () => {
  try {
    const eid = noa.playerEntity;
    if (!eid) throw new Error('Player entity not found');

    // Replace box mesh with GLB model for player
    await loadAndAttachGLBToEntity(eid, '/castle/', 'sheep.glb', {
      scaleMultiplier: 0.4,
      yOffset: 0
    });

    // Adjust camera follow position for player sheep
    const sheepHeight = noa.ents.getPositionData(eid).height;
    const eyeOffset = 0.8 * sheepHeight;

    noa.ents.removeComponent(noa.camera.cameraTarget, 'followsEntity');
    noa.ents.addComponent(noa.camera.cameraTarget, 'followsEntity', {
      entity: eid,
      offset: [0, eyeOffset, 0],
    });

    // Spawn NPC herd
    const pos = noa.entities.getPosition(eid);
    await spawnNPCSheepHerd(pos[0], pos[1], pos[2]);

    console.log('Player sheep loaded and NPC herd spawned.');
  } catch (error) {
    console.error('Failed to initialize game:', error);
  }
});

https://github.com/fenomas/noa-examples

I clone the code from here

Is it possible to config our SCSS class like the ones in tailwind css especially for media query. ‘lg:items-center’ like this way

/* Width in percentage properties */
$opacityPercentages:
  10 10%,
  20 20%,
  33 33%,
  50 50%,
  55 55%,
  60 60%,
  70 70%,
  80 80%,
  85 85%,
  100 100%;

@each $opacityName, $opacityValue in $opacityPercentages {
  .opacity-#{$opacityName} {
    opacity: #{$opacityValue};
  }
}

/* Position relative properties */
$position: relative, absolute, fixed, sticky;

@each $positionName in $position {
  .position-#{$positionName} {
    position: $positionName !important;
  }
}

is it possible to write our own media query class just like tailwind. This particular field has not yet been configured in my project. Help me to achieve it

Open for your answers

coreUI : CSidebar under CHeader

Using the latest version of coreUI, CSidebar takes up 100% height of the webpage while the CHeader, CContent, and CFooter take up the space the right.

https://coreui.io/demos/react/5.3/free/?theme=light#/dashboard

However, I am after a layout where the CHeader takes up the whole width of the top while the CSidebar, CContent, and CFooter take up the space below it:

Is this possible?

https://codesandbox.io/p/sandbox/coreui-admin-v3-ndlzq5

enter image description here

Argument of type ‘string’ is not assignable to parameter of type ‘linkType’

I am trying to map through my data but in my onChange function I keep getting the error below, can anyone help?

Argument of type ‘string’ is not assignable to parameter of type ‘linkType’

Component:

  type linkType = 'breakdown' | 'insurance' | 'lite';

  {data &&
    data.accountCoverTypes?.map((button, index) => (
      <>
        {index === data.accountCoverTypes.length - 1 ? (
          <Divider
            ...
          />
        ) : null}
        <HighlightButton
          key={index}
          ...
          onPress={() => onChange(button.link, index)}
          ...
        />
      </>
    ))}

Data:

export const data = {
  title: 'What type of cover do you have?',
  accountCoverTypes: [
    { link: 'breakdown', title: 'Breakdown' },
    { link: 'insurance', title: 'Car insurance' },
    { link: 'lite', title: "I don't have any RAC cover" },
  ],
};

Openlayer clustered feature style not being updated

I implemented cluster in my openlayer map.
Everything work as it should, (the way it work without cluster) except for selection.
The issue here is that the clustering breaks the selected style from my single selected feature. The selection is good, but the style is like desync when it get updated.

Every X seconds, I update my featur on my map :

   let newPoint = new Point([trackable.lon, trackable.lat]).transform('EPSG:4326', 'EPSG:3857')
     if (this.trackingConfigParams?.ui_map_clustering_enabled) {
    layer?.getSource()?.getFeatures()?.forEach((featureCluster)=> {
      if (feature) {
        return
      }
      feature = featureCluster?.get('features')?.find((f)=> {
        return f.get('tracking_id') === trackable.tracking_id
      })
    })
  }
     feature.setGeometry(newPoint)
     feature.setProperties({ x: value })

When I check the function call stack in the code without the cluster strategy, the “setGeometry” function seems to calls the style function located in the select function from openlayer, which allow to keep the selected style applied on the element

createSelectInteraction(trackingConfig) {
    const self = this
    let features = null
    this.selectInteraction = new Select({
      style: this.selectedFeatureStyle.bind(this)
    // ...etc

But whener I switch to the cluster strategy, this function is not automatically called anymore. I suspect the feature inside feature to cause the issue, breaking the reactivity, but I have no idea how to fix this.

The behaviour is the following :

  • I select my cluster feature (only for single feature cluster, with non cluster style) on map, it changes the style to the selected style.
  • My feature get updated, a new feature is created with new coordinates
  • my selected feature is still displayed, but has not moved, therefore, I have 2 duplicated feature, one selected who has not been updated (old coordinates) with selected style, and the other new one which has not selected style but has updated coordinates

Note that my selection are on special layer

 const overlay = new VectorLayer({
      style: null,
      source: new VectorSource({
        features: this?.selectInteraction?.getFeatures()
      }),
      zIndex: 5000
    })

I tried to changes the features from my selection layer, but did not help
I have no idea how i could fix this ATM

Any help welcome

Why is my Spring Boot POST controller returning HTTP 400 when sending data via Ajax?

I’m having trouble posting a JSON array to a Spring Boot @RequestMapping controller. Get 400 Bad Request. Any suggestions how to correct?

html code

  console.log("send:", JSON.stringify(resultList));
  $.ajax({
    url : '/oper_list', 
    type : 'post', 
    data : JSON.stringify(resultList), 
    contentType : "application/json; charset=utf-8",
    // headers: {"Accept": "application/json"},  
    // xhrFields: { withCredentials: true },
    success: (data) => alert(data),
    error: (xhr) => {
      console.error("error:", xhr.responseText)
      // console.log(xhr)
    }
  });

@controller
controller

@PostMapping("/oper_list")
@ResponseBody
public String insertOperTimeList(
    @RequestBody List<BusinessDateVO> operList
        /*, @AuthenticationPrincipal CustomManager manager*/
) {
        System.out.println("ajax to controller");
        /*if (manager == null || manager.getManager() == null) {
                return "no account";
        }*/
        if (operList == null || operList.isEmpty()) {
                return "no operList";
        }
        System.out.println("opertimelist: " + operList);
        

        //int rtNum = manager.getManager().getRm_rt_num();
        int rtNum = 1;
        int result = 0;
        for (BusinessDateVO oper : operList) {
                oper.setBd_rt_num(rtNum); 
                if (oper.getBd_date() == null || oper.getBd_open() == null || oper.getBd_close() == null) {
                        continue; 
                }

                managerService.makeOperTime(oper); // insert
                result++;
        }

        return "completed" + result + "counts";
}

configurations

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "root") 
public class WebMvcConfig implements WebMvcConfigurer{

    @Value("${root}")
    String uploadPath;

    public void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler("/file/**").addResourceLocations("file:///"+uploadPath);
        registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/");
    }

    @Override
    public void addCorsMappings(org.springframework.web.servlet.config.annotation.CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080") 
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true); 
    }
    @Override
    public Validator getValidator() {
            return new LocalValidatorFactoryBean();
    }

/////////////////////////////////////////////////////////////////////////////////////

@Configuration
@EnableWebSecurity
public class SecurityConfig{

  @Autowired
  private ManagerDetailService managerDetailService;

  @Value("${root}")
  private String rememberMeKey;

  @Bean
    @Order(1)
    public SecurityFilterChain managerSecurityFilterChain(HttpSecurity http) throws Exception {
      http
      .securityMatcher("/manager/**", "/schedule/**")
      .csrf(csrf ->csrf.disable())
      .authorizeHttpRequests(auth -> auth
          .requestMatchers("/manager/signup", "/manager/register").permitAll()
          .anyRequest().authenticated()
      )
      .formLogin(form -> form
          .loginPage("/manager/login")
          .loginProcessingUrl("/manager/login")
          .defaultSuccessUrl("/manager/")
          .permitAll()
      )
      .userDetailsService(managerDetailService)
      .rememberMe(rm-> rm
        .userDetailsService(managerDetailService)
        .key(rememberMeKey)/
        .rememberMeCookieName("LC_manager")
        .tokenValiditySeconds(60 * 60 * 24 * 100)
      )

      .logout(logout -> logout
          .logoutUrl("/manager/logout")
          .logoutSuccessUrl("/manager/")
          .clearAuthentication(true)
          .invalidateHttpSession(true)
          .permitAll());
  
      return http.build();
    }

error code

The request fails with a 400 Bad Request, but the error is swallowed silently in the backend cosole logs.

make_opertime:208 error: {"timestamp":1747989098092,"status":400,"error":"Bad Request","path":"/manager/make_opertime_list"}
error   @   make_opertime:208
c   @   jquery-3.7.1.min.js:2
fireWith    @   jquery-3.7.1.min.js:2
l   @   jquery-3.7.1.min.js:2
(anonymous) @   jquery-3.7.1.min.js:2
XMLHttpRequest.send     
send    @   jquery-3.7.1.min.js:2
ajax    @   jquery-3.7.1.min.js:2
(anonymous) @   make_opertime:199
dispatch    @   jquery-3.7.1.min.js:2
v.handle    @   jquery-3.7.1.min.js:2

//////////////////////////////////////////////////////////////////////

and

@Data
public class BusinessDateVO {

    private int bd_num;

    private String bd_date;
    private int bd_rt_num;
    private boolean bd_off;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    private String  bd_open;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    private String  bd_close;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    private String  bd_brstart;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    private String  bd_brend;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    private String  bd_loam;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    private String  bd_lopm;
    

        // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    @JsonIgnore
    private Timestamp  bd_open_ts;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    @JsonIgnore
    private Timestamp  bd_close_ts;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    @JsonIgnore
    private Timestamp  bd_brstart_ts;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    @JsonIgnore
    private Timestamp  bd_brend_ts;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    @JsonIgnore
    private Timestamp  bd_loam_ts;

    // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
    @JsonIgnore
    private Timestamp  bd_lopm_ts;



/////////////////////////////////////////////

    @JsonIgnore
    private String  bd_local_date;

    
    // public String getBd_date(){
    //  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    //  return format.format(bd_open);
    // }

    
}

Reading `STDIN` on Windows in MSYS2

Setup

  1. Symfony Console application.
  2. Packed into a Phar with Box.
  3. Distributed as an executable for Win, Linux, and MacOS with PHPMicro.

Execution

  1. MSYS2 on Win11.
  2. make import calls app import < "$filename" in a loop for an ordered list of inputs.
    1. Alternatively, cat "$filename" | app import.

Problem

Cannot read from STDIN: ftell(STDIN) returns false. There’s no data in the stream. Similar story with fopen('php://stdin', 'r').

winpty

Without winpty, I see my debug output, and can e.g. inspect the stream (that’s how I know it’s empty).

With winpty, none of that happens, and instead I get:

$ winpty un1q import:generic < datafile.csv
stdin is not a tty

Remarks

Works fine on Linux. I don’t really understand how MSYS2 works, especially with streams and pipes. From what I understood, it should transform my Bash syntax into something it runs in CMD or PowerShell (none of which I’m very familiar with).

MySQL 8 choosing sub-optimal indexing on large table despite suitable indexing

I’m working on a Laravel 12 project running MySQL 8.4. I have various models like Buyer, BuyerTier, Application and PingtreeGroup and I want to store raw transactional models in my PingtreeTransaction model.

This table will store around 500,000 entries a day, it’s schema, minus indexing looks like:

Schema::create('pingtree_transactions', function (Blueprint $table) {
    $table->ulid('id');
    $table->foreignId('company_id');
    $table->foreignId('application_id');
    $table->foreignId('buyer_id');
    $table->foreignId('buyer_tier_id');
    $table->foreignId('pingtree_group_id');
    $table->foreignId('pingtree_id');
    $table->mediumInteger('processing_duration')->default(0);
    $table->smallInteger('request_response_code')->default(200);
    $table->decimal('commission', 8, 2)->default(0.00);
    $table->string('result', 32)->default('unknown');
    $table->string('request_url')->nullable();
    $table->integer('partition_id');
    $table->date('processed_on');
    $table->dateTime('processing_started_at');
    $table->dateTime('processing_ended_at')->nullable();
    $table->timestamps();

    $table->primary(['id', 'partition_id']);
});

The various query use cases are as follows for joining transactions to models are:

  1. Fetch all pingtree transactions for any given application
  2. Fetch all pingtree transactions for any given application between two dates
  3. Fetch all pingtree transactions for any given buyer
  4. Fetch all pingtree transactions for any given buyer between two dates
  5. etc…

But then, there’s a front-end page that’s paginated and shows a date/time picker along with a tags component for each model allowing a user to filter all transactions, for example:

enter image description here

  1. Show me all pingtree transactions for the past 3 days where the Buyer is either “foo” or “Bar”, and where the BuyerTier is “a” and “b” where the result is either “accepted” or “declined” on any of them.

A user might not always include all fields in their search for models, they might only want to see everything over a period minus specific models.

For the end user, there’s a lot of possible combinations for reporting via this front-end page since this is a business choice.

So in summary, there’s two cases:

  1. Individual model joining
  2. A report page with various filters

Indexing dilemas…

Since I want to join individual models which won’t require a date, like the foreignId columns, I would’ve thought adding the following indexes are suitable:

$table->index(['application_id']);
$table->index(['processed_on']);
$table->index(['company_id']);
$table->index(['application_id']);
$table->index(['buyer_id']);
$table->index(['buyer_tier_id']);
$table->index(['result']);
$table->index(['partition_id']);
$table->index(['processed_on']);
$table->index(['processing_started_at']);
$table->index(['processing_ended_at']);

On a table with millions of rows, adding new indexes is going to lock the table, but, the issue above, is now because I don’t have a composite index, and the dates are ranges, the cardinality is really high on those columns, and lower on the buyer and buyer tier columns, so the database ends up weirdly just picking one index for processing_started_at which ends up taking minutes to load.

explain select
  *
from
  `pingtree_transactions`
where
  `company_id` in (2, 1)
  and `buyer_id` in ("154", "172")
  and `buyer_tier_id` in ("652")
  and `processing_started_at` >= '2025-05-21 23:00:00'
  and `processing_ended_at` <= '2025-05-23 22:59:59'
  and `result` in ("accepted")
order by
  `processing_started_at` desc
limit
  26 offset 0

If I then add some composite index with multiple columns in there like:

$table->index([
    'company_id',
    'buyer_tier_id',
    'buyer_id',
    'result',
    'processing_started_at',
    'processing_ended_at'
], 'composite_pingtree_transactions_all_index');

Then it only appears to use it if all of the columns are in the search query and is incredibly fast at around 5ms, but given the various combinations in filtering, this would then seemingly bloat the database with all the combinations, and if one field is missed out, it ends up falling back to a sub-optimal index.

Essentially, what combination of indexes then would best to always utilise indexing?

The reason for adding:

$table->primary(['id', 'partition_id']);

Is because I’m experimenting with partitioning, and partition_id would house the current day in YYYYMMDD format, so there would be a partition for each day, but when trying this, and adding partition id into the query it seems to use partition pruning but no indexing.

So the question here is, what indexing should I add for my use cases defined above.

Phinx targeted rollback gives No migrations to rollback

I created the following migration:

final class UpdateApiClientType extends AbstractMigration
{
    public function up(): void
    {
        $this->execute("UPDATE Api_Clients SET Type = 'ibe' WHERE ClientName = 'Test'");
    }

    function down(): void
    {
        $this->execute("UPDATE Api_Clients SET Type = 'ota' WHERE ClientName = 'Test'");
    }
}

The phinx migrate executed well. My phinxlog table contains:

20250523065423|UpdateApiClientType|2025-05-23 09:03:18|2025-05-23 09:03:18|0

However when I execute phinx rollback -t 20250523065423 I get:

No migrations to rollback

Any ideas?

How to fix multiple SEO issues found in website audit – prioritization and implementation guidance [closed]

HIGH Priority Issues:

URL Canonicalization: Need to select primary URL and set up redirects

Keyword Optimization: Title tag, meta description, and heading tags need common keywords

Custom 404 Page: Missing custom 404 error page with helpful links

Structured Data: No structured data markup implemented

Render-blocking Resources: Page has render-blocking resources affecting performance

Image Format Optimization: Images not in modern formats (WebP, AVIF)

MEDIUM Priority Issues:

Social Media Integration: No social media API/AddThis integration

LOW Priority Issues:

SPF Record: Missing SPF record for email security

HSTS Header: Missing Strict-Transport-Security header

PHP script not reading set cookie in Safari OSX and iOS

Problem: verified cookie set in PHP and visible in developer tools is not detected by PHP for Safari. Same script is working fine in Chrome and Firefox.

Simplified example that illustrates the problem:

setcookie( 'cookiename', '1', time()+1200, '/' );

Reloading the page or loading other pages shows the cookie visible in the browser storage with the correct value in Safari. PHP on any page returns an empty $_COOKIE array but only in Safari.

var_dump( $_COOKIE );

array(0) {}

… in JS

document.cookie

I’ve read at least a dozen Safari cookie issues and solutions all over the web, including these:

$_COOKIE not working in Safari iOS

Not able to read cookie on JS side on Safari/iPhone

None appear to be relevant to the issue I’m facing.

TLDR: cookie is verified set correctly in Safari but PHP or JS to read it shows empty $_COOKIE array.

EDIT: suggested solution on the most similar problem I have found:
Use the assoc array options as detailed in docs: https://www.php.net/manual/en/function.setcookie.php

    setcookie( 'cookiename', '1', [
        'expires' => time()+1200,
        'path' => '/',
        'domain' => 'domain.com',
        'secure' => false,
        'httponly' => false,
        'samesite' => 'Lax',
        ]
    );

RESULT: Safari functioning as expected locally, but not on WPE server though the cookie is still visibly set but not read on subsequent page loads.

Google Maps API MarkerClusterer is not defined

I have a working map with AdvancedMarkerElements, but it’s completely killing the browser as soon as there are a few hundred markers. So I’m trying to add MarkerClusterer, but I just won’t get it to work whatever I try – feel quite stupid now.

I’ve tried to add MarkerClusterer Library from several sources with different methods and looked at tons of examples, but I always end up with an error saying “MarkerClusterer is not defined” or similiar.

<script src="https://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer_compiled.js"></script>
<div id="map"></div>
<script type="text/javascript">
  (g => {
    var h, a, k, p = "The Google Maps JavaScript API",
      c = "google",
      l = "importLibrary",
      q = "__ib__",
      m = document,
      b = window;
    b = b[c] || (b[c] = {});
    var d = b.maps || (b.maps = {}),
      r = new Set,
      e = new URLSearchParams,
      u = () => h || (h = new Promise(async (f, n) => {
        await (a = m.createElement("script"));
        e.set("libraries", [...r] + "");
        for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]);
        e.set("callback", c + ".maps." + q);
        a.src = `https://maps.${c}apis.com/maps/api/js?` + e;
        d[q] = f;
        a.onerror = () => h = n(Error(p + " could not load."));
        a.nonce = m.querySelector("script[nonce]")?.nonce || "";
        m.head.append(a)
      }));
    d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n))
  })
  ({
    key: "",
    v: "weekly"
  });

  async function initMap() {
    const {
      Map
    } = await google.maps.importLibrary("maps");
    const {
      AdvancedMarkerElement
    } = await google.maps.importLibrary("marker");
    const center = {
      lat: 50.5459719,
      lng: 10.0703129
    };
    const map = new Map(document.getElementById("map"), {
      zoom: 6.6,
      center,
      mapId: "4504f8b37365c3d0",
    });
    geocoder = new google.maps.Geocoder();
    markers = [];
    for (const property of properties) {
      if (property.lat != '' && property.lng != '') {
        var marker = new google.maps.marker.AdvancedMarkerElement({
          map: map,
          content: buildContent(property),
          position: new google.maps.LatLng(property.lat, property.lng),
          title: property.name,
        });
        marker.addListener("gmp-click", () => {
          toggleHighlight(marker, property);
        });
        markers.push(marker);
      }
    }
    var mc = new MarkerClusterer(map, markers)
  }

  function toggleHighlight(markerView, property) {
    if (markerView.content.classList.contains("highlight")) {
      markerView.content.classList.remove("highlight");
      markerView.zIndex = null;
    } else {
      markerView.content.classList.add("highlight");
      markerView.zIndex = 1;
    }
  }

  function buildContent(property) {
    const content = document.createElement("div");

    content.classList.add("property");
    content.innerHTML = `
    <div class="icon user" id="wb${property.id}">
        <i aria-hidden="true" class="fa fa-icon fa-user" title="${property.name} (${property.type})"></i>
        <span class="fa-sr-only">${property.name} (${property.type})</span>
    </div>
    <div class="details">
        <div class="name">${property.name}</div>
        <div class="address">${property.address}</div>
        <div class="mail"><a href="mailto:${property.mail}">${property.mail}</a></div>
        <div class="phone"><a href="tel:${property.phone}">${property.phone}</a></div>
        <div class="features">
        </div>
    </div>
    `;
    return content;
  }
</script>

Please help this requirement via SAP Adobe Form [closed]

I have designed a dynamic table in an SAP Adobe Form (XFA-based). The table displays line items with multiple columns such as PRODUCT NO., DESCRIPTION, BATCH NO., QUANTITY, UNIT PRICE, DISCOUNT, VAT, and TOTAL.

These lines are coming from a repeating structure (DATA[*]) in the context.

Requirement:

  • If the PRODUCT NO. (Material Number) is the same across consecutive
    rows, I want to visually suppress those rows, i.e.:

  • Hide the row borders (top and bottom) between the repeated rows.

  • Only the last row with that material number should display the bottom
    border to “close” the section visually.

  • The column lines (left and right borders) should remain visible so
    that the table maintains structure.

I attempted the following approaches:

Hiding all borders using:

this.border.edge.presence = "hidden";

Trying to selectively show the bottom line for the last row using:

this.border.edge[2].color.value = "0,0,0"; // bottom

result :
enter image description here

expected result :
enter image description here

There are two cases:

  • If the material is the same but the batch is different, then the row
    borders should be hidden.

  • If the material is different, then the table should display normally
    with all row borders.

How can I scale my HTML and CSS to suit for different resolution devices?

How can I make my web content scale to different device sizes?

I do not know specific phone scales, and what I can really do about this. I’m looking forward to scale my code to be able to work on different devices with the same layout shown and nothing moved around. It uses JavaScript code to move stuff around, but that only toggles display:none and display:block. Do I change the JavaScript to flex, or other stuff?
If you need to know more about my code, I’ll try to provide more of it.
<iframe id="IPTV" src="nosignal.html" width="460" height="259" allowfullscreen frameborder='0' style="width: 100%; height: 100%;"></iframe>
That is an example of my iframe which uses a scale format, and I don’t know if it is device friendly.