feat(web): History in the project menu, after Installation

Navigates to the existing /history route; active state follows the URL.
46/46 e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbiaaVM2ACxeRi8ySG9ybc
This commit is contained in:
Snow Lee
2026-07-19 11:07:14 -07:00
co-authored by Claude Fable 5
parent bacb528de6
commit 317045cecb
6 changed files with 58 additions and 52 deletions
@@ -175,6 +175,9 @@ test("project menu pages each own a URL: Dashboard, Installation, Settings", asy
await page.waitForURL(`/${pid}/install`);
await expect(page.locator("#crumb")).toHaveText("Installation");
await expect(page.locator("#nav-install")).toHaveClass(/active/);
await page.click("#nav-history");
await page.waitForURL(`/${pid}/history`);
await expect(page.locator("#nav-history")).toHaveClass(/active/);
await page.click("#nav-settings");
await page.waitForURL(`/${pid}/settings`);
await expect(page.locator("#crumb")).toHaveText("Project settings");
+10 -3
View File
@@ -198,9 +198,11 @@ export default function HubApp({ config }: { config: ServerConfig }) {
? "dashboard"
: route.view === "install"
? "install"
: route.view === "settings"
? "settings"
: null,
: route.view === "history"
? "history"
: route.view === "settings"
? "settings"
: null,
// Each page is a URL; explicitly close overlay panels because
// same-path navigation doesn't change pathname.
onDashboard: () => {
@@ -213,6 +215,11 @@ export default function HubApp({ config }: { config: ServerConfig }) {
navigate(urlForView("install", current.id));
closeSidebarOnMobile();
},
onHistory: () => {
setPanel(null);
navigate(urlForView("history", current.id));
closeSidebarOnMobile();
},
onSettings: () => {
setPanel(null);
navigate(urlForView("settings", current.id));
@@ -17,9 +17,10 @@ export function projColor(s: string): string {
}
export interface ProjectMenu {
active: "dashboard" | "install" | "settings" | null;
active: "dashboard" | "install" | "history" | "settings" | null;
onDashboard: () => void;
onInstall: () => void;
onHistory: () => void;
onSettings: () => void;
}
@@ -91,6 +92,7 @@ export function ProjectNav({
[
["dashboard", "Dashboard", "dashboard", menu.onDashboard],
["install", "Installation", "terminal", menu.onInstall],
["history", "History", "hist", menu.onHistory],
["settings", "Settings", "gear", menu.onSettings],
] as const
).map(([key, label, icon, onClick]) => (
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 100 100'><text y='.9em' font-size='90'>&#128059;</text></svg>">
<script type="module" crossorigin src="/assets/index-B9q7i7UX.js"></script>
<script type="module" crossorigin src="/assets/index-T_RFOOBs.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C5jjBsTc.css">
</head>
<body>
+34 -40
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BearDrive — Google Drive for AI agents</title>
<meta name="description" content="Google Drive for AI agents: one synced folder for your whole team. Share any file with people as a public URL; share context across agents, so your agent knows what their agent knows. Open source and self-hostable.">
<meta name="description" content="Google Drive for AI agents: one synced folder for your whole team. Share any file as a public URL; give every agent the same memory, so your agent knows what their agent knows. Managed cloud, or self-host the open-source hub in one binary.">
<meta property="og:title" content="BearDrive — Google Drive for AI agents">
<meta property="og:description" content="One synced folder for humans and agents: files become public URLs for people, and shared memory for every teammate's agent. Open source, self-host in one binary.">
<meta property="og:type" content="website">
@@ -235,14 +235,6 @@ h2 { font-size: clamp(26px, 3.2vw, 34px); margin: 0 0 12px; letter-spacing: -.02
.oss-card ul { list-style: none; padding: 0; margin: 0 0 26px; }
.oss-card li { padding: 6px 0 6px 24px; font-size: 14px; color: var(--text-dim); position: relative; }
.oss-card li::before { content: "✓"; position: absolute; left: 0; color: var(--brand); font-weight: 600; }
.waitlist { display: flex; gap: 10px; flex-wrap: wrap; }
.waitlist input {
flex: 1 1 190px; padding: 8px 13px; border-radius: 6px;
border: 1px solid var(--border-strong); background: var(--bg); color: var(--text);
font: 14px/1.4 inherit; outline: none;
}
.waitlist input:focus { border-color: var(--brand-dark); }
/* closing CTA */
.closer { text-align: center; padding: 96px 0; }
.closer h2 { font-size: clamp(28px, 4vw, 44px); margin-bottom: 10px; }
@@ -280,7 +272,8 @@ footer a:hover { color: var(--text); }
<a href="#claude">Claude Code</a>
<a href="#get">Open source</a>
<a href="https://github.com/runbear-io/beardrive" title="GitHub">GitHub</a>
<a class="btn" href="https://github.com/runbear-io/beardrive">Star on GitHub</a>
<a href="https://app.beardrive.ai/auth/login">Log in</a>
<a class="btn" href="https://app.beardrive.ai/auth/signup">Sign up free</a>
</div>
</div>
</nav>
@@ -291,9 +284,10 @@ footer a:hover { color: var(--text); }
<h1>Google Drive for AI agents.<span class="green">Your agent knows what their agent knows.</span></h1>
<p class="sub">One synced folder for your whole team — humans and agents.
<b>Share any file with people</b> as a public URL; <b>give every agent the same
memory</b>, synced across machines in seconds. Real files. Self-host the whole thing in one Go binary.</p>
memory</b>, synced across machines in seconds. Real files. <b>Use the managed cloud</b>, or self-host the whole thing in one Go binary.</p>
<div class="cta-row">
<a class="btn" href="#get">Start syncing</a>
<a class="btn" href="https://app.beardrive.ai/auth/signup">Start free →</a>
<a class="btn ghost" href="#get">Self-host it</a>
<button class="install" id="install" title="Copy">
<span class="dollar">$</span>
<span id="install-cmd">brew install runbear-io/tap/beardrive</span>
@@ -399,10 +393,16 @@ every tool works content-addressed blobs append-only, no locks, offl
which device — every past version viewable and downloadable. An agent
overwriting your doc is an annoyance, not a loss.</p>
</div>
<div class="card"><div class="ico">🔒</div>
<p><b>Gated links your agents hand you.</b> When Claude writes or updates a
file, it drops a link right beside the path — a hub link that only opens for
signed-in teammates on the project. Safe to paste in any internal channel;
the turn-start hook teaches every agent to do it automatically.</p>
</div>
<div class="card"><div class="ico">🔗</div>
<p><b>Public share links.</b> Any synced file becomes a rendered page at an
unguessable URL: HTML as a page, markdown Obsidian-style, PDFs inline.
Sandboxed, rate-limited, living until you revoke it.</p>
<p><b>Public share links.</b> Need it truly public? Any synced file becomes a
rendered page at an unguessable URL: HTML as a page, markdown Obsidian-style,
PDFs inline. Sandboxed, rate-limited, living until you revoke it.</p>
</div>
<div class="card"><div class="ico">👥</div>
<p><b>Teams &amp; organizations.</b> Projects belong to your org; only
@@ -427,17 +427,19 @@ every tool works content-addressed blobs append-only, no locks, offl
<section id="claude">
<div class="wrap claude">
<div class="copy">
<p class="kicker">CLAUDE CODE</p>
<p class="kicker">CLAUDE CODE &amp; COWORK</p>
<h2>Your agents become fluent in it.</h2>
<ul>
<li><b>/beardrive:install</b> sets a whole team project up
conversationally: CLI, sign-in, project, and sync hooks.</li>
conversationally: CLI, sign-in, project, and sync hooks — one plugin that
works the same in Claude Code and Claude Cowork.</li>
<li><b>Hooks keep everything fresh</b> — a pull when you submit a prompt
(Claude reads the team's latest files) and an async push after every edit
(artifacts hit the server seconds after Claude writes them). Works for
every teammate, plugin or not.</li>
<li><b>The skill teaches Claude the CLI</b>, so "share this report with
the team" just works.</li>
<li><b>Every file it writes comes with a link</b> — the turn-start hook
teaches Claude to drop a gated hub link beside any synced path it mentions,
so "share this report with the team" just works.</li>
</ul>
<a class="btn ghost" href="https://github.com/runbear-io/beardrive/tree/main/plugin">Explore the plugin →</a>
</div>
@@ -468,8 +470,8 @@ every tool works content-addressed blobs append-only, no locks, offl
<p class="kicker">GET BEARDRIVE</p>
<h2>Self-host everything. Or let us run it.</h2>
<p class="lede">The entire product is open source under AGPL-3.0 — server, teams,
history, share links, <b>nothing held back</b>. The cloud exists for teams who'd
rather never think about buckets.</p>
history, share links, <b>nothing held back</b>. Or skip the buckets entirely —
BearDrive Cloud is live at app.beardrive.ai.</p>
<div class="oss">
<div class="oss-card">
<h3>Open source</h3>
@@ -487,16 +489,16 @@ every tool works content-addressed blobs append-only, no locks, offl
</div>
<div class="oss-card cloud">
<h3>BearDrive Cloud</h3>
<div class="tag">beardrive.ai · managed hosting · coming soon</div>
<div class="tag">app.beardrive.ai · managed hosting · live now</div>
<ul>
<li>Zero setup — <code>bdrive login</code> and go</li>
<li>SSO, backups, and support handled for you</li>
<li>Zero setup — sign up, <code>bdrive login</code>, and go</li>
<li>Managed storage, backups, and support handled for you</li>
<li>Funds the open source project</li>
</ul>
<form class="waitlist" id="waitlist">
<input type="email" id="waitlist-email" placeholder="you@company.com" aria-label="Email" required>
<button class="btn" type="submit">Join the waitlist</button>
</form>
<div class="cta-row" style="justify-content:flex-start">
<a class="btn" href="https://app.beardrive.ai/auth/signup">Start free →</a>
<a class="btn ghost" href="https://app.beardrive.ai/auth/login">Log in</a>
</div>
</div>
</div>
</div>
@@ -504,10 +506,11 @@ every tool works content-addressed blobs append-only, no locks, offl
<section class="closer">
<div class="wrap">
<h2>Share files. Share context.<br><span class="green">Self-host in one binary.</span></h2>
<p>brew install, run one hub, bdrive init. Files travel to people as URLs; context travels to every agent on the team. Cloud — zero-setup — is on the waitlist.</p>
<h2>Share files. Share context.<br><span class="green">Cloud or self-hosted.</span></h2>
<p>Sign up for the managed cloud and go, or brew install and self-host in one binary. Files travel to people as URLs; context travels to every agent on the team.</p>
<div class="cta-row">
<a class="btn" href="https://github.com/runbear-io/beardrive">Start on GitHub</a>
<a class="btn" href="https://app.beardrive.ai/auth/signup">Start free →</a>
<a class="btn ghost" href="https://github.com/runbear-io/beardrive">Self-host on GitHub</a>
<button class="install" id="install2" title="Copy">
<span class="dollar">$</span>
<span id="install-cmd2">brew install runbear-io/tap/beardrive</span>
@@ -528,15 +531,6 @@ every tool works content-addressed blobs append-only, no locks, offl
</footer>
<script>
/* waitlist: no backend yet, so hand off to email */
document.getElementById("waitlist").addEventListener("submit", (e) => {
e.preventDefault();
const email = document.getElementById("waitlist-email").value;
location.href = "mailto:hello@runbear.io" +
"?subject=" + encodeURIComponent("BearDrive Cloud waitlist") +
"&body=" + encodeURIComponent("Please add " + email + " to the BearDrive Cloud waitlist.");
});
/* copy-to-clipboard on both install commands */
for (const [btn, cmd] of [["install", "install-cmd"], ["install2", "install-cmd2"]]) {
document.getElementById(btn).addEventListener("click", async () => {