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
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
| Gesture | Motion | Action |
|---|---|---|
| Swipe Right | → | Go Forward |
| Swipe Left | ← | Go Back |
| Swipe Up | ↑ | Scroll to Top |
| Swipe Down | ↓ | Scroll to Bottom |
| Right → Down | → ↓ | Next Tab |
| Left → Up | ← ↑ | Previous Tab |
| Circle | ↻ | Refresh 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, andmouseupevents, delegating direction logic togesture.ts. - Canvas overlay — Appends a full-viewport
<canvas>on gesture start, renders the live trail using quadratic Bézier curve smoothing and scales fordevicePixelRatioso 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
contextmenulistener 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
atan2and 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.) ornullif 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_TABandPREV_TABmessages 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:
| Script | File | Purpose |
|---|---|---|
bun run dev | — | Start Astro dev server for popup UI work |
bun run build | build-tools/bundler.ts | Compile content + background scripts, then run Astro |
bun run icons | build-tools/create-icons.ts | Generate 16/32/48/128px icons from source via Sharp |
bun run release | build-tools/pack.ts | Full pipeline: format → build → icons → ZIP for distribution |
Build sequence in detail:
- Astro compiles
Popup.astroto static HTML/CSS indist/. bundler.tsruns Bun's built-in bundler oncontent.tsandbackground.ts, outputting minifiedcontent.jsandbackground.jstodist/.extract-inline.tsparses the Astro-generated HTML, extracts any remaining inline<script>blocks into separate.jsfiles, and rewrites thesrcattributes to reference them — making the output CSP-compliant.pack.tsZIPs thedist/folder into a versioned archive ready for Chrome Web Store submission.
Tech Stack
| Layer | Technology | Why |
|---|---|---|
| Extension runtime | Chrome MV3 | Current standard, required for store distribution |
| Language | TypeScript 5 | Strict types across all three runtime contexts |
| Popup UI | Astro 3 | Zero-JS static output by default; fast builds |
| Bundler | Bun | Sub-second builds, native TypeScript support |
| Icon generation | Sharp | High-quality PNG resizing without heap bloat |
| Packaging | adm-zip | Pure-JS ZIP generation, no OS tooling required |
Permissions
TabiNeko requests only what is strictly necessary:
| Permission | Reason |
|---|---|
tabs | Query and switch tabs by index |
activeTab | Identify the currently focused tab |
scripting | Inject content script programmatically |
storage | Reserved for future user gesture configuration |
No host permissions beyond http://*/* and https://*/* for content script injection. No network requests. No telemetry.
Installation (Developer Mode)
-
Clone the repository and install dependencies:
bashgit clone https://github.com/Mic-360/TabiNeko cd TabiNeko bun installgit clone https://github.com/Mic-360/TabiNeko cd TabiNeko bun install -
Build the extension:
bashbun run buildbun run build -
Open
chrome://extensions/in Chrome, enable Developer mode, click Load unpacked, and select thedist/folder. -
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.