TabiNeko — Mouse Gestures

march 2, 2026

A production-grade mouse gesture Chrome extension that lets you navigate tabs and pages with right-click drag gestures — built with TypeScript, Bun, and a custom Manifest V3 build pipeline.

tooling · web · chrome-extension · typescript · browser · ux

TypeScriptAstroBunChrome MV3Sharp

TabiNeko — Mouse Gestures

A premium Chrome extension (Manifest V3) that replaces repetitive mouse clicks with fluid right-click drag gestures. Navigate backward, forward, switch tabs, scroll, and refresh — all without lifting your hand to reach toolbar buttons or keyboard shortcuts.

The Problem

Modern browser navigation is optimized for pointing, not flowing. Every back, forward, and tab switch requires precise cursor targeting. Power users making dozens of navigation moves per session end up in a loop of micro-movements that interrupt focus. Mouse gestures have been a feature of Opera and Firefox since the early 2000s — Chrome has never shipped one natively.

The Solution

TabiNeko injects a lightweight content script into every page that listens for right-click drag events. As you drag, it renders a live visual trail on a canvas overlay and continuously classifies your motion into cardinal directions. On mouseup, it resolves the gesture signature and executes the corresponding browser action — entirely client-side, with no network calls and no permissions beyond what is strictly required.

Gesture Reference

GestureMotionAction
Swipe RightGo Forward
Swipe LeftGo Back
Swipe UpScroll to Top
Swipe DownScroll to Bottom
Right → Down→ ↓Next Tab
Left → Up← ↑Previous Tab
CircleRefresh Page

Context menu behavior is suppressed only when a gesture is detected, so right-clicking normally still works as expected.


Architecture

TabiNeko is split into three runtime contexts, each compiled independently:

1. Content Script (src/scripts/content.ts)

Injected into every HTTP/HTTPS page at document_idle. Responsibilities:

  • Gesture tracking — Listens to mousedown, mousemove, and mouseup events, delegating direction logic to gesture.ts.
  • Canvas overlay — Appends a full-viewport <canvas> on gesture start, renders the live trail using quadratic Bézier curve smoothing and scales for devicePixelRatio so the path looks crisp on retina displays.
  • Direction labels — Renders real-time arrow symbols at the gesture origin as the user draws.
  • Action dispatch — For page-level actions (scroll, back, forward, reload), executes directly in the page context. For tab operations (next/prev tab), sends a typed message to the background service worker.
  • Context menu suppression — Adds a one-shot contextmenu listener that cancels the event only when a gesture was just completed.

2. Gesture Engine (src/scripts/internal/gesture.ts)

A pure, stateless gesture classifier. Key implementation details:

  • Direction detection — Uses a 25px movement threshold from the last anchor point before registering a new cardinal direction. This filters out cursor noise and minor direction wobble.
  • Deduplication — Consecutive identical directions are collapsed (e.g., ["R", "R", "D"]["R", "D"]), preventing rapid re-triggering from slow or jittery movement.
  • Circle detection — Accumulates the signed angle change between consecutive mouse vectors using atan2 and cross-product arithmetic. A full circle is confirmed when accumulated rotation exceeds 300° and the path endpoint is within 80px of the start point. This approach handles imperfect hand-drawn circles reliably.
  • Output — Returns a string signature ("R", "L", "R,D", "CIRCLE", etc.) or null if the gesture is unrecognized.

3. Background Service Worker (src/scripts/background.ts)

A minimal Chrome MV3 service worker that handles tab-switching commands:

  • Receives NEXT_TAB and PREV_TAB messages from the content script.
  • Queries all tabs in the current window, identifies the active tab's index, and activates the next or previous tab.
  • Wrap-around logic — Last tab → First tab, First tab → Last tab.
  • Handles edge cases: tabs closed between gesture start and message receipt are caught and fail silently.

4. Popup UI (src/components/Popup.astro)

A static Astro-rendered HTML page loaded as the extension action popup. It displays the gesture reference table and branding. No JavaScript runs in the popup at runtime — content is fully pre-rendered at build time.


Build Pipeline

Chrome's Manifest V3 Content Security Policy prohibits inline scripts in extension pages, so the standard Astro build output (which injects inline <script> tags) must be post-processed. The build pipeline is composed of Bun scripts:

ScriptFilePurpose
bun run devStart Astro dev server for popup UI work
bun run buildbuild-tools/bundler.tsCompile content + background scripts, then run Astro
bun run iconsbuild-tools/create-icons.tsGenerate 16/32/48/128px icons from source via Sharp
bun run releasebuild-tools/pack.tsFull pipeline: format → build → icons → ZIP for distribution

Build sequence in detail:

  1. Astro compiles Popup.astro to static HTML/CSS in dist/.
  2. bundler.ts runs Bun's built-in bundler on content.ts and background.ts, outputting minified content.js and background.js to dist/.
  3. extract-inline.ts parses the Astro-generated HTML, extracts any remaining inline <script> blocks into separate .js files, and rewrites the src attributes to reference them — making the output CSP-compliant.
  4. pack.ts ZIPs the dist/ folder into a versioned archive ready for Chrome Web Store submission.

Tech Stack

LayerTechnologyWhy
Extension runtimeChrome MV3Current standard, required for store distribution
LanguageTypeScript 5Strict types across all three runtime contexts
Popup UIAstro 3Zero-JS static output by default; fast builds
BundlerBunSub-second builds, native TypeScript support
Icon generationSharpHigh-quality PNG resizing without heap bloat
Packagingadm-zipPure-JS ZIP generation, no OS tooling required

Permissions

TabiNeko requests only what is strictly necessary:

PermissionReason
tabsQuery and switch tabs by index
activeTabIdentify the currently focused tab
scriptingInject content script programmatically
storageReserved for future user gesture configuration

No host permissions beyond http://*/* and https://*/* for content script injection. No network requests. No telemetry.


Installation (Developer Mode)

  1. Clone the repository and install dependencies:

    bash
    git clone https://github.com/Mic-360/TabiNeko
    cd TabiNeko
    bun install
    git clone https://github.com/Mic-360/TabiNeko
    cd TabiNeko
    bun install
  2. Build the extension:

    bash
    bun run build
    bun run build
  3. Open chrome://extensions/ in Chrome, enable Developer mode, click Load unpacked, and select the dist/ folder.

  4. The TabiNeko icon will appear in your toolbar. Right-click and drag anywhere on a page to begin.


What I Learned

Manifest V3 is strict by design. The CSP restrictions that forced a custom extraction step exist because inline scripts in extension pages are a real attack surface. Building the extraction tool was annoying but the constraint is correct.

Gesture UX requires tuning, not just logic. The 25px direction threshold, 300° circle threshold, and quadratic trail smoothing weren't arbitrary — each value was arrived at by testing what felt natural vs. what felt like the extension was fighting you. Gesture recognition is a UX problem as much as a math problem.

Bun's native bundler is genuinely fast. The full build from source to loadable dist/ runs in under two seconds, which makes iteration on the content script painless.


If you want to try TabiNeko, it takes about two minutes to build and load. The gestures take a few minutes to muscle-memory — after that, going back to clicking feels slow.

back to projects