fix(hub): show "BearDrive" in the sidebar logo, never the storage name (#52)

The hub's brand fell back to config.volume — the bucket/dir basename —
so a hub on s3://beardrive/... rendered a lowercase "beardrive" logo and
tab title. Drop the fallback at the source (/api/config reports only what
a Brander provider returns) and let each app default on its own: the hub
to the literal "BearDrive", volume mode to the folder name (unchanged).

The e2e harness now seeds Volume: "beardrive" so hub.spec.ts's existing
#vault-name assertion actually catches this.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow W. Lee (Sungwon)
2026-07-27 10:18:06 +09:00
committed by GitHub
co-authored by Claude Opus 5
parent 65491f9576
commit 1463fd8979
6 changed files with 53 additions and 8 deletions
+4 -1
View File
@@ -56,7 +56,10 @@ func TestE2EServe(t *testing.T) {
if err != nil {
t.Fatal(err)
}
srv := &Server{Root: be, Projects: db, Device: webDevice, Refresh: 0, Upload: UploadConfig{Enabled: true}}
// Volume is deliberately the lowercase storage basename, so hub.spec.ts's
// "#vault-name reads BearDrive" assertion actually catches a brand that
// falls back to storage instead of the product name.
srv := &Server{Root: be, Projects: db, Device: webDevice, Refresh: 0, Volume: "beardrive", Upload: UploadConfig{Enabled: true}}
seedE2E(t, state, filepath.Join(state, "storage", p.ID), p.ID)
+2 -2
View File
@@ -52,7 +52,7 @@ export default function HubApp({ config }: { config: ServerConfig }) {
useEffect(() => {
document.title = current
? current.name + " — BearDrive"
: config.brand || config.volume || "BearDrive";
: config.brand || "BearDrive";
}, [current, config]);
if (joinToken) {
@@ -68,7 +68,7 @@ export default function HubApp({ config }: { config: ServerConfig }) {
);
}
const brand = config.brand || config.volume || "BearDrive";
const brand = config.brand || "BearDrive";
const org = (current && orgs?.find((o) => o.id === current.org)) || null;
// Insights (embedded on the project home and behind the ⋯ menu) is for
// hub admins and owners of the project's org.
+3 -3
View File
@@ -455,9 +455,9 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
if b, ok := s.Auth.(Brander); ok {
brand = b.Branding()
}
if brand == "" {
brand = s.Volume
}
// No fallback: the volume is a storage basename, not a brand. An
// unconfigured brand stays empty and each app picks its own default
// (hub: "BearDrive", volume mode: the folder name).
out := map[string]any{
"mode": mode,
"volume": s.Volume,
+42
View File
@@ -210,6 +210,48 @@ func TestFrontendServed(t *testing.T) {
}
}
// The brand is the product/self-hoster name, never the storage basename:
// a hub whose bucket is "beardrive" must not report that as its brand.
func TestConfigBrandNeverLeaksVolume(t *testing.T) {
srv, _, _ := newHub(t, true, nil)
srv.Volume = "beardrive"
var cfg struct {
Volume string `json:"volume"`
Brand string `json:"brand"`
}
read := func() {
t.Helper()
rec := do(t, srv.Handler(), "GET", "/api/config", nil)
if rec.Code != 200 {
t.Fatalf("config: %d %s", rec.Code, rec.Body)
}
if err := json.Unmarshal(rec.Body.Bytes(), &cfg); err != nil {
t.Fatal(err)
}
}
read()
if cfg.Brand != "" {
t.Errorf("unconfigured brand = %q, want empty (the frontend defaults it)", cfg.Brand)
}
if cfg.Volume != "beardrive" {
t.Errorf("volume = %q, want beardrive (VolumeApp reads this key)", cfg.Volume)
}
auth, err := OpenBuiltinAuth(filepath.Join(t.TempDir(), "auth.json"), false, nil)
if err != nil {
t.Fatal(err)
}
auth.Brand = "Acme Docs"
srv.Auth = auth
read()
if cfg.Brand != "Acme Docs" {
t.Errorf("configured brand = %q, want Acme Docs", cfg.Brand)
}
}
func TestExpandWikilinks(t *testing.T) {
got := string(expandWikilinks([]byte("a [[x y]] b [[u|v]] c [[no")))
want := "a [x y](wiki:x%20y) b [v](wiki:u) c [[no"
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BearDrive</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='%23f5a623'><rect x='4' y='4' width='5.6' height='24'/><rect x='11.2' y='4' width='14.4' height='11.2'/><rect x='11.2' y='16.8' width='16.8' height='11.2'/></svg>">
<script type="module" crossorigin src="/assets/index-C2CHlQCN.js"></script>
<script type="module" crossorigin src="/assets/index-Vwh6tzvU.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-FkLsvBWJ.css">
</head>
<body>