Skip to content

Goshtoso App Shells · v0.1.6

Console Shell

An application frame for operations consoles and HTMX products that need persistent navigation and predictable fragment lifecycle.

What the package owns

Shell-owned structure stays stable while the application supplies its brand, navigation, routes, and content.

Operations frame

HeaderPersistent identity, actions, and optional application header content.
NavigationDesktop sidebar and mobile drawer with active state and Escape recovery.
LifecycleStable main target, HTMX history, focus restoration, scroll reset, and optional navigation OOB.

Install and compose

Install
go get github.com/araihu/goshtoso-app-shells/[email protected]
Assets and composition
import (
    "net/http"

    "github.com/araihu/goshtoso/assets"
    "github.com/araihu/goshtoso/components/sidebar"
    "github.com/araihu/goshtoso-app-shells/consoleshell"
    shellassets "github.com/araihu/goshtoso-app-shells/consoleshell/assets"
)

func registerAssets(mux *http.ServeMux) {
    mux.Handle("GET /assets/", assets.Handler())
    mux.Handle("GET /consoleshell/assets/", shellassets.Handler())
}

cfg := consoleshell.Config{
    Brand: consoleshell.Brand{Name: "Operations", HomeURL: "/"},
    Navigation: consoleshell.Navigation{
        Items: []sidebar.Item{{
            ID: "runs", Label: "Runs", Href: "/runs",
        }},
    },
    Interactions: consoleshell.InteractionConfig{
        EnableHTMX: true,
        NavigationOOB: true,
        LocalRuntime: true,
    },
}
page := consoleshell.Page{
    Title: "Runs",
    Active: "runs",
    Content: runsPage(),
}
view := consoleshell.Layout(cfg, page)

Full documents and HTMX fragments

Serve Layout for ordinary browser requests. When EnableHTMX adds enhanced navigation, inspect HX-Request and serve Fragment instead; sending Layout into the main target would swap a complete document into the page.

Console Shell Fragment emits the document title and stable main region. NavigationOOB controls whether the same response also replaces the sidebar so its active state follows the new page.

HTTP response selection
func renderConsole(
    writer http.ResponseWriter,
    request *http.Request,
    cfg consoleshell.Config,
    page consoleshell.Page,
) {
    view := consoleshell.Layout(cfg, page)
    if request.Header.Get("HX-Request") == "true" {
        view = consoleshell.Fragment(cfg, page)
    }
    if err := view.Render(request.Context(), writer); err != nil {
        http.Error(writer, err.Error(), http.StatusInternalServerError)
    }
}

Consumer boundary

The shell owns the surrounding frame, responsive behavior, and lifecycle hooks. The consumer owns product vocabulary, authorization, data, navigation policy, metadata, and content.