Add welcome surface and user default surface preference
- Create welcome surface: topbar + getting-started card shown when no extension surfaces are installed. Admins see a link to Packages, non-admins see a message to contact their administrator. - Update resolveDefaultSurface priority chain: user preference → global config → first extension → /welcome - Register welcome as a core surface with authenticated auth - Final fallback is now /welcome instead of /admin, breaking the infinite back-button loop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -91,22 +91,35 @@ func (e *Engine) EnabledSurfaceIDs() []string {
|
||||
|
||||
// DefaultSurfaceRedirect returns a handler for GET / that redirects to the
|
||||
// configured default surface, falling back to the first enabled extension
|
||||
// surface, then /admin.
|
||||
// surface, then /welcome.
|
||||
func (e *Engine) DefaultSurfaceRedirect() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
target := e.resolveDefaultSurface(c.Request.Context())
|
||||
userID := c.GetString("user_id")
|
||||
target := e.resolveDefaultSurface(c.Request.Context(), userID)
|
||||
c.Redirect(http.StatusTemporaryRedirect, e.cfg.BasePath+target)
|
||||
}
|
||||
}
|
||||
|
||||
// resolveDefaultSurface returns the path to redirect to (without BasePath).
|
||||
// Priority: configured default_surface → first enabled extension surface → /admin.
|
||||
func (e *Engine) resolveDefaultSurface(ctx context.Context) string {
|
||||
// Priority: user preference → global default_surface → first enabled extension → /welcome.
|
||||
func (e *Engine) resolveDefaultSurface(ctx context.Context, userID string) string {
|
||||
if e.stores.GlobalConfig == nil || e.stores.Packages == nil {
|
||||
return "/admin"
|
||||
return "/welcome"
|
||||
}
|
||||
|
||||
// 1. Check configured default_surface
|
||||
// 1. Check user preference (default_surface in user settings)
|
||||
if userID != "" && e.stores.Users != nil {
|
||||
if user, err := e.stores.Users.GetByID(ctx, userID); err == nil && user != nil {
|
||||
if id, _ := user.Settings["default_surface"].(string); id != "" {
|
||||
if path := e.surfacePath(ctx, id); path != "" {
|
||||
return path
|
||||
}
|
||||
// User's chosen surface is missing or disabled — fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check admin-configured global default_surface
|
||||
if raw, err := e.stores.GlobalConfig.Get(ctx, "default_surface"); err == nil && raw != nil {
|
||||
if id, ok := raw["id"].(string); ok && id != "" {
|
||||
if path := e.surfacePath(ctx, id); path != "" {
|
||||
@@ -116,7 +129,7 @@ func (e *Engine) resolveDefaultSurface(ctx context.Context) string {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. First enabled extension surface
|
||||
// 3. First enabled extension surface
|
||||
surfaces, err := e.stores.Packages.ListEnabledByType(ctx, "surface")
|
||||
if err == nil {
|
||||
for _, s := range surfaces {
|
||||
@@ -129,8 +142,8 @@ func (e *Engine) resolveDefaultSurface(ctx context.Context) string {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback
|
||||
return "/admin"
|
||||
// 4. Fallback — welcome surface (no extensions installed)
|
||||
return "/welcome"
|
||||
}
|
||||
|
||||
// surfacePath returns the URL path for a surface ID, or "" if the surface
|
||||
@@ -155,9 +168,8 @@ func (e *Engine) surfacePath(ctx context.Context, id string) string {
|
||||
return "/s/" + id
|
||||
}
|
||||
|
||||
// disabledRedirect returns a handler that redirects to /admin.
|
||||
// Uses /admin (not /) to avoid a redirect loop when the default surface
|
||||
// is the one being disabled.
|
||||
// disabledRedirect returns a handler that redirects to /.
|
||||
// The DefaultSurfaceRedirect handler will resolve to the appropriate surface.
|
||||
func (e *Engine) disabledRedirect() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Redirect(http.StatusTemporaryRedirect, e.cfg.BasePath+"/admin")
|
||||
|
||||
Reference in New Issue
Block a user