diff --git a/Content/Browser/package-lock.json b/Content/Browser/package-lock.json new file mode 100644 index 0000000..c280598 --- /dev/null +++ b/Content/Browser/package-lock.json @@ -0,0 +1,1949 @@ +{ + "name": "hypertwist-browser-runtime", + "version": "1.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hypertwist-browser-runtime", + "version": "1.1.0", + "dependencies": { + "@google/model-viewer": "^4.3.1", + "@google/model-viewer-effects": "^1.5.0", + "@khronosgroup/gltf-viewer": "^1.1.0", + "@pmndrs/uikit": "^1.0.73", + "@react-spring/three": "^10.1.1", + "@react-spring/web": "^10.1.1", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.1", + "@react-three/postprocessing": "^3.0.4", + "@use-gesture/react": "^10.3.1", + "echarts": "^6.1.0", + "echarts-gl": "^2.1.0", + "leva": "^0.10.1", + "maath": "^0.10.8", + "postprocessing": "^6.39.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "three": "^0.183.0", + "three-stdlib": "^2.36.1", + "zrender": "^6.1.0", + "zustand": "^5.0.14" + }, + "devDependencies": { + "typescript": "^5.9.3", + "vite": "^7.1.11" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "license": "MIT" + }, + "node_modules/@google/model-viewer": { + "version": "4.3.1", + "license": "Apache-2.0", + "dependencies": { + "@monogrid/gainmap-js": "^3.1.0", + "lit": "^3.2.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "three": "^0.183.0" + } + }, + "node_modules/@google/model-viewer-effects": { + "version": "1.5.0", + "license": "Apache-2.0", + "dependencies": { + "lit": "^3.2.1", + "postprocessing": "^6.37.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "@google/model-viewer": "^4.1.0" + } + }, + "node_modules/@khronosgroup/gltf-viewer": { + "version": "1.1.0", + "license": "Apache-2.0", + "dependencies": { + "fast-png": "^6.2.0", + "gl-matrix": "^3.2.1", + "globals": "^15.5.0", + "jpeg-js": "^0.4.3", + "json-ptr": "^3.1.0" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.6.0", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.17", + "license": "Apache-2.0" + }, + "node_modules/@monogrid/gainmap-js": { + "version": "3.4.0", + "license": "MIT", + "dependencies": { + "promise-worker-transferable": "^1.0.4" + }, + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/@pmndrs/msdfonts": { + "version": "1.0.74", + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@pmndrs/uikit": { + "version": "1.0.74", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/msdfonts": "^1.0.74", + "@pmndrs/uikit-pub-sub": "^1.0.74", + "@preact/signals-core": "^1.5.1", + "@zappar/msdf-generator": "^1.2.4", + "yoga-layout": "^3.2.1", + "zod": "^4.4.3" + }, + "peerDependencies": { + "three": ">=0.162" + } + }, + "node_modules/@pmndrs/uikit-pub-sub": { + "version": "1.0.74", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.8.0" + } + }, + "node_modules/@preact/signals-core": { + "version": "1.14.3", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.10", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.13", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.12", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.6", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.10", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-visually-hidden": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.6", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/@react-spring/animated": { + "version": "10.1.1", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~10.1.1", + "@react-spring/types": "~10.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "10.1.1", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~10.1.1", + "@react-spring/shared": "~10.1.1", + "@react-spring/types": "~10.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "10.1.1", + "license": "MIT" + }, + "node_modules/@react-spring/shared": { + "version": "10.1.1", + "license": "MIT", + "dependencies": { + "@react-spring/rafz": "~10.1.1", + "@react-spring/types": "~10.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-spring/three": { + "version": "10.1.1", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~10.1.1", + "@react-spring/core": "~10.1.1", + "@react-spring/shared": "~10.1.1", + "@react-spring/types": "~10.1.1" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "three": ">=0.126" + } + }, + "node_modules/@react-spring/types": { + "version": "10.1.1", + "license": "MIT" + }, + "node_modules/@react-spring/web": { + "version": "10.1.1", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~10.1.1", + "@react-spring/core": "~10.1.1", + "@react-spring/shared": "~10.1.1", + "@react-spring/types": "~10.1.1", + "csstype": "^3.2.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-three/drei": { + "version": "10.7.7", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mediapipe/tasks-vision": "0.10.17", + "@monogrid/gainmap-js": "^3.0.6", + "@use-gesture/react": "^10.3.1", + "camera-controls": "^3.1.0", + "cross-env": "^7.0.3", + "detect-gpu": "^5.0.56", + "glsl-noise": "^0.0.0", + "hls.js": "^1.5.17", + "maath": "^0.10.8", + "meshline": "^3.3.1", + "stats-gl": "^2.2.8", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.8.3", + "three-stdlib": "^2.35.6", + "troika-three-text": "^0.52.4", + "tunnel-rat": "^0.1.2", + "use-sync-external-store": "^1.4.0", + "utility-types": "^3.11.0", + "zustand": "^5.0.1" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19", + "react-dom": "^19", + "three": ">=0.159" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "9.6.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^2.0.0", + "react-use-measure": "^2.1.7", + "scheduler": "^0.27.0", + "suspend-react": "^0.1.3", + "use-sync-external-store": "^1.4.0", + "zustand": "^5.0.3" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=19 <19.3", + "react-dom": ">=19 <19.3", + "react-native": ">=0.78", + "three": ">=0.156" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/postprocessing": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "maath": "^0.6.0", + "n8ao": "^1.9.4", + "postprocessing": "^6.36.6" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19.0", + "three": ">= 0.156.0" + } + }, + "node_modules/@react-three/postprocessing/node_modules/maath": { + "version": "0.6.0", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@stitches/react": { + "version": "1.2.8", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "license": "MIT" + }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "license": "MIT" + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.28.9", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.184.1", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.2", + "meshoptimizer": "~1.1.1" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "license": "MIT" + }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@zappar/msdf-generator": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "comlink": "^4.4.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/attr-accept": { + "version": "2.2.5", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/camera-controls": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=22.0.0", + "npm": ">=10.5.1" + }, + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/claygl": { + "version": "1.3.0" + }, + "node_modules/colord": { + "version": "2.9.3", + "license": "MIT" + }, + "node_modules/comlink": { + "version": "4.4.2", + "license": "Apache-2.0" + }, + "node_modules/cross-env": { + "version": "7.0.3", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-gpu": { + "version": "5.0.70", + "license": "MIT", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, + "node_modules/draco3d": { + "version": "1.5.7", + "license": "Apache-2.0" + }, + "node_modules/echarts": { + "version": "6.1.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.1.0" + } + }, + "node_modules/echarts-gl": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "claygl": "^1.2.1", + "zrender": "^5.1.1 || ^6.0.0" + }, + "peerDependencies": { + "echarts": "^5.1.2 || ^6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "0.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-png": { + "version": "6.4.0", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "license": "MIT" + }, + "node_modules/file-selector": { + "version": "0.5.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "license": "MIT" + }, + "node_modules/globals": { + "version": "15.15.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/hls.js": { + "version": "1.6.16", + "license": "Apache-2.0" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "license": "MIT" + }, + "node_modules/iobuffer": { + "version": "5.4.0", + "license": "MIT" + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/its-fine": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.9" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/json-ptr": { + "version": "3.1.1", + "license": "MIT" + }, + "node_modules/leva": { + "version": "0.10.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-portal": "^1.1.4", + "@radix-ui/react-tooltip": "^1.1.8", + "@stitches/react": "^1.2.8", + "@use-gesture/react": "^10.2.5", + "colord": "^2.9.2", + "dequal": "^2.0.2", + "merge-value": "^1.0.0", + "react-colorful": "^5.5.1", + "react-dropzone": "^12.0.0", + "v8n": "^1.3.3", + "zustand": "^3.6.9" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/leva/node_modules/zustand": { + "version": "3.7.2", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/lie": { + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lit": { + "version": "3.3.3", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.3", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/maath": { + "version": "0.10.8", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.134.0", + "three": ">=0.134.0" + } + }, + "node_modules/merge-value": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-value": "^2.0.6", + "is-extendable": "^1.0.0", + "mixin-deep": "^1.2.0", + "set-value": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/meshline": { + "version": "3.3.1", + "license": "MIT", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/n8ao": { + "version": "1.10.2", + "license": "ISC", + "peerDependencies": { + "postprocessing": ">=6.30.0", + "three": ">=0.137" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "2.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postprocessing": { + "version": "6.39.1", + "license": "Zlib", + "peerDependencies": { + "three": ">= 0.168.0 < 0.185.0" + } + }, + "node_modules/potpack": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/promise-worker-transferable": { + "version": "1.0.4", + "license": "Apache-2.0", + "dependencies": { + "is-promise": "^2.1.0", + "lie": "^3.0.2" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "19.2.7", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-colorful": { + "version": "5.7.0", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-dropzone": { + "version": "12.1.0", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.5.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/set-value": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stats-gl": { + "version": "2.4.2", + "license": "MIT", + "dependencies": { + "@types/three": "*", + "three": "^0.170.0" + }, + "peerDependencies": { + "@types/three": "*", + "three": "*" + } + }, + "node_modules/stats-gl/node_modules/three": { + "version": "0.170.0", + "license": "MIT" + }, + "node_modules/stats.js": { + "version": "0.17.0", + "license": "MIT" + }, + "node_modules/suspend-react": { + "version": "0.1.3", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/three": { + "version": "0.183.2", + "license": "MIT" + }, + "node_modules/three-mesh-bvh": { + "version": "0.8.3", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.36.1", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/three-stdlib/node_modules/fflate": { + "version": "0.6.10", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/troika-three-text": { + "version": "0.52.4", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.52.4", + "troika-worker-utils": "^0.52.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.52.4", + "license": "MIT", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.52.0", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.3.0", + "license": "0BSD" + }, + "node_modules/tunnel-rat": { + "version": "0.1.2", + "license": "MIT", + "dependencies": { + "zustand": "^4.3.2" + } + }, + "node_modules/tunnel-rat/node_modules/zustand": { + "version": "4.5.7", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/utility-types": { + "version": "3.11.0", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/v8n": { + "version": "1.5.1", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/webgl-constants": { + "version": "1.1.1" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.4.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zrender": { + "version": "6.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 413db4e..1ed3986 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -12791,6 +12791,15 @@ UHyperTwistCoachDashboardWidget::GetDisplayedBrowserRuntimeInspectSurface() cons return Surface; } +FHyperTwistTrainingControlInputReadinessInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedControlInputReadinessInspectSurface() const +{ + FHyperTwistTrainingControlInputReadinessInspectSurface Surface = + UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(); + Surface.Headline = TEXT("Control and input readiness"); + return Surface; +} + FHyperTwistTrainingCoachDashboardGuidanceRationaleInspectSurface UHyperTwistCoachDashboardWidget::GetDisplayedGuidanceRationaleInspectSurface() const { @@ -19109,6 +19118,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachBrowserRuntimeDetail"), 8 ); + ControlInputReadinessHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachControlInputReadinessHeader"), + 2 + ); + ControlInputReadinessStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachControlInputReadinessStatus"), + 2 + ); + ControlInputReadinessDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachControlInputReadinessDetail"), + 8 + ); RecognitionStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -30163,6 +30190,23 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { BrowserRuntimeDetailTextBlock->SetText(FText::FromString(BrowserRuntimeSurface.DetailLine)); } + const FHyperTwistTrainingControlInputReadinessInspectSurface ControlInputSurface = + GetDisplayedControlInputReadinessInspectSurface(); + if (ControlInputReadinessHeaderTextBlock != nullptr) + { + ControlInputReadinessHeaderTextBlock->SetText( + FText::FromString(TEXT("[Control and Input Readiness]"))); + } + if (ControlInputReadinessStatusTextBlock != nullptr) + { + ControlInputReadinessStatusTextBlock->SetText( + FText::FromString(ControlInputSurface.StatusLine)); + } + if (ControlInputReadinessDetailTextBlock != nullptr) + { + ControlInputReadinessDetailTextBlock->SetText( + FText::FromString(ControlInputSurface.DetailLine)); + } if (VerificationStatusTextBlock != nullptr) { DisplayedVerificationStatusLine = CachedCoachPanelState.VerificationStatusLine; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 29af952..8bb4435 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -1,10 +1,118 @@ #include "HyperTwistTraining/HyperTwistTrainingPanelWidget.h" +#include "HyperTwistAlgorithm/HyperTwistAlgorithmKeyboard.h" #include "HyperTwistBrowser/HyperTwistBrowserWidget.h" +#include "HyperTwistSimulation/HyperTwistClassicCubePlayerController.h" +#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h" #include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h" +#include "Misc/ConfigCacheIni.h" namespace HyperTwistTrainingPanelWidgetInternal { + struct FProjectInputGroundworkFacts + { + bool bEnhancedInputProjectGroundworkPresent = false; + bool bMotionControllerGroundworkPresent = false; + int32 MotionControllerFamilyCount = 0; + TArray MotionControllerFamilies; + }; + + FString DescribeBool(const bool bValue) + { + return bValue ? TEXT("yes") : TEXT("no"); + } + + bool AxisConfigEntriesContainToken( + const TArray& AxisConfigEntries, + const TCHAR* Token + ) + { + for (const FString& Entry : AxisConfigEntries) + { + if (Entry.Contains(Token)) + { + return true; + } + } + + return false; + } + + FProjectInputGroundworkFacts BuildProjectInputGroundworkFacts() + { + FProjectInputGroundworkFacts Facts; + + if (GConfig == nullptr) + { + return Facts; + } + + static constexpr TCHAR InputSettingsSection[] = TEXT("/Script/Engine.InputSettings"); + + FString DefaultPlayerInputClass; + FString DefaultInputComponentClass; + GConfig->GetString( + InputSettingsSection, + TEXT("DefaultPlayerInputClass"), + DefaultPlayerInputClass, + GInputIni + ); + GConfig->GetString( + InputSettingsSection, + TEXT("DefaultInputComponentClass"), + DefaultInputComponentClass, + GInputIni + ); + + Facts.bEnhancedInputProjectGroundworkPresent = + DefaultPlayerInputClass == TEXT("/Script/EnhancedInput.EnhancedPlayerInput") + && DefaultInputComponentClass == TEXT("/Script/EnhancedInput.EnhancedInputComponent"); + + GConfig->GetBool( + InputSettingsSection, + TEXT("bEnableMotionControls"), + Facts.bMotionControllerGroundworkPresent, + GInputIni + ); + + TArray AxisConfigEntries; + GConfig->GetArray( + InputSettingsSection, + TEXT("AxisConfig"), + AxisConfigEntries, + GInputIni + ); + + const struct + { + const TCHAR* Token; + const TCHAR* Label; + } MotionControllerFamilies[] = { + {TEXT("Vive_"), TEXT("Vive")}, + {TEXT("MixedReality_"), TEXT("Mixed Reality")}, + {TEXT("OculusTouch_"), TEXT("Oculus Touch")}, + {TEXT("ValveIndex_"), TEXT("Valve Index")} + }; + + for (const auto& Family : MotionControllerFamilies) + { + if (AxisConfigEntriesContainToken(AxisConfigEntries, Family.Token)) + { + Facts.MotionControllerFamilies.Add(Family.Label); + } + } + + Facts.MotionControllerFamilyCount = Facts.MotionControllerFamilies.Num(); + return Facts; + } + + const FProjectInputGroundworkFacts& GetProjectInputGroundworkFacts() + { + static const FProjectInputGroundworkFacts CachedFacts = + BuildProjectInputGroundworkFacts(); + return CachedFacts; + } + FString ResolveCoachHandoffStartActionSourceLabel( const FHyperTwistTrainingCoachPanelState& PanelState, const EHyperTwistTrainingCoachHandoffKind HandoffKind, @@ -657,6 +765,137 @@ UHyperTwistTrainingPanelWidget::GetDisplayedBrowserRuntimeInspectSurface() const return Surface; } +FHyperTwistTrainingControlInputReadinessInspectSurface +UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface() const +{ + FHyperTwistTrainingControlInputReadinessInspectSurface Surface; + Surface.Headline = TEXT("Control and input readiness"); + Surface.KeyboardProfileName = + UHyperTwistAlgorithmKeyboardLibrary::GetDefaultKeyboardProfileName(); + Surface.bClassicKeyboardProfileReady = !Surface.KeyboardProfileName.IsEmpty(); + + const AHyperTwistClassicCubePlayerController* ClassicCubeDefaults = + GetDefault(); + Surface.bClassicCubeMouseInputReady = ClassicCubeDefaults != nullptr + && ClassicCubeDefaults->bShowMouseCursor; + Surface.bClassicCubeTouchInputReady = + ClassicCubeDefaults != nullptr && ClassicCubeDefaults->bEnableTouchTurnInput; + Surface.bClassicCubeShortcutReady = ClassicCubeDefaults != nullptr + && ClassicCubeDefaults->bBindFreshAttemptShortcut + && ClassicCubeDefaults->bBindHintShortcut + && ClassicCubeDefaults->bBindSubmitSolveShortcut + && ClassicCubeDefaults->bBindModeToggleShortcut + && ClassicCubeDefaults->bBindVoiceHoldShortcut + && ClassicCubeDefaults->bBindVoiceCycleShortcut; + + const AHyperTwistVirtual3333ProjectionPlayerController* HigherDimensionalDefaults = + GetDefault(); + Surface.bHigherDimensionalKeyboardInputReady = + HigherDimensionalDefaults != nullptr && HigherDimensionalDefaults->bUseGameAndUiInputMode; + + const FHyperTwistTrainingHigherDimensionalRuntimeHostCatalog HostCatalog = + UHyperTwistTrainingRuntimeLibrary::GetBundledHigherDimensionalRuntimeHostCatalog(); + const FHyperTwistTrainingHigherDimensionalRuntimeViewContextCatalog ViewContextCatalog = + UHyperTwistTrainingRuntimeLibrary::GetBundledHigherDimensionalRuntimeViewContextCatalog(); + const FHyperTwistTrainingHigherDimensionalRuntimeSessionCatalog SessionCatalog = + UHyperTwistTrainingRuntimeLibrary::GetBundledHigherDimensionalRuntimeSessionCatalog(); + const FHyperTwistTrainingHigherDimensionalInteractiveSceneCatalog SceneCatalog = + UHyperTwistTrainingRuntimeLibrary::GetBundledHigherDimensionalInteractiveSceneCatalog(); + Surface.bHigherDimensionalDedicatedFamilyReady = + HostCatalog.IsStructurallyValid() + && ViewContextCatalog.IsStructurallyValid() + && SessionCatalog.IsStructurallyValid() + && SceneCatalog.IsStructurallyValid(); + + const HyperTwistTrainingPanelWidgetInternal::FProjectInputGroundworkFacts& InputGroundworkFacts = + HyperTwistTrainingPanelWidgetInternal::GetProjectInputGroundworkFacts(); + Surface.bEnhancedInputProjectGroundworkPresent = + InputGroundworkFacts.bEnhancedInputProjectGroundworkPresent; + Surface.bMotionControllerGroundworkPresent = + InputGroundworkFacts.bMotionControllerGroundworkPresent + && InputGroundworkFacts.MotionControllerFamilyCount > 0; + Surface.MotionControllerFamilyCount = InputGroundworkFacts.MotionControllerFamilyCount; + + FHyperTwistTrainingImmersivePresenceControlContract PresenceControlContract; + Surface.bImmersivePresenceContractReady = + UHyperTwistTrainingRuntimeLibrary::TryGetBundledImmersivePresenceControlContract( + TEXT("immersive-training-presence-control-contract"), + PresenceControlContract + ); + + Surface.bFinishedXrRuntimeReady = false; + Surface.bFinishedPreferencesReady = false; + + Surface.SummaryLine = + TEXT("Keyboard and mouse lanes are ready today; XR/controller groundwork exists but remains unfinished."); + Surface.ClassicCubeStatusLine = FString::Printf( + TEXT("Classic cube: mouse %s | touch %s | shortcuts %s."), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bClassicCubeMouseInputReady), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bClassicCubeTouchInputReady), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bClassicCubeShortcutReady) + ); + Surface.KeyboardProfileLine = FString::Printf( + TEXT("Keyboard profile: %s is the shipped classic mapping."), + *Surface.KeyboardProfileName + ); + Surface.HigherDimensionalStatusLine = FString::Printf( + TEXT("Higher-dimensional: keyboard-driven slice/layer control %s | dedicated-family runtime catalogs %s | scene surfaces %d."), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bHigherDimensionalKeyboardInputReady), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bHigherDimensionalDedicatedFamilyReady), + SceneCatalog.SceneSurfaces.Num() + ); + const FString MotionControllerFamilyLabels = + InputGroundworkFacts.MotionControllerFamilies.Num() > 0 + ? FString::Join(InputGroundworkFacts.MotionControllerFamilies, TEXT(", ")) + : FString(TEXT("none")); + Surface.XrStatusLine = FString::Printf( + TEXT("XR groundwork: EnhancedInput config %s | motion controls %s | controller families %d (%s) | immersive presence contract %s | finished XR runtime not yet shipped."), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bEnhancedInputProjectGroundworkPresent), + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bMotionControllerGroundworkPresent), + Surface.MotionControllerFamilyCount, + *MotionControllerFamilyLabels, + *HyperTwistTrainingPanelWidgetInternal::DescribeBool( + Surface.bImmersivePresenceContractReady) + ); + Surface.PreferencesStatusLine = + TEXT("Preferences: polished user-facing rebinding and broader control-settings ownership are not yet shipped."); + Surface.NextPacketLine = + TEXT("Next native packet: explicit XR host/plugin decision, dedicated headset/controller runtime owners, user-facing settings/rebinding, and Windows package validation."); + Surface.StatusLine = FString::Printf( + TEXT("classic cube %s | higher-dimensional %s | XR groundwork %s | finished VR/preferences not yet shipped"), + Surface.bClassicCubeMouseInputReady + && Surface.bClassicCubeTouchInputReady + && Surface.bClassicCubeShortcutReady + ? TEXT("ready") + : TEXT("partial"), + Surface.bHigherDimensionalKeyboardInputReady + && Surface.bHigherDimensionalDedicatedFamilyReady + ? TEXT("ready") + : TEXT("partial"), + Surface.bEnhancedInputProjectGroundworkPresent + && Surface.bMotionControllerGroundworkPresent + && Surface.bImmersivePresenceContractReady + ? TEXT("present") + : TEXT("limited") + ); + Surface.DetailLine = FString::Printf( + TEXT("%s | %s | %s | %s | %s"), + *Surface.ClassicCubeStatusLine, + *Surface.KeyboardProfileLine, + *Surface.HigherDimensionalStatusLine, + *Surface.XrStatusLine, + *Surface.PreferencesStatusLine + ); + return Surface; +} + FHyperTwistTrainingDeck UHyperTwistTrainingPanelWidget::BuildActiveCoachRecommendedDeck(const int32 MaxCases) { CachedCoachRecommendedDeck = UHyperTwistTrainingRuntimeLibrary::BuildActiveCoachRecommendedDeck( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index a03bc69..248016c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -935,6 +935,9 @@ public: FHyperTwistTrainingBrowserRuntimeInspectSurface GetDisplayedBrowserRuntimeInspectSurface() const override; + FHyperTwistTrainingControlInputReadinessInspectSurface + GetDisplayedControlInputReadinessInspectSurface() const override; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingCoachDashboardGuidanceRationaleInspectSurface GetDisplayedGuidanceRationaleInspectSurface() const; @@ -1646,6 +1649,15 @@ protected: UPROPERTY(Transient) TObjectPtr BrowserRuntimeDetailTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr ControlInputReadinessHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr ControlInputReadinessStatusTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr ControlInputReadinessDetailTextBlock = nullptr; + UPROPERTY(Transient) TObjectPtr RecognitionStatusTextBlock = nullptr; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index fc2ad6a..f47da3a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -291,6 +291,10 @@ public: virtual FHyperTwistTrainingBrowserRuntimeInspectSurface GetDisplayedBrowserRuntimeInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Input") + virtual FHyperTwistTrainingControlInputReadinessInspectSurface + GetDisplayedControlInputReadinessInspectSurface() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingDeck BuildActiveCoachRecommendedDeck(int32 MaxCases); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index fc28eda..b5633d7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -9977,6 +9977,95 @@ struct FHyperTwistTrainingBrowserRuntimeInspectSurface } }; +USTRUCT(BlueprintType) +struct FHyperTwistTrainingControlInputReadinessInspectSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ClassicCubeStatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString KeyboardProfileLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HigherDimensionalStatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString XrStatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreferencesStatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextPacketLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString KeyboardProfileName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MotionControllerFamilyCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClassicCubeMouseInputReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClassicCubeTouchInputReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClassicCubeShortcutReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClassicKeyboardProfileReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHigherDimensionalKeyboardInputReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHigherDimensionalDedicatedFamilyReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bEnhancedInputProjectGroundworkPresent = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMotionControllerGroundworkPresent = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bImmersivePresenceContractReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bFinishedXrRuntimeReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bFinishedPreferencesReady = false; + + bool IsStructurallyValid() const + { + return !SummaryLine.IsEmpty() + || !StatusLine.IsEmpty() + || !ClassicCubeStatusLine.IsEmpty() + || !HigherDimensionalStatusLine.IsEmpty() + || !XrStatusLine.IsEmpty() + || bClassicCubeMouseInputReady + || bHigherDimensionalKeyboardInputReady + || bEnhancedInputProjectGroundworkPresent + || bMotionControllerGroundworkPresent + || bImmersivePresenceContractReady; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistTrainingCoachDashboardQueueRecoveryStatusInspectSurface { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp index 32f9781..03185f2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp @@ -995,6 +995,111 @@ bool FHyperTwistBrowserCoachDashboardRuntimeInspectSurfaceTest::RunTest( return true; } +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest, + "HyperTwist.Browser.TrainingPanel.ControlInputReadinessInspectSurface", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingPanelWidget* TrainingPanel = NewObject(); + TestNotNull(TEXT("The training panel widget must be constructible."), TrainingPanel); + if (TrainingPanel == nullptr) + { + return false; + } + + const FHyperTwistTrainingControlInputReadinessInspectSurface Surface = + TrainingPanel->GetDisplayedControlInputReadinessInspectSurface(); + TestTrue( + TEXT("The control/input readiness surface must be structurally valid."), + Surface.IsStructurallyValid() + ); + TestEqual( + TEXT("The control/input readiness surface must keep a stable headline."), + Surface.Headline, + TEXT("Control and input readiness") + ); + TestEqual( + TEXT("The control/input readiness surface must expose the shipped classic keyboard profile."), + Surface.KeyboardProfileName, + TEXT("classic-wca-keyboard/v1") + ); + TestTrue( + TEXT("The control/input readiness surface must report the shipped classic cube mouse/touch/shortcut lane."), + Surface.bClassicCubeMouseInputReady + && Surface.bClassicCubeTouchInputReady + && Surface.bClassicCubeShortcutReady + ); + TestTrue( + TEXT("The control/input readiness surface must report the higher-dimensional keyboard lane and dedicated-family runtime catalogs."), + Surface.bHigherDimensionalKeyboardInputReady + && Surface.bHigherDimensionalDedicatedFamilyReady + ); + TestTrue( + TEXT("The control/input readiness surface must report the current project-level input groundwork."), + Surface.bEnhancedInputProjectGroundworkPresent + && Surface.bMotionControllerGroundworkPresent + && Surface.bImmersivePresenceContractReady + ); + TestFalse( + TEXT("The control/input readiness surface must keep unfinished XR/runtime and preferences truth explicit."), + Surface.bFinishedXrRuntimeReady || Surface.bFinishedPreferencesReady + ); + TestTrue( + TEXT("The control/input readiness surface must say plainly that finished XR runtime is not yet shipped."), + Surface.XrStatusLine.Contains(TEXT("not yet shipped")) + ); + TestTrue( + TEXT("The control/input readiness surface must point to the later settings/rebinding packet."), + Surface.NextPacketLine.Contains(TEXT("settings/rebinding")) + ); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistCoachDashboardControlInputReadinessInspectSurfaceTest, + "HyperTwist.Browser.CoachDashboard.ControlInputReadinessInspectSurface", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistCoachDashboardControlInputReadinessInspectSurfaceTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistCoachDashboardWidget* CoachDashboard = NewObject(); + TestNotNull(TEXT("The coach dashboard widget must be constructible."), CoachDashboard); + if (CoachDashboard == nullptr) + { + return false; + } + + CoachDashboard->RefreshCoachDashboardView(); + const FHyperTwistTrainingControlInputReadinessInspectSurface Surface = + CoachDashboard->GetDisplayedControlInputReadinessInspectSurface(); + TestTrue( + TEXT("The coach dashboard control/input readiness surface must stay structurally valid after a view refresh."), + Surface.IsStructurallyValid() + ); + TestEqual( + TEXT("The coach dashboard control/input readiness surface must preserve the diagnostics headline."), + Surface.Headline, + TEXT("Control and input readiness") + ); + TestTrue( + TEXT("The coach dashboard control/input readiness surface must retain the keyboard profile fact in its detail line."), + Surface.DetailLine.Contains(TEXT("classic-wca-keyboard/v1")) + ); + TestTrue( + TEXT("The coach dashboard control/input readiness surface must retain the unfinished XR/runtime truth in its detail line."), + Surface.DetailLine.Contains(TEXT("not yet shipped")) + ); + return true; +} + IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHyperTwistBrowserWidgetAuthoritativeShellArtifactsTest, "HyperTwist.Browser.Widget.AuthoritativeShellArtifacts", diff --git a/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json b/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json index a50daa0..36aae3c 100644 --- a/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json +++ b/docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json @@ -1,8 +1,8 @@ { "reportVersion": "ht-higher-dimensional-package-validation/v1", - "generatedAtUtc": "2026-06-22T01:43:08.7625247Z", + "generatedAtUtc": "2026-06-23T10:27:31.6439999Z", "projectRoot": "C:/HyperTwist_worktrees/phase10validate", - "archiveDirectory": "C:/HyperTwist_worktrees/phase10validate_packaged_phase6c_higherdim", + "archiveDirectory": "C:/HyperTwist_worktrees/phase10validate_packaged_phase6c_higherdim_refresh_20260623", "configuration": "Development", "cookMaps": [ "/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining", @@ -50,13 +50,13 @@ ] }, "result": "passed", - "packagedExecutablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim\\Windows\\UnrealHyperTwist.exe", + "packagedExecutablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim_refresh_20260623\\Windows\\UnrealHyperTwist.exe", "smokeReports": [ { "reportVersion": "ht-higher-dimensional-package-smoke/v1", - "generatedAtUtc": "2026-06-22T01:45:12.6335785Z", - "packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim", - "executablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim\\Windows\\UnrealHyperTwist.exe", + "generatedAtUtc": "2026-06-23T10:28:44.7973129Z", + "packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim_refresh_20260623", + "executablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim_refresh_20260623\\Windows\\UnrealHyperTwist.exe", "mapUrl": "/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining", "smokeSeconds": 10, "resolution": { @@ -65,16 +65,16 @@ }, "keepRunning": false, "result": "passed", - "processId": 29436, + "processId": 20320, "processStopped": true, "exitCode": null, "error": null }, { "reportVersion": "ht-higher-dimensional-package-smoke/v1", - "generatedAtUtc": "2026-06-22T01:45:23.3808621Z", - "packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim", - "executablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim\\Windows\\UnrealHyperTwist.exe", + "generatedAtUtc": "2026-06-23T10:28:55.0662545Z", + "packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim_refresh_20260623", + "executablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_phase6c_higherdim_refresh_20260623\\Windows\\UnrealHyperTwist.exe", "mapUrl": "/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining", "smokeSeconds": 10, "resolution": { @@ -83,7 +83,7 @@ }, "keepRunning": false, "result": "passed", - "processId": 35616, + "processId": 13584, "processStopped": true, "exitCode": null, "error": null diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index 084b4c7..3d2db00 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -154,6 +154,24 @@ Use these as the current governing docs: - Validation evidence on `2026-06-19`: the same primary reverse-SSH `localhost:22022` lane rebuilt isolated worktree `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 3258.26 seconds`, then `Automation RunTests HyperTwist.Browser` exported `Saved\AutomationReports\Browser-NativeOperatorDiagnosticsPanel-Verify\index.json` with `14` `HyperTwist.Browser.*` tests succeeded and `0` failed, including `CoachDashboard.RuntimeInspectSurface` and `TrainingPanel.RuntimeInspectSurface` - that same shipping/browser posture now also carries typed browser bootstrap/runtime-ready timestamps, last command and shell-state receipt timestamps, and fallback reason through the native operator and training/dashboard inspect seams, so Unreal-side diagnostics mirror the authoritative browser runtime status surface more faithfully without reopening topology - Validation evidence on `2026-06-19`: the same primary reverse-SSH `localhost:22022` lane rebuilt isolated worktree `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 3261.17 seconds`, then `Automation RunTests HyperTwist.Browser` exported `Saved\AutomationReports\Browser-NativeOperatorTimelineFidelity-Verify\index.json` with `14` `HyperTwist.Browser.*` tests succeeded and `0` failed, including `CoachDashboard.RuntimeInspectSurface`, `TrainingPanel.RuntimeInspectSurface`, and `Widget.OperatorStatusSurfaceLive` +- that same shipping/browser posture now also carries a native control/input + readiness inspect surface through `UHyperTwistTrainingPanelWidget` and + `UHyperTwistCoachDashboardWidget`, so the operator can see the shipped + classic keyboard profile, classic-cube input readiness, + higher-dimensional dedicated-family readiness, project-level + `EnhancedInput`/motion-control groundwork, and explicit unfinished + XR/preferences truth without opening the browser shell +- Validation evidence on `2026-06-23`: the same recovered primary reverse-SSH + `localhost:22022` lane rebuilt maintained isolated worktree + `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and + UnrealBuildTool `Total execution time: 1563.53 seconds`, then + `Automation RunTests HyperTwist.Browser` exported + `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` + with `16` `HyperTwist.Browser.*` tests succeeded and `0` failed, including + `CoachDashboard.ControlInputReadinessInspectSurface`, + `TrainingPanel.ControlInputReadinessInspectSurface`, + `CoachDashboard.RuntimeInspectSurface`, and + `TrainingPanel.RuntimeInspectSurface` - the same optional full-browser-client branch is now also tightened by a backend-contract matrix packet that fixes transport-neutral operation labels, minimum session or host or bridge identity facts, request or result correlation posture, freshness or reconnect rules, and the owned state/runtime payload anchors a later browser-client implementation must preserve - `MagicTile` `Phase 7C` is now landed separately through the bundled tiling native-behavior proof contract/probe seam, runtime-library proof helpers, and focused Windows validation on diff --git a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md index ccd074f..3ae8e72 100644 --- a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md +++ b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md @@ -71,11 +71,14 @@ thin adjacent placeholders. The later continuation also hardened: - `/app/browser-access` with live auth-health posture, browser-to-desktop - handoff guidance, bounded branch truth, and release-reference links + handoff guidance, bounded branch truth, release-reference links, and the same + bounded operator escalation map used by the public support lane - `/app/account` with live release-manifest viewer posture, configured target visibility, and explicit operator follow-through links - `/app/notices` with protected release-target notice posture, operator duties, - and linked protected/public notices plus corresponding-source references + linked protected/public notices plus corresponding-source references, and a + protected release follow-through plus escalation surface that keeps + entitlement, package, runtime, and rollout/compliance issues separated This keeps the protected app shell aligned with the public manual packet so the operator surface remains honest and useful after sign-in rather than becoming a @@ -134,3 +137,215 @@ The same validation hardening also gives the live-spawned `website/server` bootstrap proof an explicit `15s` timeout so real child-process boot plus same-origin HTTP verification does not fail spuriously on a loaded host while the proof scope remains unchanged. + +Latest same-lane follow-up on `2026-06-23` stayed green under the widened +public/protected/auth stack: + +- `npm run type-check` in `website/` +- `npm test -- --run` in `website/` + - `37` test files passed + - `137` tests passed +- `npm run build` in `website/` +- `npm run type-check` in `website/server` +- `npm test -- --run` in `website/server` + - `10` test files passed + - `36` tests passed +- the current server suite again covered the production-shaped bootstrap lane, + including: + - same-origin health plus built-site serving + - webhook/billing-state reflection + - bounded testing-session resolution for `/api/auth/me` and + `/api/auth/desktop-link` +- the embedded browser runtime also stayed green under: + - `npm run verify:shell` in `Content/Browser/` + - `npm run build` in `Content/Browser/` +- the same-day public-manual follow-up also widened the docs/resources manual + depth with a more concrete runtime control guide covering: + - current classic-cube desktop interaction posture + - current higher-dimensional desktop interaction posture + - explicit “supported now” versus “later XR/preferences packet” control + boundaries +- the same-lane continuation then widened the public product surface again with: + - a first-launch and desktop-setup guide on the public `/download` lane + plus matching first-launch follow-through on the protected `/app/downloads` + lane + - the same first-launch follow-through mirrored into the protected dashboard + overview and protected account surface so post-sign-in operator guidance + stays aligned with the public download lane + - a support-side escalation map separating account, package, runtime, and + rollout/compliance issues + - the same escalation separation then mirrored into protected + `/app/browser-access` and `/app/notices` so the signed-in operator lane no + longer drops back to thinner troubleshooting guidance than the public + support surface + - matching focused validation coverage on the public marketing-page suite + plus the protected download-center suite + +That follow-up also stayed green under: + +- `npm run type-check` in `website/` +- `npm test -- --run src/__tests__/public-marketing-pages.test.tsx` in + `website/` +- `npm run build` in `website/` +- `npm test -- --run` in `website/` + - `37` test files passed + - `137` tests passed +- `npm test -- --run src/__tests__/DashboardOverviewPage.test.tsx src/__tests__/download-center-page.test.tsx src/__tests__/protected-app-pages.test.tsx` + in `website/` + - `3` test files passed + - `5` tests passed + +The next same-family protected-surface continuation then stayed green under: + +- `npm run type-check` in `website/` +- `npm test -- --run src/__tests__/protected-app-pages.test.tsx` in `website/` + - `1` test file passed + - `3` tests passed +- `npm test -- --run` in `website/` + - `37` test files passed + - `137` tests passed +- `npm run build` in `website/` +- `scripts/run-hypertwist-sentrux-source-only.sh` + - `Quality: 6112` + - `All rules pass` + +Current truthful reading after that follow-up: + +- the public pages are no longer just content-complete; they are backed by a + fully green current website/runtime/server validation pass +- the shared auth/billing/dashboard/browser-to-desktop handoff surface is now + materially better evidenced than a page-only smoke pass +- the public manual now reads more like a usage guide for the current desktop + runtime instead of only a topology or release-story explanation +- the download/support surfaces now act more like an operator-first launch + manual than a generic storefront wrapper around the desktop build +- the protected dashboard/account/download surfaces now project the same + rollout and first-launch posture after sign-in instead of dropping back to a + thinner release-only shell +- the protected browser-access/notices surfaces now also keep the same + escalation and follow-through separation after sign-in instead of flattening + access, package, runtime, and rollout questions together + +Latest authority-sync follow-up later on `2026-06-23`: + +- the public manual stayed aligned with the newer native + control/input-readiness continuation rather than implying finished VR or + preferences ownership +- focused website/manual coverage stayed green under: + - `npm --prefix website test -- --run src/__tests__/public-marketing-pages.test.tsx src/__tests__/download-center-page.test.tsx src/__tests__/protected-app-pages.test.tsx src/__tests__/DashboardOverviewPage.test.tsx` + - `4` test files passed + - `13` tests passed +- the embedded browser runtime also stayed green again under: + - `npm --prefix Content/Browser run verify:shell` + - `npm --prefix Content/Browser run build` +- the current public/docs/download/account/notices surfaces therefore remain + consistent with the authoritative product truth: + - desktop simulator is primary + - browser shell is complementary and necessary + - keyboard/mouse and higher-dimensional desktop interaction are real now + - XR groundwork exists, but finished XR/preferences ownership is still a + later native packet + +Latest protected-surface resilience follow-up later on `2026-06-23`: + +- the shared release-manifest fallback was tightened so protected browser + surfaces now retain the signed-in viewer posture from local session truth + when live auth-server manifest authority is temporarily unavailable +- the protected download-center lane then also stopped relying only on the + local auth snapshot for the actual download action: + - when live manifest authority resolves fresher entitled viewer truth, the + protected release lane now follows that server-backed viewer posture for + download access instead of leaving the operator blocked behind stale local + state + - when live manifest authority is unavailable, the protected release lane + now explicitly tells the already-entitled operator that release authority + is temporarily unavailable, instead of mislabeling that state as lack of + account entitlement +- the shared browser auth layer then also gained a bounded release-authority + reconciliation path: + - when protected pages receive fresher live manifest viewer truth, the + browser session snapshot is now refreshed and persisted from that release + authority instead of remaining stale until a later bootstrap + - the adjacent protected dashboard and account surfaces now expose a compact + live-authority sync notice summarizing what was refreshed, so operators can + see the session catch-up instead of only inferring drift after the fact +- the same shared auth layer then also tightened stale-session invalidation: + - when shared auth is configured and `/api/auth/me` now returns `401`, the + provider clears the stale stored browser session instead of preserving a + false authenticated protected-shell posture + - the local-fallback lane remains intact when shared auth is not configured + or the account API is simply unreachable, so the stricter invalidation does + not erase the intended offline fallback behavior +- that fallback remains intentionally conservative: + - raw direct package delivery still stays withheld until live manifest + authority returns + - public fallback metadata still does not expose direct download URLs +- focused current validation stayed green under: + - `npm --prefix website run type-check` + - `npm --prefix website test -- --run src/__tests__/release-manifest.test.ts src/__tests__/protected-app-pages.test.tsx src/__tests__/download-center-page.test.tsx src/__tests__/public-auth-pages.test.tsx src/__tests__/public-marketing-pages.test.tsx src/__tests__/DashboardOverviewPage.test.tsx` + - `7` test files passed + - `27` tests passed + - `npm --prefix website/server test -- --run` + - `10` test files passed + - `36` tests passed + - `npm --prefix website test -- --run src/__tests__/platform-auth.bootstrap.test.tsx src/__tests__/download-center-page.test.tsx src/__tests__/protected-app-pages.test.tsx src/__tests__/DashboardOverviewPage.test.tsx src/__tests__/app-route-tree.test.tsx src/__tests__/route-shells.test.tsx src/__tests__/release-manifest.test.ts` + - `8` test files passed + - `34` tests passed + - `scripts/run-hypertwist-sentrux-source-only.sh` + - `Quality: 6105` + - `All rules pass` + +Latest website and dependency hardening follow-up later on `2026-06-23`: + +- the broader website and delivery lane then also stayed green under: + - `npm --prefix website run build` + - `npm --prefix website/server run type-check` + - `npm --prefix website/server test -- --run` + - `npm --prefix Content/Browser run verify:shell` + - `npm --prefix Content/Browser run build` +- the repo now also owns a one-command validation proof for that same public or + protected or embedded-browser website family: + - `scripts/run-hypertwist-web-surface-validation.sh` + - it completed successfully in default mode on `2026-06-23` + - it re-proved the current website checks, auth-server checks, + `Content/Browser` verify/build lane, clean production audits for + `website/` plus `Content/Browser/`, and the documented auth-server + residual warning path +- the same same-family continuation then also tightened the packaged-proof + bridge used by the public/manual website lane: + - the website/browser release surfaces no longer depend on a stale + hand-maintained Windows packaged-validation summary constant + - the current public/protected release surfaces now consume a sanitized + generated summary rendered from the checked-in authoritative higher- + dimensional package report at + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` + - the new renderer lives at + `scripts/render-hypertwist-web-package-validation-summary.mjs` + - the owned web-surface validation gate now checks that generated summary + for freshness before approving the public/auth/download/browser lane +- the browser runtime now also has a checked-in `package-lock.json`, so its + dependency truth is no longer implicit or non-auditable +- production dependency audit truth is now clearer across the current public + and protected website family: + - `npm audit --omit=dev --audit-level=high` in `website/` returned + `found 0 vulnerabilities` + - `npm audit --omit=dev --audit-level=high` in `Content/Browser/` returned + `found 0 vulnerabilities` +- the auth server dependency lane was hardened by upgrading + `supertokens-node` from `21.1.0` to `24.0.2` +- that auth-server upgrade remained type-safe and behavior-safe under the + existing same-origin, auth-health, release-manifest, billing, and + desktop-link server tests +- remaining auth-server dependency risk is now explicit: + - `npm audit --omit=dev --audit-level=high` in `website/server/` still + reports the upstream `supertokens-node -> nodemailer@8.0.11` advisory + chain + - no unsupported forced major override was landed just to hide that result, + because current upstream package metadata still declares `nodemailer` + support through the `^8.0.2` range +- the current public manual therefore remains strengthened rather than + weakened: + - browser and desktop surfaces are better validated + - current package and auth-server posture is more explicit + - unresolved third-party dependency residue is documented honestly instead + of being silently ignored diff --git a/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md b/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md index 146909c..5d779c7 100644 --- a/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md +++ b/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md @@ -258,6 +258,38 @@ Additional same-lane follow-up on `2026-06-23`: docs/resources/support surfaces now explicitly project current keyboard, higher-dimensional control, and unfinished XR/controller truth instead of only the browser-versus-desktop topology boundary + +Further same-lane validation refresh later on `2026-06-23`: + +- `scripts/run-hypertwist-gitnexus-analyze.sh` re-indexed the bounded + source-only mirror successfully at `16,051` nodes, `37,444` edges, + `641` clusters, and `300` flows +- `scripts/run-hypertwist-gitnexus-status.sh` then reported the bounded mirror + `Status: up-to-date` +- `scripts/run-hypertwist-sentrux-source-only.sh` improved again to + `Quality: 6112` with all `7` checked rules passing +- `npm run verify:shell` in `Content/Browser/` passed again +- `npm run build` in `Content/Browser/` passed again +- the public website/manual follow-up also passed: + - `npm run type-check` in `website/` + - `npm test -- --run` in `website/` + - `37` test files passed + - `137` tests passed + - `npm test -- --run src/__tests__/public-marketing-pages.test.tsx` in + `website/` + - `npm run build` in `website/` +- the previously interrupted authoritative remote Unreal editor build was then + recovered on the maintained validation root + `C:\HyperTwist_worktrees\phase10validate` by cleaning the stale + `cmd.exe` / `UnrealBuildTool` tail and rerunning the canonical helper in a + safer single-worker posture: + - `scripts/run-hypertwist-remote-unreal-build.sh --max-parallel-actions 1` + - `Result: Succeeded` + - `Total time in Parallel executor: 3027.11 seconds` + - `Total execution time: 3030.86 seconds` +- that recovered proof materially strengthens the current reading that the + earlier `C1060` compiler-heap failure was a stale/interrupted-lane or + posture issue rather than a stable source regression in the current tree - focused website coverage for `src/__tests__/public-marketing-pages.test.tsx` and `src/__tests__/protected-app-pages.test.tsx` passed after that public-manual @@ -368,6 +400,185 @@ Current highest-signal structural truth after this latest continuation: - the repaired skill-memory fixture lane is now fully green on the real remote Windows Unreal path across all `9` focused `S3` reports +Most recent same-family continuation later on `2026-06-23`: + +- `scripts/run-hypertwist-gitnexus-analyze.sh` re-indexed the bounded + source-only mirror successfully at `16,061` nodes, `37,459` edges, + `646` clusters, and `300` flows +- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded + mirror `Status: up-to-date` +- `scripts/run-hypertwist-sentrux-source-only.sh` remained green at + `Quality: 6111` with all `7` checked rules passing +- the embedded browser runtime stayed green under: + - `npm --prefix Content/Browser run verify:shell` + - `npm --prefix Content/Browser run build` +- focused current website/manual validation stayed green under: + - `npm --prefix website test -- --run src/__tests__/public-marketing-pages.test.tsx src/__tests__/download-center-page.test.tsx src/__tests__/protected-app-pages.test.tsx src/__tests__/DashboardOverviewPage.test.tsx` + - `4` test files passed + - `13` tests passed +- the recovered authoritative Unreal continuation on maintained validation root + `C:\HyperTwist_worktrees\phase10validate` then stayed green under: + - `scripts/run-hypertwist-remote-unreal-build.sh --max-parallel-actions 8` + - `Result: Succeeded` + - `Total time in Parallel executor: 1552.99 seconds` + - `Total execution time: 1563.53 seconds` +- the same recovered lane then exported + `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` + with `16` green `HyperTwist.Browser.*` tests, including: + - `HyperTwist.Browser.CoachDashboard.ControlInputReadinessInspectSurface` + - `HyperTwist.Browser.TrainingPanel.ControlInputReadinessInspectSurface` + +Latest same-family tool and website resilience follow-up later on +`2026-06-23`: + +- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded + source-only mirror `Status: up-to-date` +- `scripts/run-hypertwist-sentrux-source-only.sh` stayed green at + `Quality: 6110` with all `7` checked rules passing +- that slight score drift from the immediately prior `6111` reading did not + reopen any rule failures, cycle debt, or browser/website structural + violations +- the protected release-manifest fallback was then hardened so dashboard, + account, browser-access, and notices surfaces keep signed-in viewer posture + from the local session when live auth-server manifest authority is + unavailable, while raw download delivery authority still remains withheld +- the same bounded website lane stayed green under: + - `npm --prefix website run type-check` + - `npm --prefix website test -- --run src/__tests__/release-manifest.test.ts src/__tests__/protected-app-pages.test.tsx src/__tests__/download-center-page.test.tsx src/__tests__/public-auth-pages.test.tsx src/__tests__/public-marketing-pages.test.tsx src/__tests__/DashboardOverviewPage.test.tsx` + - `7` test files passed + - `27` tests passed + - `npm --prefix website/server test -- --run` + - `10` test files passed + - `36` tests passed + +Latest dependency and validation hardening follow-up later on `2026-06-23`: + +- `scripts/run-hypertwist-gitnexus-analyze.sh` re-indexed the bounded + source-only mirror successfully at `16,089` nodes, `37,536` edges, + `645` clusters, and `300` flows +- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded + mirror `Status: up-to-date` +- `scripts/run-hypertwist-sentrux-source-only.sh` stayed green at + `Quality: 6105` with all `7` checked rules passing +- the broader current website/runtime validation packet stayed green under: + - `npm --prefix website run build` + - `npm --prefix website/server run type-check` + - `npm --prefix website/server test -- --run` + - `npm --prefix Content/Browser run verify:shell` + - `npm --prefix Content/Browser run build` +- the repo now also owns a single bounded validation command for that same + browser/public/auth-server surface: + - `scripts/run-hypertwist-web-surface-validation.sh` + - it runs the current website type-check or focused auth-route tests or + website build or website/server type-check or server tests or + `Content/Browser` verify/build sequence in one reproducible packet + - it also runs production dependency audits for `website/`, + `website/server/`, and `Content/Browser/` + - it treats the current exact upstream `supertokens-node -> nodemailer` + auth-server advisory as a documented residual warning in default mode + rather than forcing an unsupported major override + - `--strict-auth-server-audit` remains available when the lane should fail on + that residual too +- that new wrapper was immediately validated in default mode on `2026-06-23` + and completed successfully across website checks, website/server checks, + `Content/Browser` verification/build, clean production audits for + `website/` plus `Content/Browser/`, and the documented auth-server residual + warning path +- the same same-family continuation then also replaced the stale hand-maintained + website packaged-validation constant with a generated sanitized summary + rendered from + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` + into + `website/src/shared/generated/windows-package-validation-summary.json` +- the repo now owns that bridge through: + - `scripts/render-hypertwist-web-package-validation-summary.mjs` + - `scripts/run-hypertwist-web-surface-validation.sh`, which now fails if the + generated website summary drifts stale from the checked-in authoritative + higher-dimensional package report +- the browser runtime now also has an auditable lockfile at + `Content/Browser/package-lock.json` +- production dependency audit truth after that lockfile continuation is now: + - `npm audit --omit=dev --audit-level=high` in `website/` returned + `found 0 vulnerabilities` + - `npm audit --omit=dev --audit-level=high` in `Content/Browser/` returned + `found 0 vulnerabilities` +- full browser-runtime audit still reports one low-severity development-only + advisory: + - `esbuild@0.27.7` through `vite@7.3.5` + - impact is the Windows development server lane rather than shipped browser + runtime output +- the auth server was then upgraded from `supertokens-node@21.1.0` to + `supertokens-node@24.0.2` +- that auth-server upgrade stayed green under: + - `npm --prefix website/server run type-check` + - `npm --prefix website/server test -- --run` +- remaining auth-server production audit truth is now explicit rather than + hidden: + - `npm audit --omit=dev --audit-level=high` in `website/server/` still + reports the upstream `supertokens-node -> nodemailer@8.0.11` advisory + chain + - upstream package metadata currently pins `nodemailer` as `^8.0.2` while + the current `nodemailer` latest is `9.0.1` + - no unsafe forced major override was landed, because that would cross the + supported upstream dependency range without first-party compatibility proof + +Latest refactor-tooling follow-up later on `2026-06-23`: + +- HyperTwist now has a verified repo-local analyzer binary present at: + - `tools/sentrux/bin/sentrux` +- `scripts/bootstrap-hypertwist-sentrux.sh --if-missing` now truthfully reports + that repo-local analyzer as already materialized instead of relying on + cross-repo memory +- `scripts/run-hypertwist-sentrux-source-only.sh` was re-run again on the + bounded source-only mirror and currently reports: + - `Quality: 6108` + - all `7` checked rules passing +- current highest-signal structural reading after that rerun is: + - no current browser/public/auth-server structural regression is being + reported by the owned source-only gate + - the same owned source-only mirror still covers + `UnrealHyperTwist/Source`, `Content/Browser/src`, `website/src`, + `website/server/src`, and `scripts` +- `scripts/run-hypertwist-gitnexus-analyze.sh` was then hardened further for + current-host truth: + - it still prefers the retained local CLI first + - if that real analyze path does not complete cleanly on the current host, it + now falls back explicitly to `npx -y gitnexus@latest` + - it now suppresses the retained local native-loader stderr spew during that + rejected local-analyze attempt so the wrapper output stays operationally + useful + - it now also distinguishes three states instead of flattening them together: + - clean successful analyze exit + - timeout before any readable bounded-mirror index exists + - timeout after `.gitnexus/meta.json` already proves the current disposable + mirror commit was indexed, which the wrapper can now treat as success +- short-timeout proof on the current host also established a useful safety + truth: + - a too-short timeout no longer yields a false success + - the wrapper now exits truthfully when no readable bounded-mirror index is + yet available +- because the current retained local `GitNexus` analyze path still does not + complete cleanly on this Linux host, the truthful current operator posture + remains: + - treat repo-local `sentrux` as the fast owned structural baseline + - use the bounded `GitNexus` wrapper when the deeper graph/impact pass is + worth the longer or fallback-prone run + - do not confuse a locally present retained `GitNexus` checkout with a + guaranteed host-compatible native analyze path +- a full-length follow-up on the same host then re-proved the bounded + `GitNexus` lane through the hardened wrapper: + - the wrapper rejected the broken retained local analyze path + - the fallback path then completed successfully in `79.2s` + - current bounded-mirror graph stats are now: + - `16,116` nodes + - `37,586` edges + - `647` clusters + - `300` flows +- `scripts/run-hypertwist-sentrux-source-only.sh` was then rerun again after + that graph refresh and remained at: + - `Quality: 6108` + - all `7` checked rules passing + ## Out of scope This note does not: diff --git a/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md b/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md index 2776b75..fac3811 100644 --- a/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md +++ b/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md @@ -221,6 +221,21 @@ This extends the doctrine from higher-dimensional activation/ownership proof into current structured package/archive/launch proof on the same primary reverse-SSH lane. +Further higher-dimensional package-refresh proof on `2026-06-23` then +revalidated that same maintained helper lane after the recovered authoritative +editor rebuild: the isolated Windows worktree +`C:\HyperTwist_worktrees\phase10validate` still had the packaged-game target +receipt, so `scripts\Invoke-HyperTwistHigherDimensionalPackage.ps1` ran again +in `-SkipBuild` mode, completed `BuildCookRun` with `ExitCode=0` and +`BuildCookRun time: 70.74 s`, wrote archive output to +`C:\HyperTwist_worktrees\phase10validate_packaged_phase6c_higherdim_refresh_20260623`, +smoke-launched both higher-dimensional dedicated-family training maps again, +and refreshed the checked-in aggregate proof at +`docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json`. +This extends the doctrine from a one-off helper proof into a re-proven +maintained package lane that still stays green after later editor-build +recovery work on the same primary reverse-SSH path. + Further `2026-06-13` continuation proof on that same primary lane recovered the classic-cube replay/leaderboard/package slice after a corrupted reverse sync incident: a prior aborted broad tar sync left remote @@ -400,6 +415,24 @@ This extends the doctrine from classic-cube, package, higher-dimensional, and media-export proof into the real remote skill-memory validation lane on the same maintained reverse-SSH path. +Further live recovery-proof follow-up on `2026-06-23` established: + +- a later interrupted remote editor-build attempt had left a stale + `cmd.exe` / `UnrealBuildTool` tail alive on the maintained validation root + without active `cl.exe` workers, so the lane was not cleanly post-command +- after terminating only that stale toolchain tail, the same maintained + validation root `C:\HyperTwist_worktrees\phase10validate` was rerun through + the canonical helper in the safer single-worker posture + `scripts/run-hypertwist-remote-unreal-build.sh --max-parallel-actions 1` +- that recovered authoritative rebuild completed with: + - `Result: Succeeded` + - `Total time in Parallel executor: 3027.11 seconds` + - `Total execution time: 3030.86 seconds` +- this recovery materially strengthens the current reading that the earlier + `fatal error C1060: compiler is out of heap space` result belonged to the + stale/interrupted or high-pressure lane posture rather than to a stable + source break in the current HyperTwist tree + ## Closeout wording requirement Every Unreal C++ closeout should say one of these explicitly: diff --git a/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md b/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md index b552db4..b0f756f 100644 --- a/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md +++ b/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md @@ -17,6 +17,8 @@ HyperTwist currently has real native input ownership for: - `EnhancedInput`-based project posture - broad motion-controller axis configuration at the Unreal project-settings level +- a native operator/training inspect surface that keeps those truths and the + unfinished XR/preferences boundary visible inside Unreal itself HyperTwist does **not** yet have enough current first-party runtime evidence to truthfully claim: @@ -47,6 +49,42 @@ truthfully claim: Vive, Mixed Reality, Oculus Touch, and Valve Index families - the same file keeps mouse capture, wheel, and motion-control settings active +### Native operator/training inspect truth is now real + +- `FHyperTwistTrainingControlInputReadinessInspectSurface` now exists in + `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` +- `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now + expose that surface and keep the same truth visible inside native training + or operator flows +- a same-family native quality follow-up on `2026-06-23` then tightened the + project-input-groundwork inspection behind that surface so it now reads + Unreal input settings structurally from config instead of reparsing raw + `DefaultInput.ini` text on each surface read +- the current surface explicitly reports: + - shipped `classic-wca-keyboard/v1` + - classic-cube mouse/touch/shortcut readiness + - higher-dimensional dedicated-family runtime-catalog readiness + - `EnhancedInput` plus motion-controller groundwork presence + - immersive-presence contract presence + - unfinished XR runtime and user-facing preferences/rebinding truth +- Windows validation on `2026-06-23` rebuilt the maintained validation root + `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, + UnrealBuildTool `Total execution time: 1563.53 seconds`, then exported + `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` + with all `16` `HyperTwist.Browser.*` tests green, including the new + training-panel and coach-dashboard control/input readiness checks +- the later same-day config-inspection hardening follow-up then hash-synced + `HyperTwistTrainingPanelWidget.cpp` into that same maintained validation + root, rebuilt cleanly again with `Result: Succeeded`, UnrealBuildTool + `Total execution time: 158.13 seconds`, and re-exported focused browser + automation proof to + `Saved\AutomationReports\Browser-ControlInputReadiness-StructuredConfig-Verify` + with all `16` `HyperTwist.Browser.*` tests green again, including: + - `CoachDashboard.ControlInputReadinessInspectSurface` + - `TrainingPanel.ControlInputReadinessInspectSurface` + - `CoachDashboard.RuntimeInspectSurface` + - `TrainingPanel.RuntimeInspectSurface` + ### Finished XR runtime ownership is not yet proven - `UnrealHyperTwist/UnrealHyperTwist.uproject` currently enables @@ -67,6 +105,7 @@ Current truthful product wording should say: - desktop Unreal simulator: real and shipping - classic input and higher-dimensional keyboard interaction: real and shipping +- native operator/training diagnostics already surface current input truth - immersive/scenic training direction: first-party owned and source-backed - full VR/controller/settings polish lane: not yet complete enough to market as finished diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md index 5a5713c..930fae1 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md @@ -34,6 +34,8 @@ HyperTwist now has its own bounded refactor/analyzer entry points: - `scripts/run-hypertwist-sentrux-source-only.sh` - `scripts/run-hypertwist-gitnexus-analyze.sh` - `scripts/run-hypertwist-gitnexus-status.sh` +- `scripts/run-hypertwist-web-surface-validation.sh` +- `scripts/render-hypertwist-web-package-validation-summary.mjs` - `.sentrux/rules.toml` Use them with this posture: @@ -61,6 +63,50 @@ Suggested loop: 4. treat `sentrux` failures as structural review signals, then confirm with focused product tests +Additional dependency-health loop for the current browser and website family: + +1. prefer the owned umbrella command: + `scripts/run-hypertwist-web-surface-validation.sh` +2. that command covers: + - `npm --prefix website run type-check` + - focused current website route/auth/release tests + - `npm --prefix website run build` + - `npm --prefix website/server run type-check` + - `npm --prefix website/server test -- --run` + - `npm --prefix Content/Browser run verify:shell` + - `npm --prefix Content/Browser run build` +3. when audits need to be reasoned about directly, run: + - `npm audit --omit=dev --audit-level=high` in `website/` + - `npm audit --omit=dev --audit-level=high` in `website/server/` + - `npm audit --omit=dev --audit-level=high` in `Content/Browser/` + +Current dependency truth after the `2026-06-23` hardening follow-up: + +- `website/` production audit is clean +- `Content/Browser/` production audit is clean and now has a checked-in + `package-lock.json` +- `website/server/` was upgraded to `supertokens-node@24.0.2`, but still + carries an upstream `supertokens-node -> nodemailer@8.0.11` advisory in the + production audit +- `scripts/run-hypertwist-web-surface-validation.sh` accepts that exact + documented upstream auth-server residual in default mode, but + `--strict-auth-server-audit` turns it back into a blocking failure +- do not hide that auth-server advisory with an unsupported forced major + override of `nodemailer` unless later upstream compatibility proof exists + +Current packaged-proof bridge truth after the same `2026-06-23` continuation: + +- the website/browser lane no longer relies on a stale hand-maintained Windows + packaged-validation summary constant +- `scripts/render-hypertwist-web-package-validation-summary.mjs` now renders a + sanitized summary from + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` + into + `website/src/shared/generated/windows-package-validation-summary.json` +- `scripts/run-hypertwist-web-surface-validation.sh` now checks that generated + summary for freshness before it validates the current website/auth-server/ + embedded-browser surface + ## Canonical development authorities Use these before widening implementation: diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index bafd41e..50cbc6f 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -144,7 +144,8 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | First-party provider-neutral custom-endpoint runtime routing | Implemented now | landed first-party `Phase 6R-AI` | First-party per-session provider-profile endpoint selection, runtime routing, fallback-health reflection, and bounded provider-backed transport-failure posture are now live above the landed provider-profile/BYOK and provider-routing seams. Provider-specific overlays, payment execution, provider-portal ownership, and payload shipping remain deferred. | | Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. | | Rewritten training analytics and report reference grounding | Implemented now | `apache/echarts` retained permissive lane + first-party current code | Current live `Training Analytics` reference side includes four rewritten first-party targets grounded in retained `apache/echarts`: session outcome and progress reporting, analytics data-view and export, timing-trend history and overview interaction, and the optional richer explainer or sidecar boundary. This does not displace the landed `Phase 3R-C` first-party analytics/reporting owner or elevate `ecomfe/echarts-gl` and `ecomfe/zrender` beyond support-only sidecars. | -| Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready, a same-day first-party `Browser Runtime Status` surface with shared boot-state ownership across bundled and fallback shells, a follow-on typed Unreal-side `browser-runtime-status` capture seam that retains the last valid runtime snapshot across unrelated later envelope traffic until explicit reset, a second follow-on typed Unreal-side `hypertwist-runtime-ready` capture seam that raises the original handshake payload out of raw JSON-only handling without changing queue flush behavior, a third same-day native/operator-facing status surface that consumes those typed seams inside Unreal while clearing retained runtime ownership on shell-authority change, a fourth same-day native training/operator diagnostics-panel continuation that wires that typed status ownership into `UHyperTwistTrainingPanelWidget` and the coach dashboard without making the dashboard inspect seam depend on stale rendered strings, and a fifth same-day diagnostics-fidelity continuation that now carries bootstrap/runtime-ready timestamps, last command/shell-state receipt timestamps, and fallback reason through the same native operator/training seams. This still is not proof of unlimited browser-shell parity. | +| Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready, a same-day first-party `Browser Runtime Status` surface with shared boot-state ownership across bundled and fallback shells, a follow-on typed Unreal-side `browser-runtime-status` capture seam that retains the last valid runtime snapshot across unrelated later envelope traffic until explicit reset, a second follow-on typed Unreal-side `hypertwist-runtime-ready` capture seam that raises the original handshake payload out of raw JSON-only handling without changing queue flush behavior, a third same-day native/operator-facing status surface that consumes those typed seams inside Unreal while clearing retained runtime ownership on shell-authority change, a fourth same-day native training/operator diagnostics-panel continuation that wires that typed status ownership into `UHyperTwistTrainingPanelWidget` and the coach dashboard without making the dashboard inspect seam depend on stale rendered strings, a fifth same-day diagnostics-fidelity continuation that now carries bootstrap/runtime-ready timestamps, last command/shell-state receipt timestamps, and fallback reason through the same native operator/training seams, and a later `2026-06-23` control/input readiness continuation that projects shipped keyboard/input truth and unfinished XR/preferences truth through the same native training/operator surfaces. This still is not proof of unlimited browser-shell parity. | +| Native control/input readiness inspect surface | Implemented now | landed first-party `2026-06-23` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlInputReadinessInspectSurface`, including the shipped `classic-wca-keyboard/v1` mapping, classic-cube mouse/touch/shortcut readiness, higher-dimensional dedicated-family runtime-catalog truth, project-level `EnhancedInput` plus motion-controller groundwork facts, immersive-presence contract presence, and explicit unfinished XR/runtime plus preferences truth. The recovered primary reverse-SSH `localhost:22022` lane rebuilt maintained validation worktree `C:\HyperTwist_worktrees\phase10validate` on `2026-06-23` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1563.53 seconds`, and exported `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` with all `16` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlInputReadinessInspectSurface` and `TrainingPanel.ControlInputReadinessInspectSurface`. A later same-day quality follow-up then moved the project-input-groundwork inspection behind that surface from raw text scanning into structured Unreal config reads, rebuilt the same maintained validation worktree again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 158.13 seconds`, and re-exported focused browser proof to `Saved\AutomationReports\Browser-ControlInputReadiness-StructuredConfig-Verify` with all `16` `HyperTwist.Browser.*` tests green again. | | Optional full-browser client path above Unreal backend authority | Deep-source grounded retained | landed first-party `Phase 8B` preparation packet + spec/evidence continuation + backend-contract continuation | This branch remains a spec-only first-party option: a browser-owned rendering and input shell may sit above explicit Unreal backend seams while Unreal remains authoritative for puzzle, training, timer, replay, and persistence state. The current continuation now fixes the allowed seam families to catalog, session, action, state, continuity push, replay, persistence status, and runtime health, then further fixes transport-neutral operation labels, minimum identity or freshness requirements, and the owned state/runtime payload anchors that any later browser-client backend implementation must preserve. The landed embedded browser/CEF shell remains the current shipping posture, and this branch stays separate from any native `MagicTile` behavior or renderer widening. | | Rewritten browser spatial scene and renderer reference grounding | Implemented now | `mrdoob/three.js` retained permissive lane + first-party current code | Current live `Browser 3D and XR Support` reference side includes one rewritten first-party target grounded in retained `mrdoob/three.js`: the browser spatial scene and renderer contract. This does not displace the landed `Phase 3R-F` first-party browser spatial owner trio or absorb the adjacent `react-three-fiber` renderer-bridge and `xr` session slices. | | Rewritten React-side browser renderer and event-bridge grounding | Implemented now | `pmndrs/react-three-fiber` retained permissive lane + first-party current code | Current live `Browser 3D and XR Support` reference side includes one rewritten first-party target grounded in retained `react-three-fiber`: the React scene renderer and event bridge. This does not displace the landed `Phase 3R-F` first-party browser spatial owner trio or absorb the adjacent `three.js` scene substrate and `xr` session slices. | @@ -264,9 +265,9 @@ repo. | Feature | Status | Primary authority | Notes | |---|---|---|---| -| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same public lane now also serves as a bounded operator/distribution manual through the docs/resources/download/support surfaces, explaining browser-versus-desktop posture, rollout steps, package proof, simulator-use guidance, and current input/device truth without claiming browser ownership of the native runtime or overclaiming unfinished VR/controller posture. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. A later same-family continuation then widened that release-manifest authority again to carry public runtime commerce config for operator/studio checkout URLs and live plan-price strings, so pricing, notices, and dashboard launch-readiness surfaces no longer depend only on frontend build-time checkout config. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. | -| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me` and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. | -| Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The same lane now also owns a shared `GET /api/releases/manifest` runtime authority for release version/channel/build/published/file-size/checksum/docs/source metadata, with anonymous callers intentionally denied raw download URLs while entitled session-backed callers receive the configured direct platform URL. That manifest now also carries first-party packaged-validation summary truth for the Windows higher-dimensional desktop lane, so the public `/download` page, the public `/resources` reference page, and the protected `/app/downloads` surface can project real package evidence for the dedicated-family `Magic120Cell` / `MagicCube5D` maps even while launch-tier release URLs remain unconfigured. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly, preserves requested platform continuity through `/app/downloads?platform=...`, and surfaces that requested target again after auth handoff inside the protected release lane. Both the public and protected download surfaces now also carry first-party rollout steps plus release/notices/source references so the desktop setup lane is more than a generic link bucket, and the dashboard plus public launch-status callouts now consume the same manifest-backed Windows download truth instead of only static frontend config. Actual release URLs remain deployment configuration rather than hardcoded product truth. | +| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same public lane now also serves as a bounded operator/distribution manual through the docs/resources/download/support surfaces, explaining browser-versus-desktop posture, rollout steps, package proof, simulator-use guidance, and current input/device truth without claiming browser ownership of the native runtime or overclaiming unfinished VR/controller posture. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. A later same-family continuation then widened that release-manifest authority again to carry public runtime commerce config for operator/studio checkout URLs and live plan-price strings, so pricing, notices, and dashboard launch-readiness surfaces no longer depend only on frontend build-time checkout config. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. The website lane now also owns a sanitized generated packaged-validation summary for the Windows higher-dimensional desktop proof, rendered from the checked-in authoritative package report into `website/src/shared/generated/windows-package-validation-summary.json`, and the owned web-surface validation gate now checks that generated proof for freshness before approving the current public/auth/download/browser lane. | +| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. The adjacent protected `/app/browser-access` and `/app/notices` routes now also reuse the bounded escalation-map and release-follow-through guidance from the public support/manual lane, so sign-in does not degrade operator troubleshooting quality into a thinner shell than the public surface. The protected fallback path now also preserves signed-in viewer plan/access posture from local session truth when live manifest authority is temporarily unavailable, while still keeping download authority conservative until the auth server returns. The shared browser auth layer now also has a bounded release-authority reconciliation path that refreshes and persists the local browser session snapshot from fresher manifest viewer truth, while the dashboard and account surfaces expose a compact live-authority sync notice summarizing what changed so operators can see the session catch-up clearly. That same auth layer now also clears stale stored sessions when shared auth is configured and `/api/auth/me` returns `401`, while still preserving the intended offline/local-fallback posture when the account API is merely unreachable or shared auth is not configured. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me`, stale-session invalidation, release-authority reconciliation, and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. | +| Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The same lane now also owns a shared `GET /api/releases/manifest` runtime authority for release version/channel/build/published/file-size/checksum/docs/source metadata, with anonymous callers intentionally denied raw download URLs while entitled session-backed callers receive the configured direct platform URL. That manifest now also carries first-party packaged-validation summary truth for the Windows higher-dimensional desktop lane, so the public `/download` page, the public `/resources` reference page, and the protected `/app/downloads` surface can project real package evidence for the dedicated-family `Magic120Cell` / `MagicCube5D` maps even while launch-tier release URLs remain unconfigured. That website-facing packaged proof is now sourced through a sanitized generated summary rendered from the checked-in authoritative `phase6c_dedicated_family_package_validation_report.json`, and the owned web-surface validation gate now checks that generated summary for freshness before approving the current public/auth/download/browser lane. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly, preserves requested platform continuity through `/app/downloads?platform=...`, and surfaces that requested target again after auth handoff inside the protected release lane. When live manifest authority is unavailable, the protected fallback path now still reflects the signed-in viewer posture from local session truth while continuing to withhold raw delivery authority. When live manifest authority resolves fresher entitled viewer truth than the local browser auth snapshot, the protected download-center lane now also follows that server-backed viewer posture for the actual download action instead of leaving the operator blocked behind stale local session state. Both the public and protected download surfaces now also carry first-party rollout steps, first-launch desktop setup guidance, browser-to-desktop pairing follow-through, and release/notices/source references so the desktop setup lane is more than a generic link bucket, while the support surface now carries an explicit escalation map separating account, package, runtime, and rollout/compliance problems. The protected dashboard overview and protected account surface now also mirror that same first-launch follow-through, and the protected browser-access/notices routes now mirror the same escalation separation, so post-sign-in operator guidance does not collapse back into a thinner release-only shell. The dashboard plus public launch-status callouts now consume the same manifest-backed Windows download truth instead of only static frontend config. Actual release URLs remain deployment configuration rather than hardcoded product truth. | | Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. The shared `GET /api/releases/manifest` authority now also carries public runtime commerce config for operator/studio checkout URLs and live plan-price strings, allowing the pricing page to switch from frontend build-time checkout assumptions to auth-server runtime truth when those values are configured. A spawned live-process proof now also verifies that a real signed webhook updates processed-event health and persisted billing state through the actual auth-server runtime, not only helper-level store tests, and transaction events no longer leak their id into stored `subscriptionId` state. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. | | Public open-source notices and corresponding-source surface | Implemented now | first-party `website/` app + `HYPERTWIST_MPL_DISTRIBUTION_PLACEMENT_CHECKLIST_2026-05-25.md` | HyperTwist now has a stable public `Open Source Notices` route linked from pricing, download, and footer surfaces, satisfying the requirement that public distribution surfaces expose notice and corresponding-source guidance when shipped builds contain `MPL`-covered material. The exact public corresponding-source URL still must be configured before external launch. | @@ -290,7 +291,7 @@ repo. | Higher-dimensional initial shared-map launch and view-context execution bridge | Implemented now | landed `Phase 6C` continuation packet | First-party current code first bundled higher-dimensional launch surfaces and view-context surfaces for `Magic120Cell` and `MagicCube5D`, resolved those surfaces through the training runtime library plus the live training panel/session-actor caches, and let the training subsystem start the shared-map seed run, apply default selector posture, persist the generated-mode launch request, and start the owned generated run without widening into external-process ownership. After recovering an interrupted follow-up, the primary reverse-SSH `localhost:22022` Windows lane rebuilt that cleaned slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 124.81 seconds`, and passed all `8` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including `LaunchCatalog`, `ViewContextCatalog`, `LaunchExecution`, and `RunStateActivationResolution`; that initial bridge is now superseded by the later dedicated-family map ownership continuation. | | Higher-dimensional explicit dedicated-host seam | Implemented now | landed `Phase 6C` host-surface continuation packet | First-party current code then recorded the preferred dedicated-family `Magic120Cell` / `MagicCube5D` host-map targets in an explicit higher-dimensional host-surface catalog while the slice still kept the shared training map as the interim effective runtime fallback, mirrored that resolved host seam through the training runtime library plus the panel/session caches, and threaded the host-surface id into the launch/view-context surfaces and generated-mode lineage. The primary reverse-SSH `localhost:22022` Windows lane rebuilt this widened slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2858.02 seconds`, and passed all `9` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including the new `HostCatalog`; that interim fallback is now superseded by the later dedicated-family map ownership continuation. | | Higher-dimensional executed runtime session surface | Implemented now | landed `Phase 6C` session-surface continuation packet | First-party current code now merges the higher-dimensional launch, host, and view-context seams into an explicit runtime-session catalog for `Magic120Cell` and `MagicCube5D`, mirrors that session surface through the training runtime library plus the panel/session caches, upgrades generated-mode deck lineage to record the resolved session surface, and applies the merged launch-plus-view selector posture at start time instead of only the narrower launch defaults. The primary reverse-SSH `localhost:22022` Windows lane rebuilt this widened slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 3541.98 seconds`, and passed all `10` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests, including the new `SessionCatalog`; the longer `LaunchExecution` quiet interval of `573.84 seconds` remained known-good. | -| Higher-dimensional dedicated-family interactive scene ownership | Implemented now | landed `Phase 6C` interactive-scene and dedicated-host continuation packet | First-party current code now promotes the higher-dimensional runtime-session seams into explicit `Magic120Cell` and `MagicCube5D` interactive-scene surfaces with family-owned runtime-state, projection, and persistence envelopes, replaces the shared-map bridge with dedicated-family training-map assets plus dedicated host/launch/view-context/session surfaces, exposes explicit scene-envelope decoders through the runtime library, batches default selector application so generated-mode launch execution no longer rebuilds repository views once per selector, and then defers repository-view projection across the internal generated-mode handoff itself so only the final launch state forces a view refresh. The primary reverse-SSH `localhost:22022` Windows lane rebuilt the dedicated-family ownership slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1648.33 seconds`; the later projection-deferral hardening rebuild completed in `400.77 seconds`, and the exported automation report passed all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests with `totalDuration` `41.88494873046875`, `LaunchExecution` duration `41.53509521484375`, and the controller quiet interval reduced to `41.52 seconds`. The current recovery-proof continuation now also deterministically reauthors the dedicated-family maps through `scripts/hypertwist_author_higher_dimensional_training_maps.py`, records manifest-backed owned hashes in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json`, preserves the optional `AHyperTwistHigherDimensionalTrainingShellActor` for manual/editor authoring while intentionally skipping it on the headless commandlet lane because that spawn path still crashes `UnrealEditor-Cmd`, passes all `13` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests through pulled proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json`, including `DedicatedMapAuthoringManifest` and `DedicatedMapOwnedShell`, and now has a maintained packaged-validation seam through `scripts/Invoke-HyperTwistHigherDimensionalPackage.ps1` plus `scripts/Launch-HyperTwistHigherDimensionalPackage.ps1`. That helper was then live-validated on `2026-06-22` against isolated worktree `C:\HyperTwist_worktrees\phase10validate` in `-SkipBuild` mode, with `BuildCookRun time: 118.53 s`, archive output in `C:\HyperTwist_worktrees\phase10validate_packaged_phase6c_higherdim`, both dedicated-family packaged smoke maps green, and pulled aggregate proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json`. | +| Higher-dimensional dedicated-family interactive scene ownership | Implemented now | landed `Phase 6C` interactive-scene and dedicated-host continuation packet | First-party current code now promotes the higher-dimensional runtime-session seams into explicit `Magic120Cell` and `MagicCube5D` interactive-scene surfaces with family-owned runtime-state, projection, and persistence envelopes, replaces the shared-map bridge with dedicated-family training-map assets plus dedicated host/launch/view-context/session surfaces, exposes explicit scene-envelope decoders through the runtime library, batches default selector application so generated-mode launch execution no longer rebuilds repository views once per selector, and then defers repository-view projection across the internal generated-mode handoff itself so only the final launch state forces a view refresh. The primary reverse-SSH `localhost:22022` Windows lane rebuilt the dedicated-family ownership slice on `2026-06-18` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1648.33 seconds`; the later projection-deferral hardening rebuild completed in `400.77 seconds`, and the exported automation report passed all `11` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests with `totalDuration` `41.88494873046875`, `LaunchExecution` duration `41.53509521484375`, and the controller quiet interval reduced to `41.52 seconds`. The current recovery-proof continuation now also deterministically reauthors the dedicated-family maps through `scripts/hypertwist_author_higher_dimensional_training_maps.py`, records manifest-backed owned hashes in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_map_manifest.json`, preserves the optional `AHyperTwistHigherDimensionalTrainingShellActor` for manual/editor authoring while intentionally skipping it on the headless commandlet lane because that spawn path still crashes `UnrealEditor-Cmd`, passes all `13` `HyperTwist.FirstParty.HigherDimensional.Phase6C` tests through pulled proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_map_ownership_automation_report.json`, including `DedicatedMapAuthoringManifest` and `DedicatedMapOwnedShell`, and now has a maintained packaged-validation seam through `scripts/Invoke-HyperTwistHigherDimensionalPackage.ps1` plus `scripts/Launch-HyperTwistHigherDimensionalPackage.ps1`. That helper was then live-validated on `2026-06-22` against isolated worktree `C:\HyperTwist_worktrees\phase10validate` in `-SkipBuild` mode, with `BuildCookRun time: 118.53 s`, archive output in `C:\HyperTwist_worktrees\phase10validate_packaged_phase6c_higherdim`, both dedicated-family packaged smoke maps green, and pulled aggregate proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json`. The same maintained helper lane was refreshed again on `2026-06-23` after the recovered authoritative editor rebuild, again in `-SkipBuild` mode against the existing receipt, with `BuildCookRun time: 70.74 s`, archive output in `C:\HyperTwist_worktrees\phase10validate_packaged_phase6c_higherdim_refresh_20260623`, both dedicated-family packaged smoke maps green again, and the checked-in aggregate report refreshed in place. | | Broad non-Euclidean interaction shell and host ownership | Deep-source grounded retained | `MagicTile` retained remainder | Broad interaction-shell, WinForms/OpenTK host ownership, native spherical/hyperbolic renderer ownership beyond the landed first-party native behavior proof, and generic runtime replacement remain deferred after the landed `Phase 6R-T`, `Phase 7A`, `Phase 7B`, and `Phase 7C` slices, with later native renderer reopening now governed by the separate spec-only `Phase 7D` decision gate. | ### 8. Speech input and voice sidecars diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 6a62514..37b613a 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -93,6 +93,15 @@ Current consolidated milestone snapshot: `BuildCookRun time: 118.53 s`, both dedicated-family packaged smoke maps green, and pulled aggregate proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` +- that same maintained higher-dimensional package lane was then refreshed again + on `2026-06-23` after the recovered authoritative editor rebuild, still + through the primary reverse-SSH `localhost:22022` lane against isolated + Windows worktree `C:\HyperTwist_worktrees\phase10validate`, again using + `-SkipBuild` on the already-present game receipt, archive output in + `C:\HyperTwist_worktrees\phase10validate_packaged_phase6c_higherdim_refresh_20260623`, + `BuildCookRun time: 70.74 s`, both dedicated-family packaged smoke maps + green again, and refreshed aggregate proof in + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` - the earlier dedicated-map package proof used the verified fallback reverse-SSH lane on `localhost:22023`, an isolated Windows worktree at `C:\HyperTwist_worktrees\phase3to5`, authored dedicated classic/follow-along @@ -186,6 +195,22 @@ Current consolidated milestone snapshot: including `CoachDashboard.RuntimeInspectSurface`, `TrainingPanel.RuntimeInspectSurface`, and `Widget.OperatorStatusSurfaceLive` +- that same embedded-browser lane then gained a native control/input readiness + continuation on `2026-06-23`, adding + `FHyperTwistTrainingControlInputReadinessInspectSurface` plus native + training-panel and coach-dashboard ownership for the shipped classic keyboard + profile, classic-cube mouse/touch/shortcut readiness, higher-dimensional + dedicated-family readiness, project-level `EnhancedInput` and motion-control + groundwork, and explicit unfinished XR/preferences truth; the recovered + primary reverse-SSH `localhost:22022` lane rebuilt the maintained validation + root `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, + UnrealBuildTool `Total execution time: 1563.53 seconds`, then exported + `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` + with `16` `HyperTwist.Browser.*` tests succeeded and `0` failed, including + `CoachDashboard.ControlInputReadinessInspectSurface`, + `TrainingPanel.ControlInputReadinessInspectSurface`, + `CoachDashboard.RuntimeInspectSurface`, and + `TrainingPanel.RuntimeInspectSurface` - the public HyperTwist website/distribution lane is now also live through a first-party `website/` app on `2026-06-22`, with rebranded home/about/resources/pricing/download/legal pages, a protected browser @@ -245,7 +270,20 @@ Current consolidated milestone snapshot: panel, and the public preview-versus-launch callouts all consume one shared release-metadata truth for version/channel/build/checksum/docs/source posture while keeping raw download URLs hidden from anonymous viewers and - exposing them only to entitled session-backed viewers, + exposing them only to entitled session-backed viewers, while the protected + fallback path now also preserves signed-in viewer plan/access posture from + local session truth when live manifest authority is temporarily unavailable + without promoting that fallback into raw package-delivery authority, and the + protected download-center lane now also follows the resolved manifest viewer + entitlement for the actual download action instead of relying only on the + local auth snapshot, so fresher server-backed access truth can unblock an + operator even before local session posture has fully caught up, while the + shared browser auth layer now also refreshes and persists the local session + snapshot from that fresher manifest viewer truth and the protected dashboard + plus account surfaces now surface a compact live-authority sync notice + summarizing what changed, and the same auth layer now also clears stale + stored sessions when shared auth is configured and `/api/auth/me` returns + `401` instead of preserving a false authenticated protected-shell posture, and the runtime-readiness verifier now also probes the live anonymous `/api/releases/manifest` route plus the deployed root website shell marker so it can explicitly fail when `hypertwist.app` is still serving the older @@ -295,6 +333,13 @@ Current consolidated milestone snapshot: and protected download surfaces can show real packaged proof for `Magic120Cell` and `MagicCube5D` even before the launch-tier release URLs are populated, + and a later same-family hardening continuation then replaced the stale + hand-maintained website validation constant with a sanitized generated + summary rendered from + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json`, + with the owned `scripts/run-hypertwist-web-surface-validation.sh` gate now + checking that generated web summary for freshness before approving the + public/auth/download/browser surface, and a follow-on same-family marketing-shell continuation now also exposes a compact first-party public-site-status banner across the shared marketing shell, keeps a fuller status section on the homepage, and tightens the @@ -496,6 +541,15 @@ Current consolidated milestone snapshot: `BuildCookRun time: 118.53 s`, both dedicated-family packaged smoke maps green, and pulled aggregate proof in `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` +- that same maintained higher-dimensional package lane was refreshed again on + `2026-06-23` after the recovered authoritative editor rebuild, still against + isolated Windows worktree `C:\HyperTwist_worktrees\phase10validate` and + still using `-SkipBuild` on the already-present game receipt, with archive + output in + `C:\HyperTwist_worktrees\phase10validate_packaged_phase6c_higherdim_refresh_20260623`, + `BuildCookRun time: 70.74 s`, both dedicated-family packaged smoke maps + green again, and refreshed aggregate proof in + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` - the functional `Phase 6C` ownership gap is now closed; the remaining work in this lane is optional further micro-profiling and richer non-headless decorative/manual-authored family map dressing, not missing activation, diff --git a/scripts/render-hypertwist-web-package-validation-summary.mjs b/scripts/render-hypertwist-web-package-validation-summary.mjs new file mode 100644 index 0000000..f4d5d7c --- /dev/null +++ b/scripts/render-hypertwist-web-package-validation-summary.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const repoRoot = path.resolve(__dirname, '..') +const sourcePath = path.join( + repoRoot, + 'docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json', +) +const outputPath = path.join( + repoRoot, + 'website/src/shared/generated/windows-package-validation-summary.json', +) + +const FAMILY_LABELS = { + magic120cell: 'Magic120Cell dedicated-family training map', + magiccube5d: 'MagicCube5D dedicated-family training map', +} + +function parseJson(content, label) { + try { + return JSON.parse(content) + } catch (error) { + throw new Error(`${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`) + } +} + +function assertString(value, label) { + if (typeof value !== 'string' || value.trim().length === 0) { + throw new Error(`${label} must be a non-empty string.`) + } + return value +} + +function assertBoolean(value, label) { + if (typeof value !== 'boolean') { + throw new Error(`${label} must be a boolean.`) + } + return value +} + +function normalizeSmokeResult(value, label) { + if (value === 'passed' || value === 'failed') { + return value + } + throw new Error(`${label} must be "passed" or "failed".`) +} + +function buildFallbackLabel(mapUrl) { + const mapName = mapUrl.split('/').pop() || mapUrl + return `${mapName} packaged smoke map` +} + +function buildSummary(report) { + const generatedAtUtc = assertString(report.generatedAtUtc, 'generatedAtUtc') + const configuration = assertString(report.configuration, 'configuration') + const skipBuild = assertBoolean(report.skipBuild, 'skipBuild') + const result = normalizeSmokeResult(report.result, 'result') + const validatedEntries = Array.isArray(report.authoringManifest?.validatedEntries) + ? report.authoringManifest.validatedEntries + : [] + const mapKindByUrl = new Map( + validatedEntries + .filter((entry) => typeof entry?.mapAssetPath === 'string' && typeof entry?.mapKind === 'string') + .map((entry) => [entry.mapAssetPath, entry.mapKind]), + ) + const smokeReports = Array.isArray(report.smokeReports) ? report.smokeReports : [] + + const smokeMaps = smokeReports.map((smokeReport, index) => { + const mapUrl = assertString(smokeReport.mapUrl, `smokeReports[${index}].mapUrl`) + const mapKind = mapKindByUrl.get(mapUrl) + const label = + (mapKind && FAMILY_LABELS[mapKind]) || + buildFallbackLabel(mapUrl) + + return { + map_url: mapUrl, + label, + result: normalizeSmokeResult(smokeReport.result, `smokeReports[${index}].result`), + } + }) + + return { + lane: 'Windows Unreal packaged validation', + result, + generated_at: generatedAtUtc, + configuration, + skip_build: skipBuild, + smoke_map_count: smokeMaps.length, + smoke_maps: smokeMaps, + } +} + +async function main() { + const checkOnly = process.argv.includes('--check') + const sourceContent = await fs.readFile(sourcePath, 'utf8') + const summary = buildSummary(parseJson(sourceContent, sourcePath)) + const serialized = `${JSON.stringify(summary, null, 2)}\n` + + if (checkOnly) { + let existing = null + try { + existing = await fs.readFile(outputPath, 'utf8') + } catch (error) { + if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') { + console.error(`Generated summary missing: ${outputPath}`) + process.exit(1) + } + throw error + } + + if (existing !== serialized) { + console.error('Generated web package-validation summary is stale.') + console.error(`Refresh with: node ${path.relative(repoRoot, __filename)}`) + process.exit(1) + } + + console.log(`Validated packaged-validation summary freshness: ${path.relative(repoRoot, outputPath)}`) + return + } + + await fs.mkdir(path.dirname(outputPath), { recursive: true }) + await fs.writeFile(outputPath, serialized, 'utf8') + console.log(`Wrote ${path.relative(repoRoot, outputPath)}`) +} + +await main() diff --git a/scripts/run-hypertwist-gitnexus-analyze.sh b/scripts/run-hypertwist-gitnexus-analyze.sh index 0fa4476..6b148fc 100644 --- a/scripts/run-hypertwist-gitnexus-analyze.sh +++ b/scripts/run-hypertwist-gitnexus-analyze.sh @@ -5,6 +5,7 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" local_gitnexus_cli="$repo_root/mirrors/GitNexus/gitnexus/dist/cli/index.js" analysis_root="$repo_root/.gitnexus-source-only-root" max_size_kb="${HYPERTWIST_GITNEXUS_MAX_FILE_SIZE_KB:-256}" +analysis_timeout_seconds="${HYPERTWIST_GITNEXUS_ANALYZE_TIMEOUT_SECONDS:-300}" copy_targets=( "UnrealHyperTwist/Source" @@ -40,12 +41,98 @@ git -C "$analysis_root" commit -q -m "source-only snapshot" cd "$analysis_root" +run_analysis_command() { + local -n command_ref="$1" + + if command -v timeout >/dev/null 2>&1; then + timeout "${analysis_timeout_seconds}s" "${command_ref[@]}" + else + "${command_ref[@]}" + fi +} + +read_indexed_commit() { + node -e ' + const fs = require("fs"); + const path = ".gitnexus/meta.json"; + if (!fs.existsSync(path)) { + process.exit(1); + } + const meta = JSON.parse(fs.readFileSync(path, "utf8")); + if (typeof meta.lastCommit !== "string" || meta.lastCommit.length === 0) { + process.exit(1); + } + process.stdout.write(meta.lastCommit); + ' 2>/dev/null || true +} + +run_analysis_attempt() { + local cli_kind="$1" + shift + local command=("$@") + local analysis_exit_code=0 + + set +e + if [[ "$cli_kind" == "local" ]]; then + run_analysis_command command 2>/dev/null + else + run_analysis_command command + fi + analysis_exit_code=$? + set -e + + if [[ "$analysis_exit_code" -eq 0 ]]; then + return 0 + fi + + local current_commit + current_commit="$(git rev-parse HEAD)" + local indexed_commit + indexed_commit="$(read_indexed_commit)" + + if [[ "$analysis_exit_code" -eq 124 || "$analysis_exit_code" -eq 137 ]]; then + if [[ -n "$indexed_commit" && "$indexed_commit" == "$current_commit" ]]; then + echo "GitNexus analyze hit the configured timeout after indexing the current bounded mirror; treating the run as successful." >&2 + echo "Indexed commit: ${indexed_commit:0:7} (${cli_kind} CLI, timeout ${analysis_timeout_seconds}s)." >&2 + return 0 + fi + fi + + if [[ -n "$indexed_commit" ]]; then + echo "GitNexus analyze exited with code $analysis_exit_code after indexing commit ${indexed_commit:0:7}, which did not match current mirror commit ${current_commit:0:7}." >&2 + else + echo "GitNexus analyze exited with code $analysis_exit_code before a readable bounded-mirror index result was available." >&2 + fi + + return "$analysis_exit_code" +} + +local_cli_command=( + node + "$local_gitnexus_cli" + analyze + . + --skip-agents-md +) + +npx_cli_command=( + npx + -y + gitnexus@latest + analyze + . + --skip-agents-md +) + +local_cli_command+=("$@") +npx_cli_command+=("$@") + if [[ -f "$local_gitnexus_cli" ]]; then - if node "$local_gitnexus_cli" analyze . --skip-agents-md "$@" 2>/dev/null; then + if run_analysis_attempt "local" "${local_cli_command[@]}"; then exit 0 fi - echo "Local retained GitNexus CLI was not runnable on this host. Falling back to npx gitnexus@latest." >&2 + echo "Local retained GitNexus CLI did not complete cleanly on this host. Falling back to npx gitnexus@latest." >&2 fi -exec npx -y gitnexus@latest analyze . --skip-agents-md "$@" +run_analysis_attempt "npx" "${npx_cli_command[@]}" diff --git a/scripts/run-hypertwist-web-surface-validation.sh b/scripts/run-hypertwist-web-surface-validation.sh new file mode 100644 index 0000000..8ad70f3 --- /dev/null +++ b/scripts/run-hypertwist-web-surface-validation.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +strict_auth_server_audit=0 +skip_audits=0 + +usage() { + cat <<'EOF' +Usage: + scripts/run-hypertwist-web-surface-validation.sh [--strict-auth-server-audit] [--skip-audits] + +Runs the current first-party HyperTwist web-surface validation packet: + - website type-check, focused tests, and production build + - website/server type-check and full test suite + - Content/Browser shell verification and production build + - production dependency audits for website, website/server, and Content/Browser + +Current auth-server nuance: + the default mode accepts the known upstream `supertokens-node -> nodemailer` + production advisory as a documented residual warning. Pass + `--strict-auth-server-audit` to fail on that residual too. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --strict-auth-server-audit) + strict_auth_server_audit=1 + ;; + --skip-audits) + skip_audits=1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac + shift +done + +run_step() { + local description="$1" + shift + printf '\n[%s] %s\n' "$(date -u '+%H:%M:%S')" "$description" + ( + cd "$repo_root" + "$@" + ) +} + +run_clean_prod_audit() { + local workdir="$1" + local label="$2" + + printf '\n[%s] %s production audit\n' "$(date -u '+%H:%M:%S')" "$label" + ( + cd "$workdir" + npm audit --omit=dev --audit-level=high + ) +} + +run_auth_server_audit() { + local workdir="$repo_root/website/server" + local audit_json + + printf '\n[%s] website/server production audit\n' "$(date -u '+%H:%M:%S')" + audit_json="$( + cd "$workdir" + npm audit --omit=dev --json || true + )" + + if [[ -z "$audit_json" ]]; then + echo "website/server audit returned no output." >&2 + return 1 + fi + + AUDIT_JSON="$audit_json" STRICT_AUTH_SERVER_AUDIT="$strict_auth_server_audit" node <<'EOF' +const report = JSON.parse(process.env.AUDIT_JSON || '{}') +const strict = process.env.STRICT_AUTH_SERVER_AUDIT === '1' +const vulnerabilities = report.vulnerabilities || {} +const metadata = report.metadata?.vulnerabilities || {} +const total = Number(metadata.total || 0) + +if (total === 0) { + console.log('website/server production audit is clean.') + process.exit(0) +} + +const keys = Object.keys(vulnerabilities).sort() +const expectedKeys = ['nodemailer', 'supertokens-node'] +const nodemailer = vulnerabilities.nodemailer +const supertokens = vulnerabilities['supertokens-node'] +const matchesKnownResidual = + metadata.info === 0 && + metadata.low === 0 && + metadata.moderate === 0 && + metadata.high === 2 && + metadata.critical === 0 && + keys.length === expectedKeys.length && + expectedKeys.every((key, index) => keys[index] === key) && + nodemailer?.name === 'nodemailer' && + nodemailer?.severity === 'high' && + Array.isArray(nodemailer?.effects) && + nodemailer.effects.length === 1 && + nodemailer.effects[0] === 'supertokens-node' && + Array.isArray(nodemailer?.nodes) && + nodemailer.nodes.length === 1 && + nodemailer.nodes[0] === 'node_modules/nodemailer' && + supertokens?.name === 'supertokens-node' && + supertokens?.severity === 'high' && + Array.isArray(supertokens?.via) && + supertokens.via.length === 1 && + supertokens.via[0] === 'nodemailer' + +if (matchesKnownResidual && !strict) { + console.log( + 'website/server production audit retains the documented upstream residual advisory: supertokens-node -> nodemailer.', + ) + process.exit(0) +} + +console.error('website/server production audit found unexpected or strict-mode-blocking vulnerabilities.') +console.error(JSON.stringify(report, null, 2)) +process.exit(1) +EOF +} + +run_step "website type-check" npm --prefix website run type-check +run_step \ + "higher-dimensional packaged-validation summary freshness" \ + node scripts/render-hypertwist-web-package-validation-summary.mjs --check +run_step \ + "website focused route/auth/release validation" \ + npm --prefix website test -- --run \ + src/__tests__/package-validation.test.ts \ + src/__tests__/platform-auth.bootstrap.test.tsx \ + src/__tests__/download-center-page.test.tsx \ + src/__tests__/protected-app-pages.test.tsx \ + src/__tests__/DashboardOverviewPage.test.tsx \ + src/__tests__/app-route-tree.test.tsx \ + src/__tests__/route-shells.test.tsx \ + src/__tests__/release-manifest.test.ts \ + src/__tests__/public-marketing-pages.test.tsx \ + src/__tests__/public-auth-pages.test.tsx +run_step "website production build" npm --prefix website run build +run_step "website/server type-check" npm --prefix website/server run type-check +run_step "website/server test suite" npm --prefix website/server test -- --run +run_step "Content/Browser shell verification" npm --prefix Content/Browser run verify:shell +run_step "Content/Browser production build" npm --prefix Content/Browser run build + +if [[ "$skip_audits" -eq 0 ]]; then + run_clean_prod_audit "$repo_root/website" "website" + run_auth_server_audit + run_clean_prod_audit "$repo_root/Content/Browser" "Content/Browser" +else + printf '\n[%s] Skipping production audits by request.\n' "$(date -u '+%H:%M:%S')" +fi + +printf '\n[%s] HyperTwist web-surface validation completed successfully.\n' "$(date -u '+%H:%M:%S')" +if [[ "$skip_audits" -eq 0 && "$strict_auth_server_audit" -eq 0 ]]; then + echo "Auth-server note: known upstream supertokens-node -> nodemailer residual is accepted in default mode until upstream-compatible remediation exists." +fi diff --git a/website/README.md b/website/README.md index cb97ac7..ee41f15 100644 --- a/website/README.md +++ b/website/README.md @@ -79,12 +79,50 @@ Environment templates: ## Validation ```bash +scripts/run-hypertwist-web-surface-validation.sh npm run type-check npm run test npm run build npm run check:runtime-readiness -- --frontend-env .env --server-env server/.env --health-url https://hypertwist.app ``` +Related validation and audit commands for the adjacent owned surfaces: + +```bash +npm --prefix server run type-check +npm --prefix server test -- --run +npm --prefix ../Content/Browser run verify:shell +npm --prefix ../Content/Browser run build +npm audit --omit=dev --audit-level=high +npm --prefix server audit --omit=dev --audit-level=high +npm --prefix ../Content/Browser audit --omit=dev --audit-level=high +``` + +The preferred current repo-owned umbrella gate is: + +```bash +scripts/run-hypertwist-web-surface-validation.sh +``` + +That command runs the current website type-check, focused auth or release or +route tests, website build, auth-server type-check and tests, browser-shell +verify/build, and production audits together. It accepts the current exact +upstream `supertokens-node -> nodemailer` auth-server residual in default mode +and supports `--strict-auth-server-audit` when that residual should block. +It now also checks that the website-facing Windows packaged-validation summary +is fresh against the checked-in authoritative higher-dimensional package report. + +Current dependency-health truth from the `2026-06-23` hardening pass: + +- `website/` production audit is clean +- `Content/Browser/` production audit is clean and now has a checked-in + `package-lock.json` +- `website/server/` was upgraded to `supertokens-node@24.0.2` +- `website/server/` still carries one upstream production advisory through the + supported `supertokens-node -> nodemailer@8.0.11` chain +- no unsupported forced major `nodemailer` override was landed just to hide + that remaining upstream advisory + ## Release-manifest authority The website now treats `GET /api/releases/manifest` from `website/server` as @@ -190,6 +228,12 @@ Use the runtime-readiness command before public launch or deployment approval: - the shared marketing shell now also carries a compact first-party public-site-status banner across public pages, while the homepage keeps a fuller public-site-status section for the same preview-versus-launch truth +- the website-facing Windows packaged proof now comes from the sanitized + generated file + `website/src/shared/generated/windows-package-validation-summary.json`, + rendered from + `docs/generated/higher_dimensional_training_maps/phase6c_dedicated_family_package_validation_report.json` + by `scripts/render-hypertwist-web-package-validation-summary.mjs` - the auth server can now also serve the built `website/dist` bundle directly for same-origin `hypertwist.app` deployment when that build output is present - the repo now also includes first-party same-origin `nginx` and `systemd` handoff templates under `website/deploy/` diff --git a/website/server/package-lock.json b/website/server/package-lock.json index 8e08112..9a0bbd6 100644 --- a/website/server/package-lock.json +++ b/website/server/package-lock.json @@ -12,7 +12,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", - "supertokens-node": "^21.1.0" + "supertokens-node": "^24.0.2" }, "devDependencies": { "@types/cookie-parser": "^1.4.8", @@ -1391,9 +1391,9 @@ } }, "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" @@ -2217,9 +2217,9 @@ } }, "node_modules/nodemailer": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", - "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.11.tgz", + "integrity": "sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -2689,23 +2689,23 @@ "license": "Apache-2.0" }, "node_modules/supertokens-node": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-21.1.2.tgz", - "integrity": "sha512-YX5qxT/cP/qsuvsH9j8oSsOdkvJhOk4eM+pGpJ0YToj4LZMa8FIUFnh4SDuAO/AC4SuTeIyhysMG4DdDUJdz8Q==", + "version": "24.0.2", + "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-24.0.2.tgz", + "integrity": "sha512-L4n0zchL3K36aMgUWT5WVuNCw7KWWGtZq//Ti+jWcyRphb/sVaKM76fI8I2/JBqTIGXre9dAQx3myETRSA+etQ==", "license": "Apache-2.0", "dependencies": { "buffer": "^6.0.3", "content-type": "^1.0.5", "cookie": "^0.7.2", - "cross-fetch": "^3.1.6", + "cross-fetch": "^4.1.0", "debug": "^4.3.3", "jose": "^4.13.1", "libphonenumber-js": "^1.9.44", - "nodemailer": "^6.7.2", + "nodemailer": "^8.0.2", "pako": "^2.1.0", "pkce-challenge": "^3.0.0", "process": "^0.11.10", - "set-cookie-parser": "^2.6.0", + "set-cookie-parser": "^2.7.1", "supertokens-js-override": "^0.0.4", "tldts": "^6.1.48", "twilio": "^4.19.3" diff --git a/website/server/package.json b/website/server/package.json index 798331d..97a46f3 100644 --- a/website/server/package.json +++ b/website/server/package.json @@ -15,7 +15,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", - "supertokens-node": "^21.1.0" + "supertokens-node": "^24.0.2" }, "devDependencies": { "@types/cookie-parser": "^1.4.8", diff --git a/website/src/__tests__/DashboardOverviewPage.test.tsx b/website/src/__tests__/DashboardOverviewPage.test.tsx index 1f2da2b..38219f4 100644 --- a/website/src/__tests__/DashboardOverviewPage.test.tsx +++ b/website/src/__tests__/DashboardOverviewPage.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { render, screen, waitFor } from '@testing-library/react' +import { cleanup, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' @@ -8,6 +8,7 @@ const mockCreateDesktopLinkToken = vi.fn() const mockGetAuthHealth = vi.fn() const mockBuildAuthApiBaseUrls = vi.fn(() => ['https://hypertwist.app']) const mockGetReleaseManifest = vi.fn() +const mockInvalidateSession = vi.fn() vi.mock('../auth/platform-auth', () => ({ usePlatformAuth: () => mockUsePlatformAuth(), @@ -40,11 +41,13 @@ function renderPage() { describe('DashboardOverviewPage', () => { beforeEach(() => { + cleanup() mockUsePlatformAuth.mockReset() mockCreateDesktopLinkToken.mockReset() mockGetAuthHealth.mockReset() mockBuildAuthApiBaseUrls.mockReset() mockGetReleaseManifest.mockReset() + mockInvalidateSession.mockReset() mockBuildAuthApiBaseUrls.mockReturnValue(['https://hypertwist.app']) mockGetReleaseManifest.mockResolvedValue({ ok: true, @@ -120,6 +123,9 @@ describe('DashboardOverviewPage', () => { }, }, superTokensConfigured: true, + invalidateSession: (...args: unknown[]) => mockInvalidateSession(...args), + reconcileReleaseAuthority: vi.fn(), + releaseAuthoritySyncItems: [], }) }) @@ -179,8 +185,11 @@ describe('DashboardOverviewPage', () => { expect(screen.getByText(/Public auth runtime still uses local or mixed deployment posture/i)).toBeTruthy() expect(screen.getByText('Packaged validation passed')).toBeTruthy() expect(screen.getByText(/Magic120Cell dedicated-family training map: passed/i)).toBeTruthy() + expect(screen.getByText('Desktop rollout follow-through')).toBeTruthy() + expect(screen.getByText('Pair desktop access to the browser account')).toBeTruthy() - await userEvent.click(screen.getByRole('button', { name: /generate desktop-link token/i })) + const generateDesktopLinkButtons = screen.getAllByRole('button', { name: /generate desktop-link token/i }) + await userEvent.click(generateDesktopLinkButtons[generateDesktopLinkButtons.length - 1]!) await waitFor(() => { expect(screen.getByText((content) => content === 'desktop-token-123')).toBeTruthy() @@ -188,4 +197,183 @@ describe('DashboardOverviewPage', () => { expect(screen.getByText(/https:\/\/hypertwist\.app\/api\/auth\/desktop-link\/verify\?token=desktop-token-123/i)).toBeTruthy() }) + + it('invalidates the protected browser session when desktop-link issuance returns unauthorized', async () => { + mockGetAuthHealth.mockResolvedValue({ + ok: true, + service: 'hypertwist-auth-server', + supertokens: { + configured: true, + reachable: true, + ready: true, + apiVersion: '5.4', + oauth: { + github: false, + google: false, + }, + }, + fallback: { + enabled: true, + active: false, + reason: null, + }, + billing: { + statePath: '/tmp/hypertwist-billing.json', + processedEventCount: 1, + pricePlanMapConfigured: true, + productPlanMapConfigured: true, + webhookSecretConfigured: true, + }, + runtime: { + mode: 'public', + public_origin_ready: true, + cookie_secure: true, + api_domain: 'https://hypertwist.app', + website_domain: 'https://hypertwist.app', + warnings: [], + errors: [], + }, + }) + + mockCreateDesktopLinkToken.mockRejectedValue(Object.assign(new Error('unauthorized'), { status: 401 })) + + renderPage() + + await waitFor(() => { + expect(screen.queryByText(/Checking auth server health/i)).toBeNull() + }) + + const generateDesktopLinkButtons = screen.getAllByRole('button', { name: /generate desktop-link token/i }) + await userEvent.click(generateDesktopLinkButtons[generateDesktopLinkButtons.length - 1]!) + + await waitFor(() => { + expect(mockInvalidateSession).toHaveBeenCalledWith('desktop_link_unauthorized') + }) + }) + + it('surfaces live authority sync details after release access has been refreshed from live manifest truth', async () => { + mockGetAuthHealth.mockResolvedValue({ + ok: true, + service: 'hypertwist-auth-server', + supertokens: { + configured: true, + reachable: true, + ready: true, + apiVersion: '5.4', + oauth: { + github: false, + google: false, + }, + }, + fallback: { + enabled: true, + active: false, + reason: null, + }, + billing: { + statePath: '/tmp/hypertwist-billing.json', + processedEventCount: 1, + pricePlanMapConfigured: true, + productPlanMapConfigured: true, + webhookSecretConfigured: true, + }, + runtime: { + mode: 'public', + public_origin_ready: true, + cookie_secure: true, + api_domain: 'https://hypertwist.app', + website_domain: 'https://hypertwist.app', + warnings: [], + errors: [], + }, + }) + + mockUsePlatformAuth.mockReturnValue({ + user: { + id: 'operator-1', + name: 'Operator', + email: 'operator@hypertwist.app', + authMethod: 'email', + plan: 'free', + role: 'viewer', + canDownload: false, + billing: { + source: 'session', + accessStatus: 'session-default', + canDownload: false, + lastEventType: 'transaction.completed', + }, + }, + superTokensConfigured: true, + reconcileReleaseAuthority: vi.fn(), + invalidateSession: (...args: unknown[]) => mockInvalidateSession(...args), + releaseAuthoritySyncItems: [ + 'Plan changed from local free to live operator.', + 'Desktop download access changed from local not entitled to live enabled.', + ], + }) + + mockGetReleaseManifest.mockResolvedValue({ + ok: true, + manifest: { + generated_at: '2026-06-23T12:00:00.000Z', + support_email: 'hello@hypertwist.app', + public_docs_url: 'https://docs.hypertwist.app', + release_notes_url: 'https://notes.hypertwist.app', + corresponding_source_url: 'https://hypertwist.app/open-source/source.zip', + open_source_repo_url: 'https://github.com/hypertwist/hypertwist', + viewer: { + authenticated: true, + canDownload: true, + plan: 'operator', + role: 'operator', + accessStatus: 'active', + }, + platforms: [ + { + platform_key: 'windows', + platform: 'Windows', + subtitle: 'Primary shipping lane', + details: 'Current packaged validation is strongest on the Windows Unreal lane.', + configured: true, + channel: 'candidate', + version: '1.0.1', + build_id: 'win64-1001', + published_at: '2026-06-23T00:00:00.000Z', + file_name: 'HyperTwist-Windows.zip', + file_size_bytes: 1048576, + checksum_sha256: 'abc124', + download_url: 'https://downloads.hypertwist.app/windows.exe', + download_available: true, + validation_summary: { + lane: 'Windows Unreal packaged validation', + result: 'passed', + generated_at: '2026-06-23T01:43:08.7625247Z', + configuration: 'Development', + skip_build: true, + smoke_map_count: 2, + smoke_maps: [ + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining', + label: 'Magic120Cell dedicated-family training map', + result: 'passed', + }, + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining', + label: 'MagicCube5D dedicated-family training map', + result: 'passed', + }, + ], + }, + }, + ], + }, + }) + + renderPage() + + expect(await screen.findByText('Live authority sync')).toBeTruthy() + expect(screen.getByText(/plan changed from local free to live operator/i)).toBeTruthy() + expect(screen.getByText(/desktop download access changed from local not entitled to live enabled/i)).toBeTruthy() + }) }) diff --git a/website/src/__tests__/download-center-page.test.tsx b/website/src/__tests__/download-center-page.test.tsx index 78c2334..9c101f3 100644 --- a/website/src/__tests__/download-center-page.test.tsx +++ b/website/src/__tests__/download-center-page.test.tsx @@ -1,20 +1,17 @@ -import { render, screen } from '@testing-library/react' +import { cleanup, render, screen, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { ROUTER_FUTURE_FLAGS } from '../router/router-future' const mockGetReleaseManifest = vi.fn() const mockBuildAuthApiBaseUrls = vi.fn(() => ['https://hypertwist.app']) const mockCreateDesktopLinkToken = vi.fn() const mockGetAuthHealth = vi.fn() +const mockUsePlatformAuth = vi.fn() vi.mock('../auth/platform-auth', () => ({ - usePlatformAuth: () => ({ - user: { - canDownload: true, - }, - }), + usePlatformAuth: () => mockUsePlatformAuth(), })) vi.mock('../auth/auth-api', () => ({ @@ -72,21 +69,21 @@ vi.mock('../site-config', () => ({ platformKey: 'windows', platform: 'Windows', subtitle: 'Primary shipping lane', - href: 'https://downloads.hypertwist.app/windows.exe', + configured: true, details: 'Current packaged validation is strongest on the Windows Unreal lane.', }, { platformKey: 'macos', platform: 'macOS', subtitle: 'Planned distribution surface', - href: 'https://downloads.hypertwist.app/macos.dmg', + configured: true, details: 'List a signed desktop build here when the package lane is opened.', }, { platformKey: 'linux', platform: 'Linux', subtitle: 'Operator-targeted later lane', - href: '', + configured: false, details: 'Use for future package publication after the bounded release lane is widened.', }, ], @@ -108,6 +105,21 @@ vi.mock('../site-config', () => ({ import { DownloadCenterPage } from '../pages/app-pages' describe('DownloadCenterPage', () => { + beforeEach(() => { + cleanup() + mockUsePlatformAuth.mockReset() + mockGetReleaseManifest.mockReset() + mockBuildAuthApiBaseUrls.mockReset() + mockCreateDesktopLinkToken.mockReset() + mockGetAuthHealth.mockReset() + mockBuildAuthApiBaseUrls.mockReturnValue(['https://hypertwist.app']) + mockUsePlatformAuth.mockReturnValue({ + user: { + canDownload: true, + }, + }) + }) + it('surfaces the requested protected release target when the public site passes a platform hint through auth', async () => { const queryClient = new QueryClient({ defaultOptions: { @@ -186,10 +198,123 @@ describe('DownloadCenterPage', () => { expect(screen.getAllByText('Requested target').length).toBeGreaterThan(0) expect((await screen.findByRole('link', { name: /download windows/i })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe') expect(screen.getByText('Release guide')).toBeTruthy() + expect(screen.getByText('First launch follow-through')).toBeTruthy() + expect(screen.getByText('Verify the current training lanes')).toBeTruthy() expect(await screen.findByText('Version: 1.0.0')).toBeTruthy() expect(screen.getByText('Build: win64-1000')).toBeTruthy() expect(screen.getByText('Packaged validation passed')).toBeTruthy() expect(screen.getByText(/MagicCube5D dedicated-family training map: passed/i)).toBeTruthy() expect(document.title).toBe('HyperTwist downloads | HyperTwist') }) + + it('trusts the live manifest viewer posture when local auth state is stale about download entitlement', async () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + mockUsePlatformAuth.mockReturnValue({ + user: { + canDownload: false, + }, + }) + + mockGetReleaseManifest.mockResolvedValue({ + ok: true, + manifest: { + generated_at: '2026-06-23T12:00:00.000Z', + support_email: 'hello@hypertwist.app', + public_docs_url: 'https://docs.hypertwist.app', + release_notes_url: 'https://notes.hypertwist.app', + corresponding_source_url: 'https://hypertwist.app/open-source/source.zip', + open_source_repo_url: 'https://github.com/hypertwist/hypertwist', + viewer: { + authenticated: true, + canDownload: true, + plan: 'operator', + role: 'operator', + accessStatus: 'active', + }, + platforms: [ + { + platform_key: 'windows', + platform: 'Windows', + subtitle: 'Primary shipping lane', + details: 'Current packaged validation is strongest on the Windows Unreal lane.', + configured: true, + channel: 'candidate', + version: '1.0.1', + build_id: 'win64-1001', + published_at: '2026-06-23T00:00:00.000Z', + file_name: 'HyperTwist-Windows.zip', + file_size_bytes: 1048576, + checksum_sha256: 'abc124', + download_url: 'https://downloads.hypertwist.app/windows.exe', + download_available: true, + validation_summary: { + lane: 'Windows Unreal packaged validation', + result: 'passed', + generated_at: '2026-06-23T01:43:08.7625247Z', + configuration: 'Development', + skip_build: true, + smoke_map_count: 2, + smoke_maps: [ + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining', + label: 'Magic120Cell dedicated-family training map', + result: 'passed', + }, + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining', + label: 'MagicCube5D dedicated-family training map', + result: 'passed', + }, + ], + }, + }, + ], + }, + }) + + render( + + + + + , + ) + + expect((await screen.findByRole('link', { name: /download windows/i })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe') + await waitFor(() => { + expect(screen.queryByText(/does not yet have desktop download entitlement/i)).toBeNull() + }) + }) + + it('keeps entitled fallback posture honest when live release authority is temporarily unavailable', async () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + mockGetReleaseManifest.mockRejectedValue(new Error('release manifest unavailable')) + + render( + + + + + , + ) + + expect(await screen.findByText(/fallback release posture/i)).toBeTruthy() + expect(screen.getByText(/your signed-in account still resolves to desktop access/i)).toBeTruthy() + expect(screen.getAllByText('Live release authority temporarily unavailable').length).toBeGreaterThan(0) + expect(screen.queryByText(/account not entitled yet/i)).toBeNull() + }) }) diff --git a/website/src/__tests__/package-validation.test.ts b/website/src/__tests__/package-validation.test.ts new file mode 100644 index 0000000..3f0a7c5 --- /dev/null +++ b/website/src/__tests__/package-validation.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' + +import { getReleasePlatformValidationSummary } from '../shared/package-validation' + +describe('getReleasePlatformValidationSummary', () => { + it('reads the current Windows packaged-validation truth from the generated higher-dimensional report summary', () => { + expect(getReleasePlatformValidationSummary('windows')).toMatchObject({ + lane: 'Windows Unreal packaged validation', + result: 'passed', + generated_at: '2026-06-23T10:27:31.6439999Z', + smoke_map_count: 2, + smoke_maps: [ + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining', + label: 'Magic120Cell dedicated-family training map', + result: 'passed', + }, + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining', + label: 'MagicCube5D dedicated-family training map', + result: 'passed', + }, + ], + }) + }) + + it('keeps non-Windows release lanes free of fake packaged-validation summaries', () => { + expect(getReleasePlatformValidationSummary('macos')).toBeNull() + expect(getReleasePlatformValidationSummary('linux')).toBeNull() + }) +}) diff --git a/website/src/__tests__/platform-auth.bootstrap.test.tsx b/website/src/__tests__/platform-auth.bootstrap.test.tsx index 3fa4fc8..3df0c42 100644 --- a/website/src/__tests__/platform-auth.bootstrap.test.tsx +++ b/website/src/__tests__/platform-auth.bootstrap.test.tsx @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { cleanup, render, screen, waitFor } from '@testing-library/react' -import type { ReactNode } from 'react' +import { useEffect, type ReactNode } from 'react' const mockGetCurrentUser = vi.fn() const mockLogoutCurrentUser = vi.fn() @@ -35,7 +35,7 @@ import { PlatformAuthProvider, usePlatformAuth } from '../auth/platform-auth' const AUTH_STORAGE_KEY = 'hypertwist.platform.user.v1' function AuthProbe() { - const { user, isLoading } = usePlatformAuth() + const { user, isLoading, releaseAuthoritySyncItems } = usePlatformAuth() return (
@@ -46,10 +46,33 @@ function AuthProbe() {
{user?.role ?? 'none'}
{String(user?.canDownload ?? false)}
{user?.billing?.source ?? 'none'}
+
{releaseAuthoritySyncItems.join(' | ') || 'none'}
) } +function ReleaseAuthorityReconcileProbe({ + viewer, +}: { + viewer: { + authenticated: boolean + canDownload: boolean + plan: string | null + role: string | null + accessStatus: string | null + } +}) { + const { isLoading, reconcileReleaseAuthority } = usePlatformAuth() + + useEffect(() => { + if (!isLoading) { + reconcileReleaseAuthority(viewer) + } + }, [isLoading, reconcileReleaseAuthority, viewer]) + + return +} + function renderWithProvider(children: ReactNode) { return render({children}) } @@ -160,4 +183,84 @@ describe('PlatformAuthProvider bootstrap', () => { expect(screen.getByTestId('billing-source').textContent).toBe('local-fallback') expect(mockEnsureSuperTokensInit).not.toHaveBeenCalled() }) + + it('clears a stale stored session when shared auth is configured and bootstrap returns unauthorized', async () => { + seedStoredUser({ + id: 'stale-1', + email: 'operator@hypertwist.app', + name: 'Operator', + authMethod: 'supertokens', + plan: 'operator', + role: 'operator', + canDownload: true, + billing: { + source: 'session', + accessStatus: 'active', + canDownload: true, + }, + }) + + mockGetCurrentUser.mockResolvedValue(createJsonResponse(401, { error: 'unauthorized' })) + + renderWithProvider() + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('false') + }) + + expect(screen.getByTestId('user-id').textContent).toBe('none') + expect(screen.getByTestId('plan').textContent).toBe('none') + expect(window.localStorage.getItem(AUTH_STORAGE_KEY)).toBeNull() + expect(mockEnsureSuperTokensInit).toHaveBeenCalledTimes(1) + }) + + it('reconciles stored browser auth state from live release-authority viewer truth and persists the update', async () => { + mockIsSuperTokensConfigured.mockReturnValue(false) + seedStoredUser({ + id: 'local-1', + email: 'operator@hypertwist.app', + name: 'Operator', + authMethod: 'email', + plan: 'free', + role: 'viewer', + canDownload: false, + billing: { + source: 'local-fallback', + accessStatus: 'session-default', + canDownload: false, + }, + }) + + mockGetCurrentUser.mockRejectedValue(new Error('offline')) + + renderWithProvider( + , + ) + + await waitFor(() => { + expect(screen.getByTestId('plan').textContent).toBe('operator') + }) + + expect(screen.getByTestId('role').textContent).toBe('operator') + expect(screen.getByTestId('can-download').textContent).toBe('true') + expect(screen.getByTestId('sync-items').textContent).toContain('Plan changed from local free to live operator.') + expect(screen.getByTestId('sync-items').textContent).toContain('Desktop download access changed from local not entitled to live enabled.') + expect(JSON.parse(window.localStorage.getItem(AUTH_STORAGE_KEY) || '{}')).toMatchObject({ + plan: 'operator', + role: 'operator', + canDownload: true, + billing: { + accessStatus: 'active', + canDownload: true, + }, + }) + }) }) diff --git a/website/src/__tests__/protected-app-pages.test.tsx b/website/src/__tests__/protected-app-pages.test.tsx index c7c3298..15fb4f0 100644 --- a/website/src/__tests__/protected-app-pages.test.tsx +++ b/website/src/__tests__/protected-app-pages.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { cleanup, render, screen } from '@testing-library/react' +import { cleanup, render, screen, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' import { ROUTER_FUTURE_FLAGS } from '../router/router-future' @@ -7,6 +7,7 @@ import { ROUTER_FUTURE_FLAGS } from '../router/router-future' const mockUsePlatformAuth = vi.fn() const mockGetAuthHealth = vi.fn() const mockGetReleaseManifest = vi.fn() +const mockInvalidateSession = vi.fn() vi.mock('../auth/platform-auth', () => ({ usePlatformAuth: () => mockUsePlatformAuth(), @@ -52,6 +53,7 @@ describe('protected app pages', () => { mockUsePlatformAuth.mockReset() mockGetAuthHealth.mockReset() mockGetReleaseManifest.mockReset() + mockInvalidateSession.mockReset() mockUsePlatformAuth.mockReturnValue({ user: { @@ -70,6 +72,9 @@ describe('protected app pages', () => { }, }, superTokensConfigured: true, + invalidateSession: (...args: unknown[]) => mockInvalidateSession(...args), + reconcileReleaseAuthority: vi.fn(), + releaseAuthoritySyncItems: [], }) mockGetAuthHealth.mockResolvedValue({ @@ -188,8 +193,11 @@ describe('protected app pages', () => { expect(await screen.findByText('Current browser posture')).toBeTruthy() expect(await screen.findByText(byExactTextContent('Auth runtime mode: public', 'LI'))).toBeTruthy() expect(await screen.findByText(byExactTextContent('Configured release targets: 1/2', 'LI'))).toBeTruthy() + expect(screen.getByText('Operator escalation map')).toBeTruthy() + expect(screen.getByText('Account and entitlement issues')).toBeTruthy() expect(screen.getByRole('link', { name: 'Open dashboard' }).getAttribute('href')).toBe('/app') expect(screen.getByRole('link', { name: 'Protected notices' }).getAttribute('href')).toBe('/app/notices') + expect(screen.getByRole('link', { name: 'Open support' }).getAttribute('href')).toBe('/support?topic=operator-access') }) it('surfaces account release access and protected follow-through links', async () => { @@ -201,6 +209,8 @@ describe('protected app pages', () => { expect(await screen.findByText(byExactTextContent('Windows: Primary shipping lane (Version 1.0.0, Channel candidate)', 'LI'))).toBeTruthy() expect(screen.getByRole('link', { name: 'Open downloads' }).getAttribute('href')).toBe('/app/downloads') expect(screen.getByRole('link', { name: 'Get help' }).getAttribute('href')).toBe('/support?topic=operator-access') + expect(screen.getByText('Desktop access follow-through')).toBeTruthy() + expect(screen.getByText('Verify the current training lanes')).toBeTruthy() }) it('surfaces protected notices references and release-target notice posture', async () => { @@ -211,6 +221,121 @@ describe('protected app pages', () => { expect(screen.getByText(byExactTextContent('Public repository/notices URL: https://github.com/hypertwist/hypertwist', 'LI'))).toBeTruthy() expect(screen.getByText(byExactTextContent('Corresponding-source URL: https://hypertwist.app/open-source/source.zip', 'LI'))).toBeTruthy() expect(screen.getByText(byExactTextContent('Windows: configured, validation passed', 'LI'))).toBeTruthy() + expect(screen.getByText('Escalation and release follow-through')).toBeTruthy() + expect(screen.getByText('Download and install issues')).toBeTruthy() expect(screen.getByRole('link', { name: 'Public notices' }).getAttribute('href')).toBe('/open-source-notices') + expect(screen.getByRole('link', { name: 'Open downloads' }).getAttribute('href')).toBe('/app/downloads') + }) + + it('keeps local signed-in viewer posture when protected pages fall back from live release-manifest authority', async () => { + mockGetReleaseManifest.mockRejectedValue(new Error('release manifest unavailable')) + + renderPage(, '/app/account') + + expect(await screen.findByText('Session profile')).toBeTruthy() + expect(await screen.findByText(byExactTextContent('Manifest viewer authenticated: yes', 'LI'))).toBeTruthy() + expect(await screen.findByText(byExactTextContent('Manifest viewer plan: operator', 'LI'))).toBeTruthy() + expect(await screen.findByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toBeTruthy() + expect(screen.getByText(/bounded fallback metadata until auth-server release authority returns/i)).toBeTruthy() + }) + + it('invalidates the protected browser session when live release-manifest authority comes back anonymous', async () => { + mockGetReleaseManifest.mockResolvedValue({ + ok: true, + manifest: { + generated_at: '2026-06-23T12:00:00.000Z', + support_email: 'hello@hypertwist.app', + public_docs_url: 'https://docs.hypertwist.app', + release_notes_url: 'https://notes.hypertwist.app', + corresponding_source_url: 'https://hypertwist.app/open-source/source.zip', + open_source_repo_url: 'https://github.com/hypertwist/hypertwist', + viewer: { + authenticated: false, + canDownload: false, + plan: null, + role: null, + accessStatus: null, + }, + platforms: [ + { + platform_key: 'windows', + platform: 'Windows', + subtitle: 'Primary shipping lane', + details: 'Current packaged validation is strongest on the Windows Unreal lane.', + configured: true, + channel: 'candidate', + version: '1.0.0', + build_id: 'win64-1000', + published_at: '2026-06-22T00:00:00.000Z', + file_name: 'HyperTwist-Windows.zip', + file_size_bytes: 1048576, + checksum_sha256: 'abc123', + download_url: null, + download_available: false, + validation_summary: { + lane: 'Windows Unreal packaged validation', + result: 'passed', + generated_at: '2026-06-22T01:43:08.7625247Z', + configuration: 'Development', + skip_build: true, + smoke_map_count: 2, + smoke_maps: [ + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining', + label: 'Magic120Cell dedicated-family training map', + result: 'passed', + }, + { + map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining', + label: 'MagicCube5D dedicated-family training map', + result: 'passed', + }, + ], + }, + }, + ], + }, + }) + + renderPage(, '/app/account') + + await waitFor(() => { + expect(mockInvalidateSession).toHaveBeenCalledWith('release_manifest_unauthorized') + }) + }) + + it('surfaces live authority sync details after the protected account lane refreshes from manifest truth', async () => { + mockUsePlatformAuth.mockReturnValue({ + user: { + id: 'operator-1', + name: 'Operator', + email: 'operator@hypertwist.app', + authMethod: 'email', + plan: 'free', + role: 'viewer', + canDownload: false, + billing: { + source: 'session', + accessStatus: 'session-default', + canDownload: false, + lastEventType: 'transaction.completed', + }, + }, + superTokensConfigured: true, + invalidateSession: (...args: unknown[]) => mockInvalidateSession(...args), + reconcileReleaseAuthority: vi.fn(), + releaseAuthoritySyncItems: [ + 'Plan changed from local free to live operator.', + 'Desktop download access changed from local not entitled to live enabled.', + 'Access status changed from local session-default to live active.', + ], + }) + + renderPage(, '/app/account') + + expect(await screen.findByText('Live authority sync')).toBeTruthy() + expect(screen.getByText(/plan changed from local free to live operator/i)).toBeTruthy() + expect(screen.getByText(/desktop download access changed from local not entitled to live enabled/i)).toBeTruthy() + expect(screen.getByText(/access status changed from local session-default to live active/i)).toBeTruthy() }) }) diff --git a/website/src/__tests__/public-auth-pages.test.tsx b/website/src/__tests__/public-auth-pages.test.tsx index 6d2afec..c392237 100644 --- a/website/src/__tests__/public-auth-pages.test.tsx +++ b/website/src/__tests__/public-auth-pages.test.tsx @@ -40,21 +40,21 @@ vi.mock('../site-config', async () => { platformKey: 'windows', platform: 'Windows', subtitle: 'Primary shipping lane', - href: 'https://downloads.hypertwist.app/windows.exe', + configured: true, details: 'Current packaged validation is strongest on the Windows Unreal lane.', }, { platformKey: 'macos', platform: 'macOS', subtitle: 'Planned distribution surface', - href: '', + configured: false, details: 'List a signed desktop build here when the package lane is opened.', }, { platformKey: 'linux', platform: 'Linux', subtitle: 'Operator-targeted later lane', - href: '', + configured: false, details: 'Use for future package publication after the bounded release lane is widened.', }, ], diff --git a/website/src/__tests__/public-marketing-pages.test.tsx b/website/src/__tests__/public-marketing-pages.test.tsx index b0af0ca..31ff0f4 100644 --- a/website/src/__tests__/public-marketing-pages.test.tsx +++ b/website/src/__tests__/public-marketing-pages.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { render, screen, within } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -37,21 +37,21 @@ vi.mock('../site-config', () => ({ platformKey: 'windows', platform: 'Windows', subtitle: 'Primary shipping lane', - href: 'https://downloads.hypertwist.app/windows.exe', + configured: true, details: 'Current packaged validation is strongest on the Windows Unreal lane.', }, { platformKey: 'macos', platform: 'macOS', subtitle: 'Planned distribution surface', - href: '', + configured: false, details: 'List a signed desktop build here when the package lane is opened.', }, { platformKey: 'linux', platform: 'Linux', subtitle: 'Operator-targeted later lane', - href: '', + configured: false, details: 'Use for future package publication after the bounded release lane is widened.', }, ], @@ -238,6 +238,8 @@ describe('public marketing pages', () => { expect(screen.getAllByText('Preview posture').length).toBeGreaterThan(0) expect(screen.getByText('Operator checkout: missing')).toBeTruthy() expect(screen.getByText('How the release lane works')).toBeTruthy() + expect(screen.getByText('First launch and desktop setup')).toBeTruthy() + expect(screen.getByText('Pair desktop access to the browser account')).toBeTruthy() expect(await screen.findByText('Version: 1.0.0')).toBeTruthy() expect(screen.getByText('SHA-256: abc123')).toBeTruthy() expect(screen.getByText('Packaged validation passed')).toBeTruthy() @@ -550,6 +552,8 @@ describe('public marketing pages', () => { expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy() expect(screen.getByText('Is VR/controller support already fully finished?')).toBeTruthy() expect(screen.getByText('Support lanes')).toBeTruthy() + expect(screen.getByText('Escalation map')).toBeTruthy() + expect(screen.getByText('Runtime and training issues')).toBeTruthy() }) it('surfaces current packaged desktop proof on the public resources page', () => { @@ -647,9 +651,14 @@ describe('public marketing pages', () => { }) renderWithProviders(, ['/resources']) - expect(screen.getByText('Current packaged desktop proof')).toBeTruthy() - expect(screen.getByText('Windows Unreal packaged validation')).toBeTruthy() - expect(screen.getAllByText(/MagicCube5D dedicated-family training map: passed/i).length).toBeGreaterThan(0) + const packagedProofHeading = screen.getByRole('heading', { name: 'Current packaged desktop proof' }) + const packagedProofSection = packagedProofHeading.closest('section') + + expect(packagedProofSection).toBeTruthy() + expect(within(packagedProofSection as HTMLElement).getByText('Packaged validation passed')).toBeTruthy() + expect(within(packagedProofSection as HTMLElement).getByText(/The Windows lane has current first-party package proof/i)).toBeTruthy() + expect(within(packagedProofSection as HTMLElement).getByText(/MagicCube5D dedicated-family training map: passed/i)).toBeTruthy() + expect(within(packagedProofSection as HTMLElement).getByText(/Build step during latest proof: skipped and reused existing target receipt/i)).toBeTruthy() expect(screen.getByText('Operator playbooks')).toBeTruthy() expect(screen.getByText('Simulator use today')).toBeTruthy() expect(screen.getByText('Higher-dimensional runtime guide')).toBeTruthy() diff --git a/website/src/__tests__/release-manifest.test.ts b/website/src/__tests__/release-manifest.test.ts index 247852c..066bdc3 100644 --- a/website/src/__tests__/release-manifest.test.ts +++ b/website/src/__tests__/release-manifest.test.ts @@ -10,7 +10,7 @@ describe('buildFallbackReleaseManifest', () => { platformKey: 'windows', platform: 'Windows', subtitle: 'Primary shipping lane', - href: '', + configured: false, details: 'Current packaged validation is strongest on the Windows Unreal lane.', }, ], @@ -31,4 +31,70 @@ describe('buildFallbackReleaseManifest', () => { }, }) }) + + it('keeps fallback release metadata while withholding raw download delivery authority', () => { + const manifest = buildFallbackReleaseManifest({ + downloadTargets: [ + { + platformKey: 'windows', + platform: 'Windows', + subtitle: 'Primary shipping lane', + configured: true, + details: 'Current packaged validation is strongest on the Windows Unreal lane.', + }, + ], + publicDocsUrl: 'https://hypertwist.app/resources', + releaseNotesUrl: 'https://hypertwist.app/changelog', + correspondingSourceUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist', + openSourceRepoUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist', + supportEmail: 'hello@hypertwist.app', + }) + + expect(manifest.platforms[0]).toMatchObject({ + platform_key: 'windows', + configured: true, + download_available: false, + download_url: null, + }) + }) + + it('can keep protected viewer posture in fallback metadata without exposing download authority', () => { + const manifest = buildFallbackReleaseManifest({ + downloadTargets: [ + { + platformKey: 'windows', + platform: 'Windows', + subtitle: 'Primary shipping lane', + configured: true, + details: 'Current packaged validation is strongest on the Windows Unreal lane.', + }, + ], + publicDocsUrl: 'https://hypertwist.app/resources', + releaseNotesUrl: 'https://hypertwist.app/changelog', + correspondingSourceUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist', + openSourceRepoUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist', + supportEmail: 'operator@hypertwist.app', + viewer: { + authenticated: true, + canDownload: true, + plan: 'operator', + role: 'operator', + accessStatus: 'active', + }, + }) + + expect(manifest.viewer).toMatchObject({ + authenticated: true, + canDownload: true, + plan: 'operator', + role: 'operator', + accessStatus: 'active', + }) + expect(manifest.platforms[0]).toMatchObject({ + platform_key: 'windows', + configured: true, + download_available: false, + download_url: null, + }) + }) }) diff --git a/website/src/auth/auth-api.ts b/website/src/auth/auth-api.ts index 85de385..b8e2fd4 100644 --- a/website/src/auth/auth-api.ts +++ b/website/src/auth/auth-api.ts @@ -28,6 +28,13 @@ function buildTimeoutSignal(timeoutMs: number) { } } +function createAuthApiError(status: number, code: string) { + const error = new Error(code) as Error & { status?: number; code?: string } + error.status = status + error.code = code + return error +} + export interface AuthApiFetchResult { response: Response baseUrl: string @@ -229,7 +236,10 @@ export async function createDesktopLinkToken() { }) const payload = await result.response.json() as DesktopLinkPayload | { error?: string } if (!result.response.ok || payload == null || (payload as DesktopLinkPayload).ok !== true) { - throw new Error((payload as { error?: string }).error || `desktop_link_http_${result.response.status}`) + throw createAuthApiError( + result.response.status, + (payload as { error?: string }).error || `desktop_link_http_${result.response.status}`, + ) } return payload as DesktopLinkPayload } @@ -238,7 +248,10 @@ export async function verifyDesktopLinkToken(token: string) { const result = await authApiFetch(`/api/auth/desktop-link/verify?token=${encodeURIComponent(token)}`) const payload = await result.response.json() as DesktopLinkVerifyPayload | { error?: string } if (!result.response.ok || payload == null || (payload as DesktopLinkVerifyPayload).ok !== true) { - throw new Error((payload as { error?: string }).error || `desktop_link_verify_http_${result.response.status}`) + throw createAuthApiError( + result.response.status, + (payload as { error?: string }).error || `desktop_link_verify_http_${result.response.status}`, + ) } return payload as DesktopLinkVerifyPayload } @@ -247,7 +260,7 @@ export async function getAuthHealth() { const result = await authApiFetch('/api/auth/health') const payload = await result.response.json() as AuthHealthPayload if (!result.response.ok) { - throw new Error(`auth_health_http_${result.response.status}`) + throw createAuthApiError(result.response.status, `auth_health_http_${result.response.status}`) } return payload } @@ -256,7 +269,10 @@ export async function getReleaseManifest() { const result = await authApiFetch('/api/releases/manifest') const payload = await result.response.json() as ReleaseManifestPayload | { error?: string } if (!result.response.ok || payload == null || (payload as ReleaseManifestPayload).ok !== true) { - throw new Error((payload as { error?: string }).error || `release_manifest_http_${result.response.status}`) + throw createAuthApiError( + result.response.status, + (payload as { error?: string }).error || `release_manifest_http_${result.response.status}`, + ) } return payload as ReleaseManifestPayload } diff --git a/website/src/auth/platform-auth.tsx b/website/src/auth/platform-auth.tsx index 158913a..a4f2301 100644 --- a/website/src/auth/platform-auth.tsx +++ b/website/src/auth/platform-auth.tsx @@ -1,15 +1,22 @@ import { createContext, + type Dispatch, useContext, useEffect, useMemo, useState, type ReactNode, + type SetStateAction, } from 'react' import { signIn as superTokensSignIn, signUp as superTokensSignUp } from 'supertokens-auth-react/recipe/emailpassword' import { redirectToThirdPartyLogin } from 'supertokens-auth-react/recipe/thirdparty' import { signOut as superTokensSignOut } from 'supertokens-auth-react/recipe/session' -import { getCurrentUser, logoutCurrentUser, type ApiBootstrapUserPayload } from './auth-api' +import { + getCurrentUser, + logoutCurrentUser, + type ApiBootstrapUserPayload, + type ReleaseManifestPayload, +} from './auth-api' import { ensureSuperTokensInit, isSuperTokensConfigured } from './supertokens-client' import { validateStrongPassword } from './password-policy' @@ -54,22 +61,29 @@ export interface PlatformRegisterInput { name?: string } +export type ReleaseAuthorityViewer = ReleaseManifestPayload['manifest']['viewer'] + interface PlatformAuthContextValue { user: PlatformUser | null isAuthenticated: boolean isLoading: boolean colorMode: ColorMode superTokensConfigured: boolean + releaseAuthoritySyncItems: string[] login: (input: PlatformLoginInput) => Promise<{ ok: boolean; error?: string }> register: (input: PlatformRegisterInput) => Promise<{ ok: boolean; error?: string }> loginWithProvider: (provider: 'github' | 'google' | 'orcid') => Promise logout: () => Promise toggleColorMode: () => void + reconcileReleaseAuthority: (viewer: ReleaseAuthorityViewer) => boolean + invalidateSession: (reason?: string) => void } const AUTH_STORAGE_KEY = 'hypertwist.platform.user.v1' const COLOR_MODE_STORAGE_KEY = 'hypertwist.platform.color-mode.v1' const PlatformAuthContext = createContext(null) +type PlatformUserSetter = Dispatch> +type ReleaseAuthoritySyncSetter = Dispatch> function normalizeEmail(value: unknown) { return String(value || '').trim().toLowerCase() @@ -166,6 +180,31 @@ function writeStoredColorMode(mode: ColorMode) { document.documentElement.dataset.theme = mode } +function persistSessionUser( + setUser: PlatformUserSetter, + setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter, + nextUser: PlatformUser | null, +) { + setUser(nextUser) + setReleaseAuthoritySyncItems([]) + writeStoredUser(nextUser) +} + +function restoreStoredSession( + setUser: PlatformUserSetter, + setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter, +) { + setUser(readStoredUser()) + setReleaseAuthoritySyncItems([]) +} + +function clearSessionState( + setUser: PlatformUserSetter, + setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter, +) { + persistSessionUser(setUser, setReleaseAuthoritySyncItems, null) +} + function normalizeApiUser(input: ApiBootstrapUserPayload['user'] | undefined, fallbackMethod: AuthMethod): PlatformUser | null { if (!input) return null const id = String(input.id || '').trim() @@ -197,6 +236,74 @@ function normalizeApiUser(input: ApiBootstrapUserPayload['user'] | undefined, fa } } +function computeReleaseAuthoritySyncItems(user: PlatformUser, viewer: ReleaseAuthorityViewer) { + const syncItems: string[] = [] + const localRole = user.role || 'operator' + const viewerRole = normalizeRole(viewer.role) || localRole + const localAccessStatus = user.billing?.accessStatus || 'session-default' + const viewerAccessStatus = String(viewer.accessStatus || '').trim() || localAccessStatus + + if (viewer.plan && user.plan !== viewer.plan) { + syncItems.push(`Plan changed from local ${user.plan} to live ${viewer.plan}.`) + } + + if (localRole !== viewerRole) { + syncItems.push(`Role changed from local ${localRole} to live ${viewerRole}.`) + } + + if (Boolean(user.canDownload) !== viewer.canDownload) { + syncItems.push( + `Desktop download access changed from local ${user.canDownload ? 'enabled' : 'not entitled'} to live ${viewer.canDownload ? 'enabled' : 'not entitled'}.`, + ) + } + + if (localAccessStatus !== viewerAccessStatus) { + syncItems.push(`Access status changed from local ${localAccessStatus} to live ${viewerAccessStatus}.`) + } + + return syncItems +} + +function reconcileUserWithReleaseAuthority(user: PlatformUser | null, viewer: ReleaseAuthorityViewer) { + if (!user || !viewer.authenticated) { + return { nextUser: user, syncItems: [] as string[] } + } + + const syncItems = computeReleaseAuthoritySyncItems(user, viewer) + if (syncItems.length === 0) { + return { nextUser: user, syncItems } + } + + const nextRole = normalizeRole(viewer.role) || user.role + const nextPlan = viewer.plan ? normalizePlan(viewer.plan) : user.plan + const nextAccessStatus = String(viewer.accessStatus || '').trim() || user.billing?.accessStatus || 'session-default' + const existingBilling = user.billing + + return { + nextUser: { + ...user, + plan: nextPlan, + role: nextRole, + isAdmin: user.isAdmin === true || nextRole === 'admin', + canDownload: viewer.canDownload, + billing: { + source: existingBilling?.source || 'session', + accessStatus: nextAccessStatus, + canDownload: viewer.canDownload, + subscriptionId: existingBilling?.subscriptionId || null, + customerId: existingBilling?.customerId || null, + transactionId: existingBilling?.transactionId || null, + lastEventId: existingBilling?.lastEventId || null, + lastEventType: existingBilling?.lastEventType || null, + lastEventAt: existingBilling?.lastEventAt || null, + updatedAt: existingBilling?.updatedAt || null, + statePath: existingBilling?.statePath || null, + }, + }, + syncItems, + } +} + function createLocalFallbackUser(email: string, password: string, name?: string): PlatformUser { const safeEmail = normalizeEmail(email) return { @@ -227,16 +334,23 @@ async function parseSignUpError(response: { status?: string; formFields?: Array< return 'Unable to create your account.' } -export function PlatformAuthProvider({ children }: { children: ReactNode }) { - const [user, setUser] = useState(() => readStoredUser()) - const [isLoading, setIsLoading] = useState(true) - const [colorMode, setColorMode] = useState(() => readStoredColorMode()) - const superTokensConfigured = isSuperTokensConfigured() - +function usePersistedColorMode(colorMode: ColorMode) { useEffect(() => { writeStoredColorMode(colorMode) }, [colorMode]) +} +function useBootstrapPlatformSession({ + superTokensConfigured, + setIsLoading, + setUser, + setReleaseAuthoritySyncItems, +}: { + superTokensConfigured: boolean + setIsLoading: Dispatch> + setUser: PlatformUserSetter + setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter +}) { useEffect(() => { let cancelled = false @@ -250,7 +364,11 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { const response = await getCurrentUser() if (!response.ok) { if (!cancelled) { - setUser(readStoredUser()) + if (superTokensConfigured && response.status === 401) { + clearSessionState(setUser, setReleaseAuthoritySyncItems) + } else { + restoreStoredSession(setUser, setReleaseAuthoritySyncItems) + } } return } @@ -258,12 +376,11 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { const payload = await response.json() as ApiBootstrapUserPayload const nextUser = normalizeApiUser(payload.user, deriveBootstrapAuthMethod(payload, 'supertokens')) if (!cancelled) { - setUser(nextUser) - writeStoredUser(nextUser) + persistSessionUser(setUser, setReleaseAuthoritySyncItems, nextUser) } } catch { if (!cancelled) { - setUser(readStoredUser()) + restoreStoredSession(setUser, setReleaseAuthoritySyncItems) } } finally { if (!cancelled) { @@ -277,7 +394,23 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { return () => { cancelled = true } - }, [superTokensConfigured]) + }, [setIsLoading, setReleaseAuthoritySyncItems, setUser, superTokensConfigured]) +} + +export function PlatformAuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(() => readStoredUser()) + const [isLoading, setIsLoading] = useState(true) + const [colorMode, setColorMode] = useState(() => readStoredColorMode()) + const [releaseAuthoritySyncItems, setReleaseAuthoritySyncItems] = useState([]) + const superTokensConfigured = isSuperTokensConfigured() + + usePersistedColorMode(colorMode) + useBootstrapPlatformSession({ + superTokensConfigured, + setIsLoading, + setUser, + setReleaseAuthoritySyncItems, + }) const value = useMemo(() => ({ user, @@ -285,6 +418,7 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { isLoading, colorMode, superTokensConfigured, + releaseAuthoritySyncItems, async login(input) { if (input.method === 'github' || input.method === 'google' || input.method === 'orcid') { await redirectToThirdPartyLogin({ thirdPartyId: input.method }) @@ -299,8 +433,7 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { if (!superTokensConfigured) { const localUser = createLocalFallbackUser(safeEmail, input.password || '') - setUser(localUser) - writeStoredUser(localUser) + persistSessionUser(setUser, setReleaseAuthoritySyncItems, localUser) return { ok: true } } @@ -323,8 +456,7 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { } const payload = await bootstrapResponse.json() as ApiBootstrapUserPayload const nextUser = normalizeApiUser(payload.user, deriveBootstrapAuthMethod(payload, 'supertokens')) - setUser(nextUser) - writeStoredUser(nextUser) + persistSessionUser(setUser, setReleaseAuthoritySyncItems, nextUser) } catch { return { ok: false, error: 'Signed in, but the account API is unavailable.' } } @@ -343,8 +475,7 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { if (!superTokensConfigured) { const localUser = createLocalFallbackUser(safeEmail, input.password, input.name) - setUser(localUser) - writeStoredUser(localUser) + persistSessionUser(setUser, setReleaseAuthoritySyncItems, localUser) return { ok: true } } @@ -367,8 +498,7 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { } const payload = await bootstrapResponse.json() as ApiBootstrapUserPayload const nextUser = normalizeApiUser(payload.user, deriveBootstrapAuthMethod(payload, 'supertokens')) - setUser(nextUser) - writeStoredUser(nextUser) + persistSessionUser(setUser, setReleaseAuthoritySyncItems, nextUser) } catch { return { ok: false, error: 'Account created, but the account API is unavailable.' } } @@ -383,8 +513,7 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { await redirectToThirdPartyLogin({ thirdPartyId: provider }) }, async logout() { - writeStoredUser(null) - setUser(null) + clearSessionState(setUser, setReleaseAuthoritySyncItems) if (!superTokensConfigured) { return } @@ -402,7 +531,21 @@ export function PlatformAuthProvider({ children }: { children: ReactNode }) { toggleColorMode() { setColorMode((current) => current === 'dark' ? 'light' : 'dark') }, - }), [colorMode, isLoading, superTokensConfigured, user]) + reconcileReleaseAuthority(viewer) { + const reconciled = reconcileUserWithReleaseAuthority(user, viewer) + if (reconciled.syncItems.length > 0 && reconciled.nextUser) { + setUser(reconciled.nextUser) + setReleaseAuthoritySyncItems(reconciled.syncItems) + writeStoredUser(reconciled.nextUser) + return true + } + + return false + }, + invalidateSession() { + clearSessionState(setUser, setReleaseAuthoritySyncItems) + }, + }), [colorMode, isLoading, releaseAuthoritySyncItems, superTokensConfigured, user]) return {children} } diff --git a/website/src/components/ui/PublicLaunchStatus.tsx b/website/src/components/ui/PublicLaunchStatus.tsx index 7bfcdcf..3949864 100644 --- a/website/src/components/ui/PublicLaunchStatus.tsx +++ b/website/src/components/ui/PublicLaunchStatus.tsx @@ -149,7 +149,7 @@ export function PublicLaunchStatus({ ) : null} {releaseManifestQuery.isError ? (

- Live release-manifest lookup failed. This launch checklist is currently using frontend configuration fallback for the download lane. + Live release-manifest lookup failed. This launch checklist is currently using a bounded frontend fallback and intentionally withholds direct download-lane authority until the auth server returns.

) : null} {!ready ? ( diff --git a/website/src/pages/app-pages.tsx b/website/src/pages/app-pages.tsx index 5078aee..868b4d9 100644 --- a/website/src/pages/app-pages.tsx +++ b/website/src/pages/app-pages.tsx @@ -1,15 +1,15 @@ -import { useMemo } from 'react' +import { useEffect, useMemo } from 'react' import { useMutation, useQuery } from '@tanstack/react-query' import { Link, useSearchParams } from 'react-router-dom' import { buildAuthApiBaseUrls, createDesktopLinkToken, getAuthHealth, getReleaseManifest } from '../auth/auth-api' -import { usePlatformAuth } from '../auth/platform-auth' +import { usePlatformAuth, type PlatformUser } from '../auth/platform-auth' import { SiteMetadata } from '../components/seo/SiteMetadata' import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary' import { resolvePublicLaunchReadiness } from '../public-launch' import { downloadTargets, launchReadiness, mplSourceUrl, openSourceRepoUrl, planCatalog, publicDocsUrl, releaseNotesUrl } from '../site-config' import { buildReleaseMetadataItems, resolveReleaseCommerceView, resolveReleaseManifestView, type ReleaseManifestView } from '../release-manifest' import { buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes' -import { desktopDownloadSteps, roadmapHonestyCards } from '../site-data' +import { desktopDownloadSteps, desktopFirstLaunchCards, roadmapHonestyCards, supportEscalationCards } from '../site-data' function Panel({ title, @@ -38,7 +38,7 @@ const releaseCommerceFallback = { planPriceStudio: studioFallbackPlan.price, } -function buildProtectedReleaseManifestFallback(supportEmail: string) { +function buildProtectedReleaseManifestFallback(user: PlatformUser | null | undefined, supportEmail: string) { return { downloadTargets, publicDocsUrl, @@ -46,6 +46,13 @@ function buildProtectedReleaseManifestFallback(supportEmail: string) { correspondingSourceUrl: mplSourceUrl, openSourceRepoUrl, supportEmail, + viewer: user ? { + authenticated: true, + canDownload: Boolean(user.canDownload), + plan: user.plan, + role: user.role || null, + accessStatus: user.billing?.accessStatus || null, + } : undefined, } } @@ -65,6 +72,77 @@ function useProtectedReleaseManifestQuery(scope: string, userId: string | undefi }) } +function useProtectedReleaseSurface(scope: string) { + const platformAuth = usePlatformAuth() + const { user, reconcileReleaseAuthority, invalidateSession, superTokensConfigured } = platformAuth + const canDownload = user?.canDownload === true + const releaseManifestQuery = useProtectedReleaseManifestQuery(scope, user?.id, canDownload) + const releaseManifest = useMemo( + () => resolveReleaseManifestView( + releaseManifestQuery.data?.manifest, + buildProtectedReleaseManifestFallback(user, user?.email || 'hello@hypertwist.app'), + ), + [releaseManifestQuery.data?.manifest, user], + ) + + useEffect(() => { + const liveViewer = releaseManifestQuery.data?.manifest?.viewer + if (liveViewer?.authenticated && typeof reconcileReleaseAuthority === 'function') { + reconcileReleaseAuthority(liveViewer) + } + }, [reconcileReleaseAuthority, releaseManifestQuery.data?.manifest?.viewer]) + + useEffect(() => { + const liveViewer = releaseManifestQuery.data?.manifest?.viewer + if ( + user + && superTokensConfigured + && liveViewer + && liveViewer.authenticated === false + && typeof invalidateSession === 'function' + ) { + invalidateSession('release_manifest_unauthorized') + } + }, [invalidateSession, releaseManifestQuery.data?.manifest?.viewer, superTokensConfigured, user]) + + return { + ...platformAuth, + canDownload, + releaseManifest, + releaseManifestQuery, + releaseAuthoritySyncItems: platformAuth.releaseAuthoritySyncItems ?? [], + } +} + +function getProtectedDownloadActionLabel({ + configured, + viewerCanDownload, + releaseAuthorityUnavailable, +}: { + configured: boolean + viewerCanDownload: boolean + releaseAuthorityUnavailable: boolean +}) { + if (!configured) { + return 'Release URL not configured yet' + } + + if (viewerCanDownload) { + return releaseAuthorityUnavailable + ? 'Live release authority temporarily unavailable' + : 'Download temporarily unavailable' + } + + return 'Account not entitled yet' +} + +function isUnauthorizedAuthApiError(error: unknown) { + return typeof error === 'object' + && error !== null + && 'status' in error + && Number((error as { status?: number }).status) === 401 +} + function ReleaseAuthorityLinks({ releaseManifest, includePublicNoticesLink = false, @@ -123,22 +201,88 @@ function ReleaseAuthorityLinks({ ) } +function DesktopFirstLaunchChecklist({ title, kicker }: { title: string; kicker: string }) { + return ( + +
+ {desktopFirstLaunchCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+ ) +} + +function SupportEscalationChecklist({ + title, + kicker, + description, + secondaryLink, +}: { + title: string + kicker: string + description: string + secondaryLink: { + to: string + label: string + } +}) { + return ( + +

{description}

+
+ {supportEscalationCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+ + Open support + + + {secondaryLink.label} + +
+
+ ) +} + export function DashboardOverviewPage() { - const { user, superTokensConfigured } = usePlatformAuth() + const { + user, + superTokensConfigured, + invalidateSession, + releaseAuthoritySyncItems, + releaseManifest, + releaseManifestQuery, + } = useProtectedReleaseSurface('dashboard') const healthQuery = useProtectedAuthHealthQuery('dashboard') - const releaseManifestQuery = useProtectedReleaseManifestQuery('dashboard', user?.id, user?.canDownload === true) const desktopLinkMutation = useMutation({ mutationFn: createDesktopLinkToken, + onError(error) { + if (isUnauthorizedAuthApiError(error) && typeof invalidateSession === 'function') { + invalidateSession('desktop_link_unauthorized') + } + }, }) - const releaseManifest = useMemo( - () => resolveReleaseManifestView( - releaseManifestQuery.data?.manifest, - buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'), - ), - [releaseManifestQuery.data?.manifest, user?.email], - ) const releaseCommerce = useMemo( () => resolveReleaseCommerceView(releaseManifestQuery.data?.manifest, releaseCommerceFallback), [releaseManifestQuery.data?.manifest], @@ -205,6 +349,19 @@ export function DashboardOverviewPage() { This session is currently using local fallback posture, not fully shared production auth.

) : null} + {releaseManifestQuery.data?.manifest && releaseAuthoritySyncItems.length > 0 ? ( +
+

Live authority sync

+

+ The protected browser session was refreshed from live release authority so the local dashboard view catches up to current account-access truth. +

+
    + {releaseAuthoritySyncItems.map((item) => ( +
  • {item}
  • + ))} +
+
+ ) : null} @@ -331,6 +488,11 @@ export function DashboardOverviewPage() { )} + +
    {roadmapHonestyCards.map((item) => ( @@ -344,18 +506,9 @@ export function DashboardOverviewPage() { } export function DownloadCenterPage() { - const { user } = usePlatformAuth() + const { user, canDownload, releaseManifest, releaseManifestQuery } = useProtectedReleaseSurface('protected') const [searchParams] = useSearchParams() - const canDownload = user?.canDownload === true const requestedPlatform = normalizeDownloadPlatform(searchParams.get('platform')) - const releaseManifestQuery = useProtectedReleaseManifestQuery('protected', user?.id, canDownload) - const releaseManifest = useMemo( - () => resolveReleaseManifestView( - releaseManifestQuery.data?.manifest, - buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'), - ), - [canDownload, releaseManifestQuery.data?.manifest, user?.email], - ) const orderedTargets = useMemo(() => { if (!requestedPlatform) { return releaseManifest.platforms @@ -372,6 +525,8 @@ export function DownloadCenterPage() { ] }, [releaseManifest.platforms, requestedPlatform]) const requestedPlatformLabel = requestedPlatform ? getDownloadPlatformLabel(requestedPlatform) : null + const viewerCanDownload = releaseManifest.viewer.canDownload || canDownload + const releaseAuthorityUnavailable = releaseManifestQuery.isError return ( <> @@ -392,18 +547,27 @@ export function DownloadCenterPage() {

    The live release manifest could not be loaded from the auth server right now. - This panel is showing bounded fallback site metadata instead of current runtime release authority. + This panel is showing bounded fallback site metadata instead of current runtime release authority, and direct package delivery remains intentionally withheld until the auth server returns.

    ) : null} - {!canDownload ? ( + {!viewerCanDownload ? (

    Your current account does not yet have desktop download entitlement. Complete the matching checkout or operator provisioning step, then refresh this dashboard.

    ) : null} + {releaseAuthorityUnavailable && viewerCanDownload ? ( +
    +

    Fallback release posture

    +

    + Your signed-in account still resolves to desktop access, but live release authority is temporarily unavailable. + Direct package delivery remains intentionally withheld until the auth server returns. +

    +
    + ) : null} {requestedPlatformLabel ? (

    Requested target

    @@ -438,13 +602,17 @@ export function DownloadCenterPage() {
) : null} - {target.download_url && canDownload ? ( + {target.download_url && viewerCanDownload ? ( Download {target.platform} ) : (
- {target.configured ? 'Account not entitled yet' : 'Release URL not configured yet'} + {getProtectedDownloadActionLabel({ + configured: target.configured, + viewerCanDownload, + releaseAuthorityUnavailable, + })}
)} @@ -482,22 +650,19 @@ export function DownloadCenterPage() { ) : null}
+ + ) } export function BrowserAccessPage() { - const { user } = usePlatformAuth() + const { user, releaseManifest, releaseManifestQuery } = useProtectedReleaseSurface('browser-access') const healthQuery = useProtectedAuthHealthQuery('browser-access') - const releaseManifestQuery = useProtectedReleaseManifestQuery('browser-access', user?.id, user?.canDownload === true) - const releaseManifest = useMemo( - () => resolveReleaseManifestView( - releaseManifestQuery.data?.manifest, - buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'), - ), - [releaseManifestQuery.data?.manifest, user?.email], - ) const configuredPlatformCount = useMemo( () => releaseManifest.platforms.filter((platform) => platform.configured).length, [releaseManifest.platforms], @@ -565,6 +730,12 @@ export function BrowserAccessPage() { +
  • The optional full-browser client remains spec-only.
  • @@ -583,15 +754,13 @@ export function BrowserAccessPage() { } export function AccountPage() { - const { user, superTokensConfigured } = usePlatformAuth() - const releaseManifestQuery = useProtectedReleaseManifestQuery('account', user?.id, user?.canDownload === true) - const releaseManifest = useMemo( - () => resolveReleaseManifestView( - releaseManifestQuery.data?.manifest, - buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'), - ), - [releaseManifestQuery.data?.manifest, user?.email], - ) + const { + user, + superTokensConfigured, + releaseAuthoritySyncItems, + releaseManifest, + releaseManifestQuery, + } = useProtectedReleaseSurface('account') const configuredPlatforms = useMemo( () => releaseManifest.platforms.filter((platform) => platform.configured), [releaseManifest.platforms], @@ -617,6 +786,19 @@ export function AccountPage() {
  • Billing source: {user?.billing?.source || 'session'}
  • Auth stack: {superTokensConfigured ? 'SuperTokens-backed' : 'Local fallback mode'}
+ {releaseManifestQuery.data?.manifest && releaseAuthoritySyncItems.length > 0 ? ( +
+

Live authority sync

+

+ The protected browser session was refreshed from live release authority so the local account snapshot matches current release-access truth. +

+
    + {releaseAuthoritySyncItems.map((item) => ( +
  • {item}
  • + ))} +
+
+ ) : null}
{releaseManifestQuery.isLoading ?

Refreshing release-access posture from the live manifest...

: null} @@ -665,21 +847,18 @@ export function AccountPage() {
+ + ) } export function NoticesPage() { - const { user } = usePlatformAuth() - const releaseManifestQuery = useProtectedReleaseManifestQuery('notices', user?.id, user?.canDownload === true) - const releaseManifest = useMemo( - () => resolveReleaseManifestView( - releaseManifestQuery.data?.manifest, - buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'), - ), - [releaseManifestQuery.data?.manifest, user?.email], - ) + const { releaseManifest, releaseManifestQuery } = useProtectedReleaseSurface('notices') const configuredPlatforms = useMemo( () => releaseManifest.platforms.filter((platform) => platform.configured), [releaseManifest.platforms], @@ -737,6 +916,12 @@ export function NoticesPage() { includePublicNoticesLink /> + ) diff --git a/website/src/pages/public-pages-commerce.tsx b/website/src/pages/public-pages-commerce.tsx index 946bb6b..83c4a0e 100644 --- a/website/src/pages/public-pages-commerce.tsx +++ b/website/src/pages/public-pages-commerce.tsx @@ -15,6 +15,7 @@ import { import { buildProtectedDownloadPath, buildSupportPath } from '../site-routes' import { deliverySurfaceCards, + desktopFirstLaunchCards, desktopDownloadSteps, desktopReleaseSignals, digitalDeliveryCards, @@ -157,7 +158,7 @@ export function DownloadPage() {

The live release manifest could not be loaded from the auth server right now. - This page is showing bounded fallback site metadata instead of current runtime release authority. + This page is showing bounded fallback site metadata instead of current runtime release authority, and direct package delivery stays intentionally withheld until that authority returns.

@@ -208,6 +209,25 @@ export function DownloadPage() { +
+
+ {desktopFirstLaunchCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+
diff --git a/website/src/pages/public-pages-marketing.tsx b/website/src/pages/public-pages-marketing.tsx index 8086529..dbc6fde 100644 --- a/website/src/pages/public-pages-marketing.tsx +++ b/website/src/pages/public-pages-marketing.tsx @@ -6,9 +6,9 @@ import { getReleaseManifest } from '../auth/auth-api' import { MarketingShell } from '../components/layout/MarketingShell' import { SiteMetadata } from '../components/seo/SiteMetadata' import { PublicLaunchStatus } from '../components/ui/PublicLaunchStatus' +import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary' import { brandConfig } from '../site-config' -import { formatReleasePublishedAt, resolveReleaseManifestView } from '../release-manifest' -import { getReleasePlatformValidationSummary } from '../shared/package-validation' +import { resolveReleaseManifestView } from '../release-manifest' import { capabilityPillars, changelogEntries, @@ -24,7 +24,9 @@ import { releaseStoryCards, resourceCollections, roadmapHonestyCards, + runtimeControlGuideCards, shippingNowCards, + supportEscalationCards, simulatorManualCards, supportFaqs, } from '../site-data' @@ -250,7 +252,19 @@ export function AboutPage() { export function ResourcesPage() { const [query, setQuery] = useState('') const deferredQuery = useDeferredValue(query) - const windowsValidationSummary = getReleasePlatformValidationSummary('windows') + const releaseManifestQuery = useQuery({ + queryKey: ['release-manifest', 'public-resources'], + queryFn: getReleaseManifest, + retry: false, + }) + const releaseManifest = useMemo( + () => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()), + [releaseManifestQuery.data?.manifest], + ) + const windowsValidationPlatform = useMemo( + () => releaseManifest.platforms.find((platform) => platform.platform_key === 'windows' && platform.validation_summary) ?? null, + [releaseManifest], + ) const filteredCollections = useMemo(() => { const normalized = deferredQuery.trim().toLowerCase() @@ -393,6 +407,25 @@ export function ResourcesPage() { +
+
+ {runtimeControlGuideCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+
- {windowsValidationSummary ? ( + {windowsValidationPlatform ? (
-
-

{windowsValidationSummary.lane}

-

- Latest public-safe package evidence was generated{' '} - {formatReleasePublishedAt(windowsValidationSummary.generated_at) || windowsValidationSummary.generated_at} - {' '}in {windowsValidationSummary.configuration} mode and passed across{' '} - {windowsValidationSummary.smoke_map_count} dedicated-family higher-dimensional training map{windowsValidationSummary.smoke_map_count === 1 ? '' : 's'}. -

-
    - {windowsValidationSummary.smoke_maps.map((map) => ( -
  • - {map.label}: {map.result} -
  • - ))} -
-
- - Open download center - - - Open operator dashboard - -
-
+ +
+ + Open download center + + + Open operator dashboard + +
) : null} @@ -586,6 +604,25 @@ export function DocsPage() { +
+
+ {runtimeControlGuideCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+ {releaseManifest.public_docs_url ? (
@@ -661,6 +698,25 @@ export function SupportPage() { ))}
+ +
+
+ {supportEscalationCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
) diff --git a/website/src/release-manifest.ts b/website/src/release-manifest.ts index be068a2..67cf1a6 100644 --- a/website/src/release-manifest.ts +++ b/website/src/release-manifest.ts @@ -6,10 +6,18 @@ type FallbackDownloadTarget = { platformKey: DownloadPlatformKey platform: string subtitle: string - href: string + configured: boolean details: string } +type FallbackViewer = { + authenticated: boolean + canDownload: boolean + plan: string | null + role: string | null + accessStatus: string | null +} + export type ReleaseManifestView = ReleaseManifestPayload['manifest'] export type ReleaseManifestPlatformView = ReleaseManifestView['platforms'][number] export type ReleaseManifestCommerceView = NonNullable @@ -21,6 +29,7 @@ export function buildFallbackReleaseManifest({ correspondingSourceUrl, openSourceRepoUrl, supportEmail, + viewer, }: { downloadTargets: readonly FallbackDownloadTarget[] publicDocsUrl: string @@ -28,6 +37,7 @@ export function buildFallbackReleaseManifest({ correspondingSourceUrl: string openSourceRepoUrl: string supportEmail: string + viewer?: FallbackViewer }): ReleaseManifestView { return { generated_at: '', @@ -36,7 +46,7 @@ export function buildFallbackReleaseManifest({ release_notes_url: releaseNotesUrl || null, corresponding_source_url: correspondingSourceUrl || null, open_source_repo_url: openSourceRepoUrl || null, - viewer: { + viewer: viewer ?? { authenticated: false, canDownload: false, plan: null, @@ -48,7 +58,7 @@ export function buildFallbackReleaseManifest({ platform: target.platform, subtitle: target.subtitle, details: target.details, - configured: Boolean(target.href), + configured: target.configured, channel: 'preview', version: null, build_id: null, @@ -56,8 +66,8 @@ export function buildFallbackReleaseManifest({ file_name: null, file_size_bytes: null, checksum_sha256: null, - download_url: target.href || null, - download_available: Boolean(target.href), + download_url: null, + download_available: false, validation_summary: getReleasePlatformValidationSummary(target.platformKey), })), } diff --git a/website/src/shared/generated/windows-package-validation-summary.json b/website/src/shared/generated/windows-package-validation-summary.json new file mode 100644 index 0000000..44aedec --- /dev/null +++ b/website/src/shared/generated/windows-package-validation-summary.json @@ -0,0 +1,20 @@ +{ + "lane": "Windows Unreal packaged validation", + "result": "passed", + "generated_at": "2026-06-23T10:27:31.6439999Z", + "configuration": "Development", + "skip_build": true, + "smoke_map_count": 2, + "smoke_maps": [ + { + "map_url": "/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining", + "label": "Magic120Cell dedicated-family training map", + "result": "passed" + }, + { + "map_url": "/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining", + "label": "MagicCube5D dedicated-family training map", + "result": "passed" + } + ] +} diff --git a/website/src/shared/package-validation.ts b/website/src/shared/package-validation.ts index ff63ef5..2b500e6 100644 --- a/website/src/shared/package-validation.ts +++ b/website/src/shared/package-validation.ts @@ -1,4 +1,5 @@ import type { DownloadPlatformKey } from '../site-routes' +import generatedWindowsValidationSummary from './generated/windows-package-validation-summary.json' export interface ReleasePlatformValidationSmokeMap { map_url: string @@ -16,10 +17,10 @@ export interface ReleasePlatformValidationSummary { smoke_maps: ReleasePlatformValidationSmokeMap[] } -const WINDOWS_VALIDATION_SUMMARY: ReleasePlatformValidationSummary = { +const WINDOWS_VALIDATION_SUMMARY_FALLBACK: ReleasePlatformValidationSummary = { lane: 'Windows Unreal packaged validation', result: 'passed', - generated_at: '2026-06-22T01:43:08.7625247Z', + generated_at: '2026-06-23T10:27:31.6439999Z', configuration: 'Development', skip_build: true, smoke_map_count: 2, @@ -37,6 +38,43 @@ const WINDOWS_VALIDATION_SUMMARY: ReleasePlatformValidationSummary = { ], } +function isValidationSmokeMap( + input: unknown, +): input is ReleasePlatformValidationSmokeMap { + return Boolean( + input + && typeof input === 'object' + && typeof (input as ReleasePlatformValidationSmokeMap).map_url === 'string' + && typeof (input as ReleasePlatformValidationSmokeMap).label === 'string' + && ((input as ReleasePlatformValidationSmokeMap).result === 'passed' + || (input as ReleasePlatformValidationSmokeMap).result === 'failed'), + ) +} + +function isValidationSummary( + input: unknown, +): input is ReleasePlatformValidationSummary { + return Boolean( + input + && typeof input === 'object' + && typeof (input as ReleasePlatformValidationSummary).lane === 'string' + && ((input as ReleasePlatformValidationSummary).result === 'passed' + || (input as ReleasePlatformValidationSummary).result === 'failed') + && typeof (input as ReleasePlatformValidationSummary).generated_at === 'string' + && typeof (input as ReleasePlatformValidationSummary).configuration === 'string' + && typeof (input as ReleasePlatformValidationSummary).skip_build === 'boolean' + && typeof (input as ReleasePlatformValidationSummary).smoke_map_count === 'number' + && Array.isArray((input as ReleasePlatformValidationSummary).smoke_maps) + && (input as ReleasePlatformValidationSummary).smoke_maps.every(isValidationSmokeMap), + ) +} + +const WINDOWS_VALIDATION_SUMMARY: ReleasePlatformValidationSummary = isValidationSummary( + generatedWindowsValidationSummary, +) + ? generatedWindowsValidationSummary + : WINDOWS_VALIDATION_SUMMARY_FALLBACK + export function getReleasePlatformValidationSummary( platformKey: DownloadPlatformKey, ): ReleasePlatformValidationSummary | null { diff --git a/website/src/site-config.ts b/website/src/site-config.ts index e141ef0..e7a4646 100644 --- a/website/src/site-config.ts +++ b/website/src/site-config.ts @@ -7,6 +7,22 @@ function readTrimmedEnv(name: string, fallback = '') { return String(env[name] || fallback).trim() } +function readBooleanEnv(...names: string[]) { + for (const name of names) { + const value = String(env[name] || '').trim().toLowerCase() + if (!value) { + continue + } + if (value === '1' || value === 'true' || value === 'yes' || value === 'on') { + return true + } + if (value === '0' || value === 'false' || value === 'no' || value === 'off') { + return false + } + } + return false +} + export const brandConfig = { brandName: 'HyperTwist', legalName: 'HyperTwist', @@ -28,7 +44,7 @@ type DownloadTarget = { platformKey: DownloadPlatformKey platform: string subtitle: string - href: string + configured: boolean details: string } @@ -82,21 +98,21 @@ export const downloadTargets = [ platformKey: 'windows', platform: 'Windows', subtitle: 'Primary shipping lane', - href: readTrimmedEnv('VITE_WINDOWS_DOWNLOAD_URL'), + configured: readBooleanEnv('VITE_WINDOWS_RELEASE_CONFIGURED', 'VITE_WINDOWS_DOWNLOAD_CONFIGURED'), details: 'Current packaged validation is strongest on the Windows Unreal lane.', }, { platformKey: 'macos', platform: 'macOS', subtitle: 'Planned distribution surface', - href: readTrimmedEnv('VITE_MAC_DOWNLOAD_URL'), + configured: readBooleanEnv('VITE_MAC_RELEASE_CONFIGURED', 'VITE_MAC_DOWNLOAD_CONFIGURED'), details: 'List a signed desktop build here when the package lane is opened.', }, { platformKey: 'linux', platform: 'Linux', subtitle: 'Operator-targeted later lane', - href: readTrimmedEnv('VITE_LINUX_DOWNLOAD_URL'), + configured: readBooleanEnv('VITE_LINUX_RELEASE_CONFIGURED', 'VITE_LINUX_DOWNLOAD_CONFIGURED'), details: 'Use for future package publication after the bounded release lane is widened.', }, ] as const satisfies readonly DownloadTarget[] @@ -109,9 +125,9 @@ export const openSourceRepoUrl = readTrimmedEnv('VITE_OPEN_SOURCE_REPO_URL') export const launchReadiness = { operatorCheckoutConfigured: Boolean(operatorCheckoutUrl), studioCheckoutConfigured: Boolean(studioCheckoutUrl), - windowsDownloadConfigured: Boolean(downloadTargets.find((target) => target.platform === 'Windows')?.href), - macDownloadConfigured: Boolean(downloadTargets.find((target) => target.platform === 'macOS')?.href), - linuxDownloadConfigured: Boolean(downloadTargets.find((target) => target.platform === 'Linux')?.href), + windowsDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'windows')?.configured), + macDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'macos')?.configured), + linuxDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'linux')?.configured), mplSourceConfigured: Boolean(mplSourceUrl), openSourceRepoConfigured: Boolean(openSourceRepoUrl), } as const diff --git a/website/src/site-data.ts b/website/src/site-data.ts index 14fb499..880bd94 100644 --- a/website/src/site-data.ts +++ b/website/src/site-data.ts @@ -1,4 +1,4 @@ -import { brandConfig, downloadTargets, mplSourceUrl, openSourceRepoUrl, publicDocsUrl, releaseNotesUrl } from './site-config' +import { brandConfig, mplSourceUrl, openSourceRepoUrl, publicDocsUrl, releaseNotesUrl } from './site-config' export const heroMetrics = [ { label: 'Current runtime center', value: 'Native Unreal' }, @@ -228,6 +228,36 @@ export const inputAndDevicePostureCards = [ }, ] as const +export const runtimeControlGuideCards = [ + { + title: 'Classic-cube desktop session', + description: 'This is the current practical control posture for the packaged classic-cube runtime.', + bullets: [ + 'Use left click or touch to drive face interaction in the live training lane.', + 'Use middle-mouse drag to orbit and scroll-wheel zoom to reframe the active cube during practice or replay review.', + 'Use the shipped fresh-attempt, hint, and hold-to-talk shortcuts when you want restart, guidance, or bounded voice interaction from the native runtime.', + ], + }, + { + title: 'Higher-dimensional desktop session', + description: 'This is the current practical control posture for the dedicated Magic120Cell and MagicCube5D runtime families.', + bullets: [ + 'Launch the dedicated-family desktop maps instead of expecting the public website to host the serious runtime lane.', + 'Use the current keyboard-driven slice, layer, or rotation interaction together with the family-owned projection and focus defaults.', + 'Treat symmetry, stereo, visibility, and focus posture as current packaged runtime ownership rather than as a fully generalized preferences suite.', + ], + }, + { + title: 'Current control boundary', + description: 'This keeps the manual useful by being explicit about what is supported now and what still belongs to a later native completion packet.', + bullets: [ + 'Use keyboard and mouse as the current strong operator-facing control lane.', + 'Do not assume a finished headset-specific VR onboarding, controller rebinding, or broad preferences UI has already landed.', + 'Treat future XR/controller/settings widening as a separate native completion packet, not as something the public website should overclaim today.', + ], + }, +] as const + export const deploymentReadinessTracks = [ { title: '1. Identity and access posture', @@ -487,6 +517,11 @@ export const resourceCollections = [ ] as const export const changelogEntries = [ + { + date: 'June 23, 2026', + title: 'Higher-dimensional package lane refreshed and public launch manual widened', + details: 'The maintained Windows higher-dimensional package helper was revalidated after the recovered editor rebuild, and the public download/support surfaces gained first-launch desktop setup plus clearer operator escalation guidance.', + }, { date: 'June 22, 2026', title: 'Public HyperTwist website, auth shell, and distribution surfaces landed', @@ -579,6 +614,45 @@ export const desktopDownloadSteps = [ 'Generate a desktop-link token in the browser dashboard so the installed app can pair to your account without exposing credentials.', ] as const +export const desktopFirstLaunchCards = [ + { + title: 'Install and verify the delivered build', + description: 'Treat the first launch as part of release validation, not as a detached post-download afterthought.', + bullets: [ + 'Confirm you installed the platform build that matches the dashboard-selected release target.', + 'Review the release notes and package-validation summary before broad internal rollout.', + 'Keep the open-source notices and corresponding-source links reachable anywhere the build is redistributed.', + ], + }, + { + title: 'Pair desktop access to the browser account', + description: 'Identity should pass through the bounded desktop-link handoff instead of through password reuse inside the runtime.', + bullets: [ + 'Open the protected dashboard and generate a desktop-link token.', + 'Use that token during first launch so the installed runtime inherits the correct account and entitlement posture.', + 'Return to the dashboard if the account, plan, or release lane changes later.', + ], + }, + { + title: 'Verify the current training lanes', + description: 'First launch should confirm the real simulator posture HyperTwist already claims publicly.', + bullets: [ + 'Open the classic-cube training lane to confirm timing, replay, and coaching posture.', + 'Open the dedicated Magic120Cell or MagicCube5D training maps to confirm the higher-dimensional packaged lane.', + 'Treat any unfinished XR/controller/preferences behavior as a later native packet, not as a first-launch surprise.', + ], + }, + { + title: 'Know when to return to browser surfaces', + description: 'The browser shell stays relevant after install because rollout and entitlement work remain there on purpose.', + bullets: [ + 'Use the dashboard for account, billing, and release-manifest review.', + 'Use support and notices pages for rollout, legal, or access questions.', + 'Use public docs and resources pages when you need the product-safe operator manual again.', + ], + }, +] as const + export const desktopReleaseSignals = [ { title: 'Package validation truth stays visible', @@ -594,6 +668,45 @@ export const desktopReleaseSignals = [ }, ] as const +export const supportEscalationCards = [ + { + title: 'Account and entitlement issues', + description: 'Use the browser/operator lane first when the problem is access rather than simulator execution.', + bullets: [ + 'Missing plan access, absent download buttons, or failed desktop-link pairing belong to the protected dashboard lane.', + 'Support should confirm auth, billing, and entitlement posture before troubleshooting simulator behavior.', + 'Do not treat account-state issues as proof that the packaged runtime itself is broken.', + ], + }, + { + title: 'Download and install issues', + description: 'Use the release lane and package evidence before widening into runtime diagnosis.', + bullets: [ + 'Confirm the selected platform, release version, and package validation summary first.', + 'Recheck notices, release notes, and rollout instructions before redistributing the build internally.', + 'Escalate with the exact target platform and release channel when the package itself is the issue.', + ], + }, + { + title: 'Runtime and training issues', + description: 'Desktop runtime issues should be described in simulator terms, not only in website terms.', + bullets: [ + 'Call out whether the issue affects classic-cube training, replay/coaching, or higher-dimensional family maps.', + 'Separate unfinished XR/controller/preferences expectations from actual regressions in the shipped keyboard/mouse lanes.', + 'Keep MagicTile browser-host behavior and dedicated-family packaged-map behavior distinguished when reporting issues.', + ], + }, + { + title: 'Rollout and compliance issues', + description: 'When the question is about public launch safety, support should keep package, pricing, and legal posture tied together.', + bullets: [ + 'Route pricing, notices, and corresponding-source questions through the public/support lane instead of improvising local answers.', + 'Treat public distribution pages as part of the shipped release posture.', + 'Keep release, payment, and legal guidance synchronized before broader operator rollout.', + ], + }, +] as const + export const companyNarrative = { mission: 'HyperTwist closes the gap between physical cubing practice, deep replay analysis, operator-grade coaching, and serious higher-dimensional puzzle study.', posture: