Hey IH π
I just pushed Leap to the Chrome Web Store β a vertical-sidebar extension that gives regular Chrome the Arc Browser experience: Spaces, vertical tabs, pinned sites, nested folders, keyboard shortcuts, cloud sync. Tagline: "Leap between spaces."
I didn't want to switch browsers. I wanted Arc's workflow inside the browser I already use. So I built it.
Posting here as a build-in-public write-up β the three architectural decisions that mattered, the stack, and the traps I wish someone had warned me about. Would love your feedback.
The obvious move is to render your own tab list in a React sidebar and pretend Chrome's native UI doesn't exist. I didn't do that.
Every Leap "Space" is a real chrome.tabGroups group under the hood. Switching a space means: collapse every group β expand the target β activate its first tab.
Why it matters: in MV3, chrome.tabs.hide() is not available to regular extensions. You cannot hide tabs to emulate spaces. You can collapse tab groups β and a collapsed group visually disappears from the tab strip while keeping the tabs alive. That single API constraint drove the whole architecture.
Upside: zero desync between "what Leap thinks" and "what Chrome thinks". Downside: you're bound to tab-group semantics (one group per tab, group color enum, etc.) forever.
All writes go to chrome.storage.local first. The service worker then debounces a sync to the backend. The UI never blocks on the network.
fetch directly, they only go through query hooks in queries/The mental model: local is the source of truth at read time, server is the source of truth at reconcile time. It's the only pattern that gives you the Arc-like instant feel while still having cloud sync across devices.
chrome.identity = one login, foreverThis was the most pleasant surprise. The flow:
chrome.identity.getAuthToken() grabs a Google OAuth token from the signed-in Chrome profile.signInWithCredential() into a Firebase session.Net result: the user clicks "Sign in with Google" once. That's it. Forever. No token refresh UI, no re-login after a restart, no cookie jar issues. For a Chrome extension this is gold β auth friction was my biggest fear and it turned out to be a 40-line integration.
Extension: React 18, TypeScript strict (no any), Tailwind 3, Zustand, TanStack Query, Framer Motion (spring-based, Apple-aesthetic micro-animations), Vite + CRXJS, Manifest V3.
Server: Go 1.24, Fiber v3, goqu v9 (SQL builder) + pgx v5, Neon Postgres, Firebase Admin, go-playground/validator, log/slog. Plain structs as models, manual DI, golang-migrate for schema.
Monorepo: Turborepo + pnpm, a [@leap](/leap)/shared package for the TypeScript types (Go produces the matching JSON via struct tags).
Infra: Neon (DB), Railway (Go server, Docker), Firebase (auth), Biome for the TS side.
chrome.storage. I learned this the hard way with a debounce timer that silently stopped firing.parentId + depth schema β easier to enforce depth at write time than to chase orphaned nodes later.Leap is live on the Chrome Web Store right now: π Install Leap
Free, no account needed to try locally β sign in only if you want cross-device sync.
Happy to answer anything about the stack, the MV3 constraints, or the build-in-public journey in the comments. π
β Roman
I actually know a few Chrome extension founders who've scaled past 1k installs personally. Happy to ask them if they'd answer some of your questions about onboarding and retention for free.