diff --git a/app/ui/app.go b/app/ui/app.go index d73df2c02..f84dd8d2e 100644 --- a/app/ui/app.go +++ b/app/ui/app.go @@ -7,7 +7,9 @@ import ( "embed" "errors" "io/fs" + "mime" "net/http" + "path/filepath" "strings" "time" ) @@ -15,6 +17,13 @@ import ( //go:embed app/dist var appFS embed.FS +func init() { + mime.AddExtensionType(".js", "application/javascript; charset=utf-8") + mime.AddExtensionType(".css", "text/css; charset=utf-8") + mime.AddExtensionType(".woff2", "font/woff2") + mime.AddExtensionType(".svg", "image/svg+xml") +} + // appHandler returns an HTTP handler that serves the React SPA. // It tries to serve real files first, then falls back to index.html for React Router. func (s *Server) appHandler() http.Handler { @@ -24,11 +33,19 @@ func (s *Server) appHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := strings.TrimPrefix(r.URL.Path, "/") - if _, err := fsys.Open(p); err == nil { - // Serve the file directly + + if file, err := fsys.Open(p); err == nil { + file.Close() + + // Ensure proper Content-Type headers + if contentType := mime.TypeByExtension(filepath.Ext(p)); contentType != "" { + w.Header().Set("Content-Type", contentType) + } + fileServer.ServeHTTP(w, r) return } + // Fallback – serve index.html for unknown paths so React Router works data, err := fs.ReadFile(fsys, "index.html") if err != nil { @@ -39,6 +56,8 @@ func (s *Server) appHandler() http.Handler { } return } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") http.ServeContent(w, r, "index.html", time.Time{}, bytes.NewReader(data)) }) } diff --git a/app/ui/app_test.go b/app/ui/app_test.go new file mode 100644 index 000000000..fedacb3ac --- /dev/null +++ b/app/ui/app_test.go @@ -0,0 +1,39 @@ +//go:build windows || darwin + +package ui + +import ( + "io/fs" + "strings" + "testing" +) + +// TestEmbeddedAssets verifies that the correct UI assets are embedded. +// This test will FAIL THE BUILD if wrong files are embedded. +func TestEmbeddedAssets(t *testing.T) { + fsys, err := fs.Sub(appFS, "app/dist") + if err != nil { + t.Fatal("app/dist not found in embedded filesystem - UI not built") + } + + data, err := fs.ReadFile(fsys, "index.html") + if err != nil { + t.Fatal("index.html not found - run 'go generate' first") + } + + html := string(data) + + if strings.Contains(html, "/src/main.tsx") { + t.Fatal("Wrong index.html embedded: has /src/main.tsx (dev paths). The UI was not built. Run 'npm run build' first.") + } + + if !strings.Contains(html, "/assets/index-") { + t.Fatal("Wrong index.html embedded: missing /assets/index-* (production paths). The UI was not built correctly.") + } + + if _, err := fsys.Open("assets"); err != nil { + t.Fatal("assets/ directory not found - UI build incomplete") + } + + t.Log("Embedded assets verified - UI built correctly") +}