From dd017288e7eb64dee17913073e37ad890b9dea2b Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 8 Feb 2026 23:03:49 +0000 Subject: [PATCH] fix(tray-apps): SPA routing, TypeScript fixes, and deferred onboarding - Add spaHandler() to both BugSETI and Core IDE for Angular client-side routing (AssetFileServerFS doesn't fallback to index.html) - Fix jellyfin.component.ts sanitizer initialization order (both apps) - Fix chat.component.ts Event/KeyboardEvent type mismatch - Defer onboarding window to ApplicationStarted event hook Co-Authored-By: Virgil --- .../src/app/jellyfin/jellyfin.component.ts | 6 ++- cmd/bugseti/main.go | 37 ++++++++++++++++--- .../frontend/src/app/chat/chat.component.ts | 2 +- .../src/app/jellyfin/jellyfin.component.ts | 6 ++- cmd/core-ide/main.go | 19 +++++++++- 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/cmd/bugseti/frontend/src/app/jellyfin/jellyfin.component.ts b/cmd/bugseti/frontend/src/app/jellyfin/jellyfin.component.ts index 0f7c8382..95801067 100644 --- a/cmd/bugseti/frontend/src/app/jellyfin/jellyfin.component.ts +++ b/cmd/bugseti/frontend/src/app/jellyfin/jellyfin.component.ts @@ -145,10 +145,12 @@ export class JellyfinComponent { apiKey = ''; mediaSourceId = ''; - safeWebUrl: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl('https://media.lthn.ai/web/index.html'); + safeWebUrl!: SafeResourceUrl; streamUrl = ''; - constructor(private sanitizer: DomSanitizer) {} + constructor(private sanitizer: DomSanitizer) { + this.safeWebUrl = this.sanitizer.bypassSecurityTrustResourceUrl('https://media.lthn.ai/web/index.html'); + } load(): void { const base = this.normalizeBase(this.serverUrl); diff --git a/cmd/bugseti/main.go b/cmd/bugseti/main.go index 369cc70f..4cd5dcd9 100644 --- a/cmd/bugseti/main.go +++ b/cmd/bugseti/main.go @@ -12,12 +12,15 @@ import ( "embed" "io/fs" "log" + "net/http" "runtime" + "strings" "github.com/host-uk/core/cmd/bugseti/icons" "github.com/host-uk/core/internal/bugseti" "github.com/host-uk/core/internal/bugseti/updater" "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" ) //go:embed all:frontend/dist/bugseti/browser @@ -80,7 +83,7 @@ func main() { Description: "Distributed Bug Fixing - like SETI@home but for code", Services: services, Assets: application.AssetOptions{ - Handler: application.AssetFileServerFS(staticAssets), + Handler: spaHandler(staticAssets), }, Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory, @@ -236,9 +239,31 @@ func setupSystemTray(app *application.App, fetcher *bugseti.FetcherService, queu systray.SetMenu(trayMenu) - // Check if onboarding needed - if !config.IsOnboarded() { - onboardingWindow.Show() - onboardingWindow.Focus() - } + // Check if onboarding needed (deferred until app is running) + app.Event.RegisterApplicationEventHook(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + if !config.IsOnboarded() { + onboardingWindow.Show() + onboardingWindow.Focus() + } + }) +} + +// spaHandler wraps an fs.FS to serve static files with SPA fallback. +// If the requested path doesn't match a real file, it serves index.html +// so Angular's client-side router can handle the route. +func spaHandler(fsys fs.FS) http.Handler { + fileServer := http.FileServer(http.FS(fsys)) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + if path == "" { + path = "index.html" + } + + // Check if the file exists + if _, err := fs.Stat(fsys, path); err != nil { + // File doesn't exist — serve index.html for SPA routing + r.URL.Path = "/" + } + fileServer.ServeHTTP(w, r) + }) } diff --git a/cmd/core-ide/frontend/src/app/chat/chat.component.ts b/cmd/core-ide/frontend/src/app/chat/chat.component.ts index ac6ca837..c00941db 100644 --- a/cmd/core-ide/frontend/src/app/chat/chat.component.ts +++ b/cmd/core-ide/frontend/src/app/chat/chat.component.ts @@ -51,7 +51,7 @@ import { Subscription } from 'rxjs'; diff --git a/cmd/core-ide/frontend/src/app/jellyfin/jellyfin.component.ts b/cmd/core-ide/frontend/src/app/jellyfin/jellyfin.component.ts index 29242321..bc75942b 100644 --- a/cmd/core-ide/frontend/src/app/jellyfin/jellyfin.component.ts +++ b/cmd/core-ide/frontend/src/app/jellyfin/jellyfin.component.ts @@ -133,10 +133,12 @@ export class JellyfinComponent { apiKey = ''; mediaSourceId = ''; - safeWebUrl: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl('https://media.lthn.ai/web/index.html'); + safeWebUrl!: SafeResourceUrl; streamUrl = ''; - constructor(private sanitizer: DomSanitizer) {} + constructor(private sanitizer: DomSanitizer) { + this.safeWebUrl = this.sanitizer.bypassSecurityTrustResourceUrl('https://media.lthn.ai/web/index.html'); + } load(): void { const base = this.normalizeBase(this.serverUrl); diff --git a/cmd/core-ide/main.go b/cmd/core-ide/main.go index f9efb9fe..18bfa942 100644 --- a/cmd/core-ide/main.go +++ b/cmd/core-ide/main.go @@ -9,7 +9,9 @@ import ( "embed" "io/fs" "log" + "net/http" "runtime" + "strings" "github.com/host-uk/core/cmd/core-ide/icons" "github.com/host-uk/core/pkg/mcp/ide" @@ -50,7 +52,7 @@ func main() { application.NewService(buildService), }, Assets: application.AssetOptions{ - Handler: application.AssetFileServerFS(staticAssets), + Handler: spaHandler(staticAssets), }, Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory, @@ -149,3 +151,18 @@ func setupSystemTray(app *application.App, ideService *IDEService) { systray.SetMenu(trayMenu) } + +// spaHandler wraps an fs.FS to serve static files with SPA fallback. +func spaHandler(fsys fs.FS) http.Handler { + fileServer := http.FileServer(http.FS(fsys)) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + if path == "" { + path = "index.html" + } + if _, err := fs.Stat(fsys, path); err != nil { + r.URL.Path = "/" + } + fileServer.ServeHTTP(w, r) + }) +}