feat(web): a Beta pill beside the BearDrive wordmark (#154)

BearDrive launches free with a beta label, so the hub says so next to its
own name. Subtle honey pill at low alpha, not the solid accent: the accent
is already spent on the mark two elements to its left, and this sits there
until it doesn't.

It keys on what the lockup actually SAYS (`brand === "BearDrive"`), not on
whether `brand` was configured. A hub that set its own brand is labelling
somebody else's product and "Acme Docs Beta" is a claim we have no business
making for them — but the managed hub and every default self-host reach here
with the string "BearDrive", and the obvious `!config.brand` test would have
missed them: CLOUD_BRAND defaults to "BearDrive", so a BuiltinAuth hub sends
the name rather than an empty field, and the pill would have shown only on
the PropelAuth path.


Claude-Session: https://claude.ai/code/session_019vWP4DbsWCeD6zWN5gaqsV

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow Lee (Sungwon)
2026-08-11 20:27:45 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 8931004b4a
commit f70d54192a
7 changed files with 42 additions and 8 deletions
+17 -2
View File
@@ -122,7 +122,22 @@ export default function HubApp({ config }: { config: ServerConfig }) {
// Top of the sidebar is the brand; project and account actions live in
// their own sections below (PropelAuth-style layout).
const vault = <VaultHeader name={brand} onHome={() => navigate("/")} search={!!current} />;
// The Beta pill labels the BEARDRIVE wordmark, so it keys on what the
// lockup actually says rather than on whether `brand` was configured. A hub
// that set its own brand is labelling somebody else's product, and "Acme
// Docs Beta" is a claim we have no business making for them — but the
// managed hub and every default self-host DO reach here with "BearDrive",
// and a `!config.brand` test would have missed the ones that set it
// explicitly (CLOUD_BRAND defaults to "BearDrive", so BuiltinAuth hubs send
// the string rather than an empty field).
const vault = (
<VaultHeader
name={brand}
onHome={() => navigate("/")}
search={!!current}
beta={brand === "BearDrive"}
/>
);
const accountBar = config.me ? (
<AccountBar
@@ -393,7 +408,7 @@ function JoinInvite({ token, onDone }: { token: string; onDone: (orgId: string |
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
return (
<AppShell vault={<VaultHeader name="BearDrive" />} topbar={<Topbar />}>
<AppShell vault={<VaultHeader name="BearDrive" beta />} topbar={<Topbar />}>
<Page>
<div className="empty">Joining</div>
</Page>
@@ -309,8 +309,13 @@ export function VaultHeader(props: {
onHome?: () => void; // hub: the project name doubles as a home link
showSignout?: boolean; // volume mode: sign-out stays in the header (no account bar)
search?: boolean; // icon-only ⌘K search trigger beside the brand
/* BearDrive is pre-1.0 and says so next to its own wordmark. Callers pass
this only when the lockup IS the BearDrive brand — a hub that set its own
`brand` is labelling somebody else's product, and "Acme Docs Beta" is a
claim we have no business making for them. */
beta?: boolean;
}) {
const { name, onHome, showSignout, search } = props;
const { name, onHome, showSignout, search, beta } = props;
return (
<header id="vault">
<span id="vault-badge">
@@ -331,6 +336,7 @@ export function VaultHeader(props: {
>
{name}
</span>
{beta && <span id="vault-beta">Beta</span>}
<div className="vault-actions">
{search && (
<Tooltip delayDuration={150}>
+13
View File
@@ -128,6 +128,19 @@ button, input, a.btn { font-family: inherit; }
letter-spacing: .01em; line-height: 1;
flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Beta pill. Subtle on purpose: it sits beside the wordmark forever until it
doesn't, so it reads as a footnote to the brand, not a second logo. Honey
at low alpha rather than the solid accent — the accent is spent on the mark
two elements to the left. */
#vault-beta {
flex: none;
font-size: 10px; font-weight: 600; line-height: 1;
letter-spacing: .06em; text-transform: uppercase;
color: var(--accent-bright);
background: color-mix(in srgb, var(--accent) 13%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
border-radius: 999px; padding: 3px 6px;
}
.vault-actions { display: flex; align-items: center; gap: 4px; }
#vault #signout, .icon-btn2 {
width: 28px; height: 28px; border-radius: 6px;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,10 +5,10 @@
<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-o0X8ZIDt.js"></script>
<script type="module" crossorigin src="/assets/index-Cg3Ijtd_.js"></script>
<link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-CqkleIqs.js">
<link rel="modulepreload" crossorigin href="/assets/mermaid-CP2pUOT9.js">
<link rel="stylesheet" crossorigin href="/assets/index-Bhy4rJG7.css">
<link rel="stylesheet" crossorigin href="/assets/index-aFlVpSeL.css">
</head>
<body>
<div id="root"></div>