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 <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-08 23:03:49 +00:00
parent 5c3b70a1eb
commit dd017288e7
5 changed files with 58 additions and 12 deletions

View file

@ -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);

View file

@ -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)
})
}

View file

@ -51,7 +51,7 @@ import { Subscription } from 'rxjs';
<textarea
class="form-textarea"
[(ngModel)]="draft"
(keydown.enter)="sendMessage($event)"
(keydown.enter)="sendMessage($any($event))"
placeholder="Type a message... (Enter to send)"
rows="2"
></textarea>

View file

@ -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);

View file

@ -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)
})
}