This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 10b3ee8  fix: slide session cookie maxAge + clear 3D selection on 
empty-plane click (#29)
10b3ee8 is described below

commit 10b3ee83fac9d2f586b48b5c9219ce98df018339
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Thu May 28 21:14:11 2026 +0800

    fix: slide session cookie maxAge + clear 3D selection on empty-plane click 
(#29)
    
    * fix(auth): slide the session cookie maxAge on each request
    
    The server slides a session's TTL on every authenticated request
    (`sessions.touch()`), but the cookie's maxAge was stamped only at
    login. So an actively-used session kept sliding server-side while the
    browser cookie still expired at login + ttl — the user was silently
    logged out mid-session. requireAuth now re-stamps the cookie (same
    options as login, only maxAge refreshed) after a successful touch, so
    the browser expiry tracks the sliding server TTL.
    
    * fix(infra-3d): clear selection when clicking empty plane space
    
    pmndrs `pointermissed` only fires on a true void hit, so clicking a
    colored zone/tier plane (raycast left on so planes occlude cubes) never
    cleared the selection — only clicking past every plane did. A deselect
    handler on the planes isn't an option: operators orbit-drag from empty
    plane space to rotate WITHOUT changing selection.
    
    Detect the empty click at the DOM level on the canvas, guarded so it
    only fires for a genuine empty click:
      - target is the <canvas> (not a portaled <Html> overlay — clicking
        the detail card must not deselect)
      - pointer barely moved (a click, not an orbit/pan drag)
      - no cube absorbed the gesture (onNodeClick stamped within 120ms)
    
    Replaces the @pointermissed wiring. Needs a live click-test before merge.
---
 apps/bff/src/user/middleware.ts                | 14 +++++++++
 apps/ui/src/features/infra-3d/Infra3DScene.vue | 41 +++++++++++++++++++++-----
 2 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/apps/bff/src/user/middleware.ts b/apps/bff/src/user/middleware.ts
index bc7a589..9a7d616 100644
--- a/apps/bff/src/user/middleware.ts
+++ b/apps/bff/src/user/middleware.ts
@@ -60,6 +60,20 @@ export function requireAuth(deps: AuthDeps) {
       return void reply.code(401).send({ error: 'unauthenticated' });
     }
     req.session = session;
+    // Sliding session: touch() just slid the server-side TTL, so
+    // re-stamp the cookie's maxAge to match. The cookie's expiry was
+    // set only at login, so without this an actively-used session still
+    // expires in the browser at login + ttl while the server believes
+    // it's alive — the user is logged out mid-session. Mirror the login
+    // cookie options exactly so only maxAge slides.
+    const s = deps.config.current.session;
+    reply.setCookie(s.cookieName, sid, {
+      httpOnly: true,
+      sameSite: 'strict',
+      secure: s.cookieSecure,
+      path: '/',
+      maxAge: s.ttlMinutes * 60,
+    });
   };
 }
 
diff --git a/apps/ui/src/features/infra-3d/Infra3DScene.vue 
b/apps/ui/src/features/infra-3d/Infra3DScene.vue
index 14b6a4b..eb5a8af 100644
--- a/apps/ui/src/features/infra-3d/Infra3DScene.vue
+++ b/apps/ui/src/features/infra-3d/Infra3DScene.vue
@@ -808,6 +808,9 @@ function onNodePointerOut(node: SceneServiceNode): void {
  * safe).
  */
 function onNodeClick(node: SceneServiceNode): void {
+  // Stamp so the canvas-host pointerup handler knows a cube absorbed
+  // this gesture and must not treat the same release as an empty click.
+  lastCubeClickAt = performance.now();
   dbgBadge(`CUBE CLICKED → ${node.shortName}`, '#f97316', 
`(${node.layerKey})`);
   dbg('node.pointerdown', {
     shortName: node.shortName,
@@ -853,12 +856,30 @@ function nodeHandlers(node: SceneServiceNode): 
NodeHandlers {
   return h;
 }
 
-/** Fired by TresCanvas when a pointer event hits nothing in the
- *  scene — the pmndrs "pointer-missed" pattern. Empty-space click =
- *  clear the current selection. */
-function onPointerMissed(): void {
-  dbg('canvas.pointermissed', { selected: props.selectedNodeId });
-  emit('select', null);
+// Empty-space deselect. pmndrs `pointermissed` only fires on a true
+// void hit (the ray hits nothing) — clicking a colored zone/tier plane
+// hits the plane (raycast stays on so planes occlude cubes), so it
+// never cleared the selection. We can't add a deselect handler to the
+// planes either: operators orbit-drag FROM empty plane space to rotate
+// WITHOUT changing selection (see CLAUDE.md). So detect an empty click
+// at the DOM level on the canvas itself, with three guards:
+//   - target is the <canvas> (not a portaled <Html> overlay like the
+//     detail card — clicking the card must not deselect)
+//   - the pointer barely moved (a click, not an orbit/pan drag)
+//   - no cube absorbed this gesture (onNodeClick stamped recently)
+let lastCubeClickAt = 0;
+let pointerDownX = 0;
+let pointerDownY = 0;
+const CLICK_SLOP_PX = 5;
+function onHostPointerDown(e: PointerEvent): void {
+  pointerDownX = e.clientX;
+  pointerDownY = e.clientY;
+}
+function onHostPointerUp(e: PointerEvent): void {
+  if ((e.target as HTMLElement | null)?.tagName !== 'CANVAS') return;
+  if (Math.hypot(e.clientX - pointerDownX, e.clientY - pointerDownY) > 
CLICK_SLOP_PX) return;
+  if (performance.now() - lastCubeClickAt < 120) return;
+  if (props.selectedNodeId !== null) emit('select', null);
 }
 
 // Diagnostic — fires whenever the parent updates `selectedNodeId`.
@@ -1116,13 +1137,17 @@ onUnmounted(() => {
 </script>
 
 <template>
-  <div ref="canvasHostEl" class="canvas-host">
+  <div
+    ref="canvasHostEl"
+    class="canvas-host"
+    @pointerdown="onHostPointerDown"
+    @pointerup="onHostPointerUp"
+  >
     <TresCanvas
       clear-color="#0a0d12"
       :antialias="true"
       power-preference="high-performance"
       @loop="onSceneLoop"
-      @pointermissed="onPointerMissed"
     >
       <TresPerspectiveCamera
         ref="cameraRef"

Reply via email to