diff --git a/Makefile b/Makefile index f06fb61a..dcfb0a94 100644 --- a/Makefile +++ b/Makefile @@ -47,9 +47,34 @@ help: @echo " ./release v0.3.0 - Tag, push, and release (notes auto-generated from commits)" # Run unit tests locally (fast, no containers) -test: +test: lint-rendering go test ./internal/... -v -short +# Rendering-ownership guard (AGENTS.md → Rendering ownership): templ renders +# UI markup, JS orchestrates. Fails when a web/src file builds more HTML +# template literals than its recorded baseline (web/src/rendering-allowlist.txt), +# or when a .templ file gains an inline + } @@ -273,7 +227,7 @@ templ LibraryList(user User, libraries []LibraryData, users []User) { ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -233,7 +233,7 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(library.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 247, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 201, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -251,7 +251,7 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(library.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 249, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 203, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -273,7 +273,7 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(library.TypeName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 254, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 208, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -295,7 +295,7 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(library.FolderCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 259, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 213, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -313,7 +313,7 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue("/api/libraries/" + library.ID + "/scan") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 265, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 219, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) if templ_7745c5c3_Err != nil { @@ -334,13 +334,13 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(library.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 275, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 229, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" onclick=\"toggleLibraryPanel(this)\" class=\"btn btn-secondary text-xs px-3 py-1.5\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" @click=\"toggleLibraryPanel($el)\" class=\"btn btn-secondary text-xs px-3 py-1.5\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -355,7 +355,7 @@ func LibraryList(user User, libraries []LibraryData, users []User) templ.Compone var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue("library-panel-" + library.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 284, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 238, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) if templ_7745c5c3_Err != nil { @@ -430,7 +430,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(folder.FolderPath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 306, Col: 99} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 260, Col: 99} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -443,7 +443,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/library/" + libraryID + "/folders") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 309, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 263, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) if templ_7745c5c3_Err != nil { @@ -456,7 +456,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(hxValsFolderDelete(folder.FolderPath)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 310, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 264, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) if templ_7745c5c3_Err != nil { @@ -469,7 +469,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue("#library-panel-" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 311, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 265, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17) if templ_7745c5c3_Err != nil { @@ -500,7 +500,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/library/" + libraryID + "/folders") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 321, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 275, Col: 80} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18) if templ_7745c5c3_Err != nil { @@ -513,7 +513,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.ResolveAttributeValue("#library-panel-" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 321, Col: 124} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 275, Col: 124} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var19) if templ_7745c5c3_Err != nil { @@ -526,7 +526,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue("folder-input-" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 325, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 279, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20) if templ_7745c5c3_Err != nil { @@ -539,7 +539,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 332, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 286, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21) if templ_7745c5c3_Err != nil { @@ -552,13 +552,13 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.ResolveAttributeValue("folder-input-" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 333, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 287, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\" onclick=\"openFolderBrowser(this.dataset.targetInput, this.dataset.libraryId)\" class=\"btn btn-secondary text-sm shrink-0\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\" @click=\"openFolderBrowser($el.dataset.targetInput, $el.dataset.libraryId)\" class=\"btn btn-secondary text-sm shrink-0\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -609,7 +609,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/library/" + libraryID + "/visibility") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 362, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 316, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23) if templ_7745c5c3_Err != nil { @@ -622,7 +622,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(`{"user_id": "` + u.ID + `"}`) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 363, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 317, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24) if templ_7745c5c3_Err != nil { @@ -635,7 +635,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue("#library-panel-" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 365, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 319, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25) if templ_7745c5c3_Err != nil { @@ -648,7 +648,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 368, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 322, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -661,7 +661,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 369, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 323, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -689,7 +689,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var28 templ.SafeURL templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs("/admin/libraries/" + libraryID + "/issues") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 378, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 332, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -716,7 +716,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(issueCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 383, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 337, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -743,7 +743,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue(libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 392, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 346, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30) if templ_7745c5c3_Err != nil { @@ -756,7 +756,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.ResolveAttributeValue(library.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 393, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 347, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31) if templ_7745c5c3_Err != nil { @@ -769,13 +769,13 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var32 string templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(library.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 394, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 348, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\" onclick=\"openEditModal(this.dataset.editId, this.dataset.editName, this.dataset.editDesc)\" class=\"btn btn-secondary text-sm\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\" @click=\"openEditModal($el.dataset.editId, $el.dataset.editName, $el.dataset.editDesc)\" class=\"btn btn-secondary text-sm\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -790,7 +790,7 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var33 string templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 402, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 356, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) if templ_7745c5c3_Err != nil { @@ -803,13 +803,13 @@ func LibraryPanel(user User, libraryID string, library LibraryData, folders []Fo var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(library.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 403, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 357, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" onclick=\"openDeleteModal(this.dataset.deleteId, this.dataset.deleteName)\" class=\"btn btn-danger text-sm\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" @click=\"openDeleteModal($el.dataset.deleteId, $el.dataset.deleteName)\" class=\"btn btn-danger text-sm\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -861,7 +861,7 @@ func FolderBrowserContent(currentPath string, parentPath string, entries []DirEn var templ_7745c5c3_Var36 string templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(currentPath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 418, Col: 89} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 372, Col: 89} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { @@ -879,7 +879,7 @@ func FolderBrowserContent(currentPath string, parentPath string, entries []DirEn var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/library/browse?path=" + parentPath + "&target_input=" + targetInput + "&library_id=" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 424, Col: 117} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 378, Col: 117} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37) if templ_7745c5c3_Err != nil { @@ -910,7 +910,7 @@ func FolderBrowserContent(currentPath string, parentPath string, entries []DirEn var templ_7745c5c3_Var38 string templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/library/browse?path=" + entry.Path + "&target_input=" + targetInput + "&library_id=" + libraryID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 437, Col: 118} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 391, Col: 118} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var38) if templ_7745c5c3_Err != nil { @@ -931,7 +931,7 @@ func FolderBrowserContent(currentPath string, parentPath string, entries []DirEn var templ_7745c5c3_Var39 string templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 442, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 396, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { @@ -949,7 +949,7 @@ func FolderBrowserContent(currentPath string, parentPath string, entries []DirEn var templ_7745c5c3_Var40 string templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(targetInput) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 449, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 403, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40) if templ_7745c5c3_Err != nil { @@ -962,7 +962,7 @@ func FolderBrowserContent(currentPath string, parentPath string, entries []DirEn var templ_7745c5c3_Var41 string templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(currentPath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 450, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 404, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41) if templ_7745c5c3_Err != nil { diff --git a/web/src/admin.ts b/web/src/admin.ts index 5fd2c9d3..b4d74c7c 100644 --- a/web/src/admin.ts +++ b/web/src/admin.ts @@ -450,10 +450,86 @@ export { stopScanStatusPolling, }; +// Library admin page helpers (rendering-ownership: these orchestrate htmx +// swaps and modal visibility on server-rendered DOM; they render nothing). +function toggleLibraryPanel(btn: HTMLElement): void { + const libId = btn.dataset.libId || ""; + const panel = document.getElementById(`library-panel-${libId}`); + if (!panel) return; + if (panel.innerHTML.trim() !== "") { + panel.innerHTML = ""; + } else { + window.htmx.ajax("GET", `/admin/library/${libId}/panel`, { + target: `#library-panel-${libId}`, + swap: "innerHTML", + }); + } +} + +function openEditModal(id: string, name: string, description: string): void { + const form = document.getElementById("edit-library-form"); + if (!form) return; + form.setAttribute("hx-put", `/admin/library/${id}`); + const nameInput = document.getElementById("edit-library-name"); + if (nameInput) (nameInput as HTMLInputElement).value = name; + const descInput = document.getElementById("edit-library-desc"); + if (descInput) (descInput as HTMLInputElement).value = description; + window.htmx.process(form); + document.getElementById("edit-library-modal")?.classList.remove("hidden"); +} + +function openDeleteModal(id: string, name: string): void { + const btn = document.getElementById("delete-library-confirm"); + if (!btn) return; + btn.setAttribute("hx-delete", `/admin/library/${id}`); + btn.setAttribute("hx-target", "#libraries-container"); + btn.setAttribute("hx-swap", "innerHTML"); + const nameEl = document.getElementById("delete-library-name"); + if (nameEl) nameEl.textContent = name; + window.htmx.process(btn); + document.getElementById("delete-library-modal")?.classList.remove("hidden"); +} + +function openFolderBrowser(targetInputId: string, libraryId: string): void { + const content = document.getElementById("folder-browser-content"); + if (!content) return; + content.setAttribute("hx-get", "/admin/library/browse"); + content.setAttribute( + "hx-vals", + JSON.stringify({ target_input: targetInputId, library_id: libraryId }), + ); + content.setAttribute("hx-trigger", "load"); + window.htmx.process(content); + document.getElementById("folder-browser-modal")?.classList.remove("hidden"); +} + +// Close the delete-library modal after a successful delete: the htmx swap +// already removes the deleted row from the libraries list, but the modal +// lives outside the swapped container and would otherwise stay open. +function bindDeleteLibraryModalClose(): void { + document + .getElementById("delete-library-confirm") + ?.addEventListener("htmx:afterRequest", function (evt) { + const detail = (evt as CustomEvent).detail as { successful?: boolean }; + if (detail.successful) { + document + .getElementById("delete-library-modal") + ?.classList.add("hidden"); + } + }); +} + Alpine.data("admin", () => ({ + init() { + bindDeleteLibraryModalClose(); + }, checkActiveScans, hideScanProgress, loadWatchStatus, scanAllLibraries, stopScanStatusPolling, + openDeleteModal, + openEditModal, + openFolderBrowser, + toggleLibraryPanel, })); diff --git a/web/src/rendering-allowlist.txt b/web/src/rendering-allowlist.txt new file mode 100644 index 00000000..5dcac303 --- /dev/null +++ b/web/src/rendering-allowlist.txt @@ -0,0 +1,15 @@ +# Rendering-ownership baseline (AGENTS.md → Rendering ownership). +# Files allowed to build HTML via template literals, with their current +# site counts. `make lint-rendering` fails when a count grows above its +# baseline. Shrink counts as fragments move to templ partials — the end +# state of the triage is an empty list. The reader (web/src/reader/) is a +# sanctioned island and not counted here. +web/src/admin.ts:1 +web/src/api-explorer.ts:2 +web/src/collections.ts:1 +web/src/custom-section-builder.ts:2 +web/src/linking.ts:1 +web/src/search.ts:3 +web/src/stats.ts:6 +web/src/toast.ts:1 +web/src/unlinked_books.ts:1 diff --git a/web/src/theme.ts b/web/src/theme.ts index 59b37b89..c08ed192 100644 --- a/web/src/theme.ts +++ b/web/src/theme.ts @@ -52,7 +52,7 @@ const TOKEN_STORAGE_KEY = "token"; // auth pages survive a theme change. const applyTheme = (theme: string): void => { const setThemeClass = (el: Element): void => { - [...el.classList].forEach((c) => { + Array.from(el.classList).forEach((c) => { if (c.startsWith("theme-")) el.classList.remove(c); }); el.classList.add(`theme-${theme}`);