diff --git a/server/app.go b/server/app.go index 37c7be3..b5fac66 100644 --- a/server/app.go +++ b/server/app.go @@ -89,6 +89,8 @@ func (app *application) routes() http.Handler { mux.HandleFunc("/playground/", app.servePlayground) mux.HandleFunc("/view/gltf", app.servePage("gltf.html")) mux.HandleFunc("/view/gltf/", app.servePage("gltf.html")) + mux.HandleFunc("/sculpt", app.servePage("assets/sculpt/index.html")) + mux.HandleFunc("/sculpt/", app.servePage("assets/sculpt/index.html")) mux.HandleFunc("/view/sculpt", app.servePage("assets/sculpt/index.html")) mux.HandleFunc("/view/sculpt/", app.servePage("assets/sculpt/index.html")) mux.HandleFunc("/", app.serveHome) diff --git a/server/app_test.go b/server/app_test.go index 5a0bc13..b41dedd 100644 --- a/server/app_test.go +++ b/server/app_test.go @@ -217,6 +217,46 @@ func TestSavedPlaygroundPageRoute(t *testing.T) { } } +func TestSculptRoutesAndAssets(t *testing.T) { + routes := testApplication(t).routes() + for _, path := range []string{"/sculpt", "/sculpt/", "/view/sculpt", "/view/sculpt/", "/assets/sculpt/app.js", "/assets/sculpt/styles.css"} { + t.Run(path, func(t *testing.T) { + response := httptest.NewRecorder() + routes.ServeHTTP(response, httptest.NewRequest(http.MethodGet, path, nil)) + if response.Code != http.StatusOK { + t.Fatalf("status = %d", response.Code) + } + for name, expected := range map[string]string{ + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + } { + if got := response.Header().Get(name); got != expected { + t.Fatalf("%s = %q, want %q", name, got, expected) + } + } + switch path { + case "/assets/sculpt/app.js", "/assets/sculpt/styles.css": + expected := "text/javascript; charset=utf-8" + if filepath.Ext(path) == ".css" { + expected = "text/css; charset=utf-8" + } + if response.Header().Get("Content-Type") != expected || response.Body.Len() == 0 { + t.Fatalf("missing asset or wrong content type: %q", response.Header().Get("Content-Type")) + } + default: + if got := response.Header().Get("Content-Type"); got != "text/html; charset=utf-8" { + t.Fatalf("content type = %q", got) + } + for _, content := range []string{"Sculpt — Yawn", `src="/assets/sculpt/app.js"`, `href="/assets/sculpt/styles.css"`, `"@yawn/core": "/pkg/core.js"`} { + if !bytes.Contains(response.Body.Bytes(), []byte(content)) { + t.Fatalf("sculpt page missing %q", content) + } + } + } + }) + } +} + func TestGltfViewerPageRoute(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/view/gltf", nil) response := httptest.NewRecorder() diff --git a/server/web/assets/sculpt/README.md b/server/web/assets/sculpt/README.md index e4fb8e1..fb745e2 100644 --- a/server/web/assets/sculpt/README.md +++ b/server/web/assets/sculpt/README.md @@ -1,6 +1,6 @@ # Yawn Sculpt -Open `/view/sculpt` on the Yawn server (`npm run server`). All example code lives here; the only integration outside this folder is the embedded-page route in `server/app.go`. Rendering imports **only `@yawn/core`**. No handles, third-party scene graph, or remote document storage. +Open `/sculpt` on the Yawn server (`npm run server`); `/view/sculpt` remains a compatibility alias. The production Docker image embeds this example, so the existing main-branch deployment poller publishes it at `/sculpt` with each deployment. All example code lives here; the only integration outside this folder is the embedded-page route in `server/app.go`. Rendering imports **only `@yawn/core`**. No handles, third-party scene graph, or remote document storage. ## Workflow diff --git a/server/web/assets/sculpt/browser-check.js b/server/web/assets/sculpt/browser-check.js index 9e9d8d8..5c36e90 100644 --- a/server/web/assets/sculpt/browser-check.js +++ b/server/web/assets/sculpt/browser-check.js @@ -1,4 +1,4 @@ -// Run explicitly in a fresh /view/sculpt page: +// Run explicitly in a fresh /sculpt page: // await (await import('/assets/sculpt/browser-check.js')).check() // Synthetic input checks logic, not physical pen feel. Hooks are always restored. import { Renderer } from "./renderer.js";