From 12c2ae1d17c458f177f7c713ee3a6374720ad586 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 6 Aug 2026 01:29:08 +0400 Subject: [PATCH] mavweb: the last two pages leave Go (V-409) CLAUDE.md says every page is its own embedded .html file next to main.go, and that no page markup lives in Go. Two pages were still Go string constants: passkeyPageHTML in webauthn.go and modelsHTML in models.go. They are now passkey.html and models.html, embedded. The identifiers keep their names, so passkey_prf_test.go still reads passkeyPageHTML and still asserts on the same bytes. Both templates now build through parsePage, and Page and handleModels render through renderPage. handleEcosystem did too and now does the same. Co-Authored-By: Claude Opus 5 --- cmd/mavweb/ecosystem.go | 5 +-- cmd/mavweb/models.go | 46 +++----------------------- cmd/mavweb/models.html | 35 ++++++++++++++++++++ cmd/mavweb/passkey.html | 58 +++++++++++++++++++++++++++++++++ cmd/mavweb/webauthn.go | 72 ++++++----------------------------------- 5 files changed, 108 insertions(+), 108 deletions(-) create mode 100644 cmd/mavweb/models.html create mode 100644 cmd/mavweb/passkey.html diff --git a/cmd/mavweb/ecosystem.go b/cmd/mavweb/ecosystem.go index 77a329c..1bb1439 100644 --- a/cmd/mavweb/ecosystem.go +++ b/cmd/mavweb/ecosystem.go @@ -117,8 +117,5 @@ func handleEcosystem(w http.ResponseWriter, r *http.Request, urls ecoURLs, core d.Calls.Rows = rows } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - if err := ecosystemTmpl.Execute(w, d); err != nil { - log.Printf("ecosystem render: %v", err) - } + renderPage(w, ecosystemTmpl, d) } diff --git a/cmd/mavweb/models.go b/cmd/mavweb/models.go index ded6af1..2b38bcd 100644 --- a/cmd/mavweb/models.go +++ b/cmd/mavweb/models.go @@ -2,8 +2,8 @@ package main import ( "context" + _ "embed" "errors" - "html/template" "log" "net/http" "strconv" @@ -31,43 +31,10 @@ type modelController interface { SwapModel(ctx context.Context, req ipc.SwapModelReq) (ipc.SwapModelResp, error) } -var modelsTmpl = template.Must(template.New("models").Funcs(shellFuncs()).Parse(shellHTML + modelsHTML)) +//go:embed models.html +var modelsHTML string -const modelsHTML = `{{template "shellTop" "models"}} -

Resident model

-

swapping requires step-up — assert a passkey first. The old model is unloaded before the new one is loaded (one model fits the iGPU at a time), so turns during the load are refused and fall back to the classifier.

-

a swap is not remembered. Nothing writes it down, so the next restart of the daemon — including the one mavupdate does — comes back on phraser.model_path from the config. Make it stick by editing that.

-{{if .Msg}}
{{.Msg}}
{{end}} -{{if .Err}}
{{.Err}}
{{end}} -{{if .Off}} -
-

swap not configured

-

this core has no phraser.swap_models allowlist, so there is nothing to swap to. Add the gguf paths you allow to deploy/mavend.json and restart once.

-
-{{else}} -
-

loaded now

-
- - - - - -
model{{.Status.Model}}
file{{.Status.ModelPath}}
server{{.Status.BaseURL}}
n_ctx{{.Status.NCtx}}
n_gpu_layers{{.Status.NGpuLayers}}
-

the model name is what llama-server reports for itself, not what the config says it should be.

-
-
-

allowed models {{len .Status.Swappable}}

-{{if .Status.Swappable}}
-{{range .Status.Swappable}} -{{end}} -
file
{{.}}
- -
-{{else}}
no models allowlisted
{{end}} -
-{{end}} -{{template "shellBottom"}}` +var modelsTmpl = parsePage("models", modelsHTML, nil) type modelsPage struct { Msg string @@ -154,8 +121,5 @@ func handleModels(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, swap } } page.Status = st - w.Header().Set("Content-Type", "text/html; charset=utf-8") - if err := modelsTmpl.Execute(w, page); err != nil { - log.Printf("models render: %v", err) - } + renderPage(w, modelsTmpl, page) } diff --git a/cmd/mavweb/models.html b/cmd/mavweb/models.html new file mode 100644 index 0000000..0990b54 --- /dev/null +++ b/cmd/mavweb/models.html @@ -0,0 +1,35 @@ +{{template "shellTop" "models"}} +

Resident model

+

swapping requires step-up — assert a passkey first. The old model is unloaded before the new one is loaded (one model fits the iGPU at a time), so turns during the load are refused and fall back to the classifier.

+

a swap is not remembered. Nothing writes it down, so the next restart of the daemon — including the one mavupdate does — comes back on phraser.model_path from the config. Make it stick by editing that.

+{{if .Msg}}
{{.Msg}}
{{end}} +{{if .Err}}
{{.Err}}
{{end}} +{{if .Off}} +
+

swap not configured

+

this core has no phraser.swap_models allowlist, so there is nothing to swap to. Add the gguf paths you allow to deploy/mavend.json and restart once.

+
+{{else}} +
+

loaded now

+
+ + + + + +
model{{.Status.Model}}
file{{.Status.ModelPath}}
server{{.Status.BaseURL}}
n_ctx{{.Status.NCtx}}
n_gpu_layers{{.Status.NGpuLayers}}
+

the model name is what llama-server reports for itself, not what the config says it should be.

+
+
+

allowed models {{len .Status.Swappable}}

+{{if .Status.Swappable}}
+{{range .Status.Swappable}} +{{end}} +
file
{{.}}
+ +
+{{else}}
no models allowlisted
{{end}} +
+{{end}} +{{template "shellBottom"}} diff --git a/cmd/mavweb/passkey.html b/cmd/mavweb/passkey.html new file mode 100644 index 0000000..6ea67e0 --- /dev/null +++ b/cmd/mavweb/passkey.html @@ -0,0 +1,58 @@ +{{template "shellTop" "passkey"}} +

Passkey

+

Enroll a passkey once, then assert it to unlock destructive actions (tool enable) for a few minutes.

+
+ + + + +
+

Rewriting the cold-start key points it at the passkey you assert next. Every other enrolled passkey stops being able to unlock a cold-booted daemon.

+
+{{template "shellBottom"}} + diff --git a/cmd/mavweb/webauthn.go b/cmd/mavweb/webauthn.go index 3312c7f..f1ef4a7 100644 --- a/cmd/mavweb/webauthn.go +++ b/cmd/mavweb/webauthn.go @@ -2,6 +2,7 @@ package main import ( "context" + _ "embed" "encoding/json" "errors" "fmt" @@ -76,72 +77,17 @@ func newPasskeyHandle(cfg webauthn.Config, core ipc.CoreAPI, storePath string, s // on: assert here (bumps the daemon session to L3 for the assertion TTL), then // enable a tool on /tools within that window. func (h *PasskeyHandle) Page(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - passkeyTmpl.Execute(w, nil) + renderPage(w, passkeyTmpl, nil) } -var passkeyTmpl = parsePage("passkey", passkeyPageHTML, nil) +// passkeyPageHTML — the enrolment page's own markup, wrapped by passkeyTmpl +// with shellTop/shellBottom. It was a Go string constant, which is the one +// place page markup still lived in Go. +// +//go:embed passkey.html +var passkeyPageHTML string -// passkeyPageHTML — rendered via passkeyTmpl, which wraps it with -// shellTop/shellBottom. -const passkeyPageHTML = `{{template "shellTop" "passkey"}} -

Passkey

-

Enroll a passkey once, then assert it to unlock destructive actions (tool enable) for a few minutes.

-
- - - - -
-

Rewriting the cold-start key points it at the passkey you assert next. Every other enrolled passkey stops being able to unlock a cold-booted daemon.

-
-{{template "shellBottom"}} -` +var passkeyTmpl = parsePage("passkey", passkeyPageHTML, nil) func (h *PasskeyHandle) RegisterBegin(w http.ResponseWriter, r *http.Request) { opts, challenge, err := h.rp.CreationOptions([]byte("maven-user"), "maven user")