diff --git a/cmd/mavweb/main.go b/cmd/mavweb/main.go index 9d8368c..e6c8c0c 100644 --- a/cmd/mavweb/main.go +++ b/cmd/mavweb/main.go @@ -75,6 +75,14 @@ var morningHTML string //go:embed events.html var eventsHTML string +// shellHTML — the shell partial every page is wrapped in: "shellTop", the +// "sidebar" it calls, and "shellBottom". It used to be two Go string constants +// with the sidebar assembled by a strings.Builder, which is the one piece of +// markup that was still concatenated in Go. +// +//go:embed shell.html +var shellHTML string + // ── Ethos Workstation Shell ── // // Two template pieces that wrap every page: @@ -135,74 +143,40 @@ var sidebarSections = []struct { }, } -func sidebarActive(url, key string, activeKey string) string { - if key == activeKey { - return `class="active"` - } - return "" -} - -// sidebarHTML renders the sidebar navigation given the active page key. -func sidebarHTML(active string) template.HTML { - var b strings.Builder - for _, sec := range sidebarSections { - b.WriteString(``) - } - return template.HTML(b.String()) -} - -// pageIcon returns an ethos-icons.svg reference for the given page. +// pageIcon returns the ethos-icons.svg symbol id for the given page. The +// sidebar template wraps it in the reference. func pageIcon(key string) string { switch key { case "dash": - return `` + return "i-grid" case "history": - return `` + return "i-clock" case "trace": - return `` + return "i-wave" case "notifications": - return `` + return "i-bell" case "tasks": - return `` + return "i-grid" case "reminders": - return `` + return "i-calendar" case "routines": - return `` + return "i-repeat" case "morning": - return `` + return "i-calendar" case "chat": - return `` + return "i-message" case "voice": - return `` + return "i-mic" case "ecosystem": - return `` + return "i-grid" case "tools": - return `` + return "i-settings" case "models": - return `` + return "i-wave" case "passkey": - return `` + return "i-lock" default: - return `` + return "i-search" } } @@ -242,63 +216,12 @@ func pageTitle(key string) string { } } -// shellTopHTML opens the shell and renders the top bar + sidebar. -// Usage: {{template "shellTop" ""}} -const shellTopHTML = `{{define "shellTop"}} - - - -maven · {{pageTitle .}} - -
-
- -
-
- - Search - Ctrl+/ -
- - - - -
-
-
- -
-{{end}}` - -// shellBottomHTML closes the content area, inspector, and shell. -// Usage: {{template "shellBottom"}} -const shellBottomHTML = `{{define "shellBottom"}} -
- -
-
- -{{end}}` - // shellFuncs returns the FuncMap shared by every server-rendered page template. func shellFuncs() template.FuncMap { return template.FuncMap{ - "pageTitle": pageTitle, - "sidebarHTML": sidebarHTML, + "pageTitle": pageTitle, + "pageIcon": pageIcon, + "sidebarSections": func() any { return sidebarSections }, "ago": func(t time.Time) string { if t.IsZero() { return "never" @@ -313,21 +236,21 @@ func shellFuncs() template.FuncMap { // a small fetch loop refreshes the tables in place. html/template escapes the // user text in facts/nudges. Read-only: browses the append-only store via // CoreAPI, never writes — the store IS the audit trail, this just shows it. -var dashTmpl = template.Must(template.New("dash").Funcs(shellFuncs()).Parse(shellTopHTML + dashHTML + shellBottomHTML)) +var dashTmpl = template.Must(template.New("dash").Funcs(shellFuncs()).Parse(shellHTML + dashHTML)) // ecosystemTmpl — read-only view of the Nexus/Praxis/Hexis siblings, whose only // human surface is here (they ship no web UI of their own). -var ecosystemTmpl = template.Must(template.New("ecosystem").Funcs(shellFuncs()).Parse(shellTopHTML + ecosystemHTML + shellBottomHTML)) +var ecosystemTmpl = template.Must(template.New("ecosystem").Funcs(shellFuncs()).Parse(shellHTML + ecosystemHTML)) // eventsTmpl — the unified intake journal (Vikunja #283), read-only. Same // shape as trace.html and morning.html: server-rendered, refreshed on reload. -var eventsTmpl = template.Must(template.New("events").Funcs(shellFuncs()).Parse(shellTopHTML + eventsHTML + shellBottomHTML)) +var eventsTmpl = template.Must(template.New("events").Funcs(shellFuncs()).Parse(shellHTML + eventsHTML)) // morningTmpl — read-only view of today's checklist state per configured // morning routine (internal/morning). Same shape as trace.html: a plain // server-rendered page, refreshed on reload — no live-update loop, since // checklist state changes on the scale of minutes, not seconds. -var morningTmpl = template.Must(template.New("morning").Funcs(shellFuncs()).Parse(shellTopHTML + morningHTML + shellBottomHTML)) +var morningTmpl = template.Must(template.New("morning").Funcs(shellFuncs()).Parse(shellHTML + morningHTML)) func noCache(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -747,7 +670,7 @@ var toolsTmpl = template.Must(template.New("tools").Funcs(func() template.FuncMa m := shellFuncs() m["join"] = strings.Join return m -}()).Parse(shellTopHTML + toolsHTML + shellBottomHTML)) +}()).Parse(shellHTML + toolsHTML)) const toolsHTML = `{{template "shellTop" "tools"}}

Tools

@@ -835,19 +758,19 @@ const routinesHTML = `{{template "shellTop" "routines"}} {{template "shellBottom"}}` -var historyTmpl = template.Must(template.New("history").Funcs(shellFuncs()).Parse(shellTopHTML + historyHTML + shellBottomHTML)) +var historyTmpl = template.Must(template.New("history").Funcs(shellFuncs()).Parse(shellHTML + historyHTML)) -var notificationsTmpl = template.Must(template.New("notifications").Funcs(shellFuncs()).Parse(shellTopHTML + notificationsHTML + shellBottomHTML)) +var notificationsTmpl = template.Must(template.New("notifications").Funcs(shellFuncs()).Parse(shellHTML + notificationsHTML)) -var remindersTmpl = template.Must(template.New("reminders").Funcs(shellFuncs()).Parse(shellTopHTML + remindersHTML + shellBottomHTML)) +var remindersTmpl = template.Must(template.New("reminders").Funcs(shellFuncs()).Parse(shellHTML + remindersHTML)) -var passkeyTmpl = template.Must(template.New("passkey").Funcs(shellFuncs()).Parse(shellTopHTML + passkeyPageHTML + shellBottomHTML)) +var passkeyTmpl = template.Must(template.New("passkey").Funcs(shellFuncs()).Parse(shellHTML + passkeyPageHTML)) -var voiceTmpl = template.Must(template.New("voice").Funcs(shellFuncs()).Parse(shellTopHTML + voiceHTML + shellBottomHTML)) +var voiceTmpl = template.Must(template.New("voice").Funcs(shellFuncs()).Parse(shellHTML + voiceHTML)) -var tasksTmpl = template.Must(template.New("tasks").Funcs(shellFuncs()).Parse(shellTopHTML + tasksHTML + shellBottomHTML)) +var tasksTmpl = template.Must(template.New("tasks").Funcs(shellFuncs()).Parse(shellHTML + tasksHTML)) -var routinesTmpl = template.Must(template.New("routines").Funcs(shellFuncs()).Parse(shellTopHTML + routinesHTML + shellBottomHTML)) +var routinesTmpl = template.Must(template.New("routines").Funcs(shellFuncs()).Parse(shellHTML + routinesHTML)) var traceTmpl = template.Must(template.New("trace").Funcs(func() template.FuncMap { m := shellFuncs() @@ -859,7 +782,7 @@ var traceTmpl = template.Must(template.New("trace").Funcs(func() template.FuncMa } m["join"] = strings.Join return m -}()).Parse(shellTopHTML + traceHTML + shellBottomHTML)) +}()).Parse(shellHTML + traceHTML)) func handleHistory(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) { if core == nil { @@ -1649,7 +1572,7 @@ func handlePTT(w http.ResponseWriter, r *http.Request, voiceAddr string, session // chatTmpl — plain text conversation interface. No JS: form POSTs to /api/chat // and the handler redirects back to /chat with the response. -var chatTmpl = template.Must(template.New("chat").Funcs(shellFuncs()).Parse(shellTopHTML + chatPageHTML + shellBottomHTML)) +var chatTmpl = template.Must(template.New("chat").Funcs(shellFuncs()).Parse(shellHTML + chatPageHTML)) const chatPageHTML = `{{template "shellTop" "chat"}}

Chat

diff --git a/cmd/mavweb/models.go b/cmd/mavweb/models.go index 28b598a..ded6af1 100644 --- a/cmd/mavweb/models.go +++ b/cmd/mavweb/models.go @@ -31,7 +31,7 @@ type modelController interface { SwapModel(ctx context.Context, req ipc.SwapModelReq) (ipc.SwapModelResp, error) } -var modelsTmpl = template.Must(template.New("models").Funcs(shellFuncs()).Parse(shellTopHTML + modelsHTML + shellBottomHTML)) +var modelsTmpl = template.Must(template.New("models").Funcs(shellFuncs()).Parse(shellHTML + modelsHTML)) const modelsHTML = `{{template "shellTop" "models"}}

Resident model

diff --git a/cmd/mavweb/shell.html b/cmd/mavweb/shell.html new file mode 100644 index 0000000..6321ae8 --- /dev/null +++ b/cmd/mavweb/shell.html @@ -0,0 +1,49 @@ +{{define "shellTop"}} + + + +maven · {{pageTitle .}} + +
+
+ +
+
+ + Search + Ctrl+/ +
+ + + + +
+
+
+ +
+{{end}}{{/* sidebar — the section list, with the active page's link marked. The dot + argument is the page key the page passed to shellTop. */}}{{define "sidebar"}}{{$active := .}}{{range sidebarSections}} +{{end}}{{end}}{{define "shellBottom"}} +
+ +
+
+ +{{end}} \ No newline at end of file