BLOCKER: All template-rendered pages return empty content #18

Closed
opened 2026-03-02 01:28:03 +01:00 by clawbot · 0 comments
Collaborator

Bug

All server-rendered HTML pages (index, login, sources list, source detail, edit, logs, profile) return HTTP 200 with an empty body (1 byte: newline). The web UI is completely non-functional.

Root Cause

In internal/handlers/handlers.go, parsePageTemplate() uses:

func parsePageTemplate(pageFile string) *template.Template {
    return template.Must(
        template.ParseFS(templates.Templates, "htmlheader.html", "navbar.html", "base.html", pageFile),
    )
}

Go's template.ParseFS names the resulting template set after the first file parsed (htmlheader.html). When tmpl.Execute(w, data) is called in renderTemplate(), it executes the root template — which is htmlheader.html. Since that file consists entirely of a {{define "htmlheader"}}...{{end}} block, the root template is empty.

The page-specific templates (e.g., source_detail.html) have:

{{template "base" .}}
{{define "title"}}...{{end}}
{{define "content"}}...{{end}}

The {{template "base" .}} invocation never executes because Execute() runs the (empty) root template, not the page template.

Evidence

Testing with curl:

$ curl -s http://localhost:9999/ | wc -c
0
$ curl -s http://localhost:9999/pages/login | wc -c
0

The healthcheck (/.well-known/healthcheck) still works because it uses respondJSON() directly, not template rendering.

Fix

Two options:

Option A (simpler): Change renderTemplate() to use ExecuteTemplate with the page template name:

if err := tmpl.ExecuteTemplate(w, pageFile, m); err != nil {

But this requires page templates to use the file's own name rather than {{template "base" .}}.

Option B (recommended): Restructure so the base template is the root. Parse the page file first so it becomes the root template:

func parsePageTemplate(pageFile string) *template.Template {
    return template.Must(
        template.ParseFS(templates.Templates, pageFile, "base.html", "htmlheader.html", "navbar.html"),
    )
}

Since page templates start with {{template "base" .}} (a top-level action), executing the root template will invoke the base template, which in turn uses the defined content and title blocks.

Impact

The entire web management UI is blank. Users cannot see or interact with any page. Only the JSON healthcheck endpoint works. This blocks the 1.0 release.

## Bug All server-rendered HTML pages (index, login, sources list, source detail, edit, logs, profile) return HTTP 200 with an empty body (1 byte: newline). The web UI is completely non-functional. ## Root Cause In `internal/handlers/handlers.go`, `parsePageTemplate()` uses: ```go func parsePageTemplate(pageFile string) *template.Template { return template.Must( template.ParseFS(templates.Templates, "htmlheader.html", "navbar.html", "base.html", pageFile), ) } ``` Go's `template.ParseFS` names the resulting template set after the first file parsed (`htmlheader.html`). When `tmpl.Execute(w, data)` is called in `renderTemplate()`, it executes the root template — which is `htmlheader.html`. Since that file consists entirely of a `{{define "htmlheader"}}...{{end}}` block, the root template is empty. The page-specific templates (e.g., `source_detail.html`) have: ``` {{template "base" .}} {{define "title"}}...{{end}} {{define "content"}}...{{end}} ``` The `{{template "base" .}}` invocation never executes because `Execute()` runs the (empty) root template, not the page template. ## Evidence Testing with curl: ``` $ curl -s http://localhost:9999/ | wc -c 0 $ curl -s http://localhost:9999/pages/login | wc -c 0 ``` The healthcheck (`/.well-known/healthcheck`) still works because it uses `respondJSON()` directly, not template rendering. ## Fix Two options: **Option A** (simpler): Change `renderTemplate()` to use `ExecuteTemplate` with the page template name: ```go if err := tmpl.ExecuteTemplate(w, pageFile, m); err != nil { ``` But this requires page templates to use the file's own name rather than `{{template "base" .}}`. **Option B** (recommended): Restructure so the base template is the root. Parse the page file first so it becomes the root template: ```go func parsePageTemplate(pageFile string) *template.Template { return template.Must( template.ParseFS(templates.Templates, pageFile, "base.html", "htmlheader.html", "navbar.html"), ) } ``` Since page templates start with `{{template "base" .}}` (a top-level action), executing the root template will invoke the base template, which in turn uses the defined `content` and `title` blocks. ## Impact The entire web management UI is blank. Users cannot see or interact with any page. Only the JSON healthcheck endpoint works. This blocks the 1.0 release.
clawbot added the
bot
label 2026-03-02 01:28:03 +01:00
sneak closed this issue 2026-03-04 01:19:43 +01:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: sneak/webhooker#18
No description provided.