* feat(plugins): group Apps sidebar by plugin manifest category Implements the plugin SDK's documented PluginManifest.category semantics (silo-plugin-sdk proto/silo/plugin/v1/common.proto): a slash-delimited path that groups plugins in the user-facing Apps section, e.g. "Books/Audiobooks" lands under Apps -> Books. The field existed in the manifest proto but silo-server never surfaced it. Server: the user plugin-settings list/detail responses now include an additive-only `category,omitempty` string sourced from the already-loaded manifest via GetCategory(); no new parsing paths. Web: PluginSettingsSummary gains `category?: string`, and AppSidebar groups Apps entries by the FIRST segment of the category path (one level of grouping for now; deeper segments intentionally ignored, documented against the SDK contract). When fewer than 2 distinct categories exist among the visible app links, today's flat list under the single "Apps" header is kept; with 2+ categories, per-category sub-headers render via the existing SidebarSectionHeader (labels hide in the collapsed sidebar the same way other section headers do). Uncategorized plugins fall under "Other", which always sorts last. Tests: Go unit tests for the summary converter (category passthrough and JSON omission when empty) and vitest coverage for the pure groupAppNavLinks helper plus grouped/flat/collapsed sidebar rendering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(plugins): use generic category examples in comments and tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(web): simplify Apps sidebar link list rendering - fold the duplicated <ul> list markup in the grouped and flat Apps branches into a single renderAppNavList helper so the list styling cannot drift between the two render paths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
61 lines
1.5 KiB
Go
61 lines
1.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/plugins"
|
|
)
|
|
|
|
func TestToUserPluginSettingsSummaryIncludesManifestCategory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
installation := &plugins.Installation{
|
|
ID: 42,
|
|
PluginID: "example-audiobooks",
|
|
Version: "1.2.3",
|
|
}
|
|
manifest := &pluginv1.PluginManifest{
|
|
Category: "Tools/Utilities",
|
|
}
|
|
|
|
got := toUserPluginSettingsSummary(installation, manifest)
|
|
|
|
if got.Category != "Tools/Utilities" {
|
|
t.Fatalf("Category = %q, want %q", got.Category, "Tools/Utilities")
|
|
}
|
|
if got.ID != 42 || got.PluginID != "example-audiobooks" || got.Version != "1.2.3" {
|
|
t.Fatalf("identity fields = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestToUserPluginSettingsSummaryOmitsEmptyCategory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
installation := &plugins.Installation{
|
|
ID: 7,
|
|
PluginID: "example-plain",
|
|
Version: "0.1.0",
|
|
}
|
|
|
|
got := toUserPluginSettingsSummary(installation, &pluginv1.PluginManifest{})
|
|
|
|
if got.Category != "" {
|
|
t.Fatalf("Category = %q, want empty", got.Category)
|
|
}
|
|
|
|
// The field is additive-only; ensure it stays absent from the JSON
|
|
// payload for manifests without a category so existing clients see an
|
|
// unchanged response shape.
|
|
encoded, err := json.Marshal(got)
|
|
if err != nil {
|
|
t.Fatalf("marshaling summary: %v", err)
|
|
}
|
|
if strings.Contains(string(encoded), "\"category\"") {
|
|
t.Fatalf("JSON payload should omit empty category, got %s", encoded)
|
|
}
|
|
}
|