Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
41f0194
Update coding agent names in README
zarazhangrui Apr 14, 2026
9f26e51
feat: bento layout + custom group support
zarazhangrui Apr 14, 2026
81796a5
feat: add session import and global search
mrfoolish Jun 30, 2026
e95db23
feat: harden session restore and dashboard state
mrfoolish Jun 30, 2026
220c748
feat: improve dashboard accessibility and state sync
mrfoolish Jun 30, 2026
134c484
fix: refine more menu popover layering
mrfoolish Jun 30, 2026
617a3c8
feat: improve imported tab restore behavior
mrfoolish Jul 1, 2026
ff082eb
refactor: centralize tab out dashboard state
mrfoolish Jul 3, 2026
186270d
refactor: extract imported session controller
mrfoolish Jul 3, 2026
22e98e3
refactor: extract later list renderer
mrfoolish Jul 3, 2026
0be84a0
fix: reduce dashboard startup console noise
mrfoolish Jul 3, 2026
25e6088
fix: harden dashboard tab actions and favicon rendering
mrfoolish Jul 3, 2026
fa89d78
refactor: extract dashboard view utilities
mrfoolish Jul 3, 2026
3faa38d
refactor: centralize tab filtering rules
mrfoolish Jul 3, 2026
c413f4a
test: cover dashboard view utilities
mrfoolish Jul 3, 2026
9deabac
refactor: extract dashboard search renderer
mrfoolish Jul 3, 2026
253c66c
refactor: extract dashboard card renderer
mrfoolish Jul 3, 2026
76bf4a4
refactor: harden dashboard foundation modules
mrfoolish Jul 6, 2026
2cd1d7b
refactor: extract dashboard ui effects
mrfoolish Jul 7, 2026
30f4cd6
refactor: split dashboard actions and stabilize updates
mrfoolish Jul 7, 2026
8365a34
feat: add tiered confirmation for bulk tab closing
mrfoolish Jul 7, 2026
0e9ce16
fix: reduce redundant dashboard refreshes
mrfoolish Jul 8, 2026
5d951a2
feat: improve duplicate tab management
mrfoolish Jul 10, 2026
241304f
refactor: extract dashboard runtime helpers
mrfoolish Jul 10, 2026
90a5134
refactor: split dashboard app runtime modules
mrfoolish Jul 10, 2026
2ebd655
perf: reduce auto refresh tab churn
mrfoolish Jul 10, 2026
07b588c
refactor: extract dashboard render flow
mrfoolish Jul 10, 2026
f36f0d6
refactor: harden dashboard architecture boundaries
mrfoolish Jul 10, 2026
c169a35
fix: reduce auto refresh tab churn
mrfoolish Jul 10, 2026
fd24787
Merge 'fix/auto-refresh-tab-churn' into 'main'
mrfoolish Jul 10, 2026
2f20f6d
feat: add theme, i18n, and tab moving controls
mrfoolish Jul 11, 2026
53c899a
fix: refine favicons, tooltips, and docs
mrfoolish Jul 11, 2026
bec467f
feat: add custom grouping rule management
mrfoolish Jul 11, 2026
4fadaed
refactor: extract dashboard static text renderer
mrfoolish Jul 11, 2026
0f22846
feat: restore imported sessions by window
mrfoolish Jul 27, 2026
09f609a
fix: improve scoped search and later list interactions
mrfoolish Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 71 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,91 +1,110 @@
# Tab Out

**Keep tabs on your tabs.**
<div align="center">

Tab Out is a Chrome extension that replaces your new tab page with a dashboard of everything you have open. Tabs are grouped by domain, with homepages (Gmail, X, LinkedIn, etc.) pulled into their own group. Close tabs with a satisfying swoosh + confetti.

No server. No account. No external API calls. Just a Chrome extension.
# Tab Out

---
[中文](./README.zh.md) &nbsp;·&nbsp;
[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
[![Chrome](https://img.shields.io/badge/Chrome%20Extension-MV3-blue)](https://developer.chrome.com/docs/extensions/mv3/)

## Install with a coding agent
</div>

Send your coding agent (Claude Code, Cursor, Windsurf, etc.) this repo and say **"install this"**:
<br>

```
https://github.com/zarazhangrui/tab-out
```
> **Keep tabs on your tabs.** Tab Out replaces your new tab page with a clean,
> local dashboard for every open tab, grouped by domain and built for fast cleanup.

The agent will walk you through it. Takes about 1 minute.
No server. No account. No external API calls. Your tab data stays on your machine.

---
<br>

## Features

- **See all your tabs at a glance** on a clean grid, grouped by domain
- **Homepages group** pulls Gmail inbox, X home, YouTube, LinkedIn, GitHub homepages into one card
- **Close tabs with style** with swoosh sound + confetti burst
- **Duplicate detection** flags when you have the same page open twice, with one-click cleanup
- **Click any tab to jump to it** across windows, no new tab opened
- **Save for later** bookmark tabs to a checklist before closing them
- **Localhost grouping** shows port numbers next to each tab so you can tell your vibe coding projects apart
- **Expandable groups** show the first 8 tabs with a clickable "+N more"
- **100% local** your data never leaves your machine
- **Pure Chrome extension** no server, no Node.js, no npm, no setup beyond loading the extension
- **Domain grouping**: see open tabs grouped by site on a clean grid.
- **Homepages card**: Gmail, X, YouTube, LinkedIn, GitHub homepages are pulled into one cleanup-friendly group.
- **One-click cleanup**: close one tab, all duplicates, one domain group, or all open tabs.
- **Swoosh + confetti**: closing tabs feels deliberate and visible.
- **Duplicate detection**: repeated pages show `(2x)` badges with dedicated cleanup actions.
- **Cross-window jump**: click a tab title to focus that exact tab, even in another Chrome window.
- **Advanced tab moving**: move one tab, one Tab Out group, or all tabs into the current window.
- **Custom grouping rules**: group tabs by exact host, host suffix, and optional path prefix.
- **Save for later**: save tabs into a local checklist before closing them.
- **Import / export sessions**: export current groups with window context, then restore imports into the current window or their original windows.
- **Import / export grouping rules**: move rule sets between browsers or keep a local backup.
- **Search everywhere**: one search covers open tabs, imported sessions, and the later list.
- **Light / dark / system themes**: switch theme from the More menu.
- **English / Chinese UI**: switch language from the More menu. English is the default.
- **Resilient favicons**: tries real tab favicons first, then falls back to Chrome's favicon endpoint and local placeholders.
- **100% local**: saved tabs, imported sessions, grouping rules, settings, language, theme, and advanced controls use `chrome.storage.local`.

<br>

## How It Works

```text
Open a new tab
-> Tab Out shows your open tabs grouped by domain
-> Homepages (Gmail, X, etc.) appear in their own group
-> Click a tab title to jump to it across windows
-> Use More to switch theme, language, auto refresh, tab moving, and grouping rules
-> Add custom grouping rules for exact hosts, host suffixes, or path prefixes
-> Move tabs into the current window when advanced tab moving is enabled
-> Save tabs for later before closing them
-> Export or import tab sessions with window context when you need a snapshot
-> Restore imported sessions into the current window or their original windows
```

Everything runs inside the Chrome extension. No server or build step is required.

---
<br>

## Manual Setup

**1. Clone the repo**

```bash
git clone https://github.com/zarazhangrui/tab-out.git
git clone https://github.com/mrfoolish/tab-out.git
```

**2. Load the Chrome extension**

1. Open Chrome and go to `chrome://extensions`
2. Enable **Developer mode** (top-right toggle)
3. Click **Load unpacked**
4. Navigate to the `extension/` folder inside the cloned repo and select it
| Step | Action |
|------|--------|
| 1 | Open `chrome://extensions` |
| 2 | Toggle **Developer mode** in the top-right corner |
| 3 | Click **Load unpacked** |
| 4 | Select the `extension/` folder in this repo |

**3. Open a new tab**

You'll see Tab Out.
Tab Out will replace the default Chrome new tab page.

---
<br>

## How it works
## Install With A Coding Agent

```
You open a new tab
-> Tab Out shows your open tabs grouped by domain
-> Homepages (Gmail, X, etc.) get their own group at the top
-> Click any tab title to jump to it
-> Close groups you're done with (swoosh + confetti)
-> Save tabs for later before closing them
Send your coding agent this repo:

```text
https://github.com/mrfoolish/tab-out
```

Everything runs inside the Chrome extension. No external server, no API calls, no data sent anywhere. Saved tabs are stored in `chrome.storage.local`.
Say **"install this"**. It can walk you through loading the unpacked extension in about a minute.

---
<br>

## Tech stack
## Tech Stack

| What | How |
|------|-----|
| Extension | Chrome Manifest V3 |
| Storage | chrome.storage.local |
| Sound | Web Audio API (synthesized, no files) |
| Animations | CSS transitions + JS confetti particles |
| Storage | `chrome.storage.local` |
| Tabs | `chrome.tabs` |
| Favicons | Tab favicon URL, Chrome `_favicon` endpoint, and local placeholders |
| Sound | Web Audio API |
| UI | HTML, CSS, vanilla JavaScript |

---
<br>

## License

MIT

---

Built by [Zara](https://x.com/zarazhangrui)
MIT. Fork maintained at [mrfoolish/tab-out](https://github.com/mrfoolish/tab-out). Original project by [Zara](https://x.com/zarazhangrui).
110 changes: 110 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<div align="center">

# Tab Out

[English](./README.md) &nbsp;·&nbsp;
[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
[![Chrome](https://img.shields.io/badge/Chrome%20Extension-MV3-blue)](https://developer.chrome.com/docs/extensions/mv3/)

</div>

<br>

> **掌控你的标签页。** Tab Out 会替换 Chrome 新标签页,展示一个本地运行的标签页面板,
> 按域名分组,适合快速清理、跳转、保存和搬家。

无需服务器、无需账号、无需外部 API。你的标签页数据只保存在本机。

<br>

## 功能

- **域名分组**:用清爽网格按站点展示已打开标签页。
- **Homepages 分组**:Gmail、X、YouTube、LinkedIn、GitHub 首页会归到一个便于清理的分组。
- **一键清理**:支持关闭单个标签页、全部重复项、某个域名分组或全部打开的标签页。
- **嗖声 + 彩纸**:关闭标签页时有音效和粒子反馈。
- **重复检测**:同一页面打开多次会显示 `(2x)` 标记,并提供专门的去重操作。
- **跨窗口跳转**:点击标签页标题即可定位到对应标签页,即使它在其它 Chrome 窗口。
- **高级标签搬家**:开启后可把单个标签页、某个 Tab Out 分组或全部标签页搬到当前窗口。
- **自定义分组规则**:可按精确主机、主机后缀和可选路径前缀归并标签页。
- **稍后列表**:关闭前可把标签页保存到本地 checklist。
- **导入 / 导出会话**:导出当前分组和窗口信息,也可以将导入会话恢复到当前窗口或原窗口。
- **导入 / 导出分组规则**:可在浏览器之间迁移规则,或保存一份本地备份。
- **全局搜索**:一个搜索框覆盖打开的标签页、导入会话和稍后列表。
- **浅色 / 深色 / 跟随系统主题**:可在 More 菜单中切换。
- **英文 / 中文界面**:可在 More 菜单中切换语言,默认英文。
- **稳定的站点图标**:优先使用真实标签页 favicon,失败后回退到 Chrome `_favicon` 和本地占位图。
- **100% 本地**:保存的标签页、导入会话、分组规则、设置、语言、主题和高级开关都存储在 `chrome.storage.local`。

<br>

## 工作方式

```text
打开一个新标签页
-> Tab Out 按域名分组展示所有已打开标签页
-> Homepages(Gmail、X 等)出现在独立分组中
-> 点击标签页标题可跨窗口跳转
-> 在 More 菜单中切换主题、语言、自动刷新、标签搬家和分组规则
-> 按精确主机、主机后缀或路径前缀添加自定义分组规则
-> 开启高级标签搬家后,可把标签页搬到当前窗口
-> 关闭前可保存到稍后列表
-> 需要快照时可导出或导入带窗口信息的标签会话和分组规则
-> 导入会话可恢复到当前窗口,也可按原窗口恢复
```

所有逻辑都在 Chrome 扩展内部运行。不需要服务器,也不需要构建步骤。

<br>

## 手动安装

**1. 克隆仓库**

```bash
git clone https://github.com/mrfoolish/tab-out.git
```

**2. 加载到 Chrome**

| 步骤 | 操作 |
|------|------|
| 1 | 打开 `chrome://extensions` |
| 2 | 打开右上角 **开发者模式** |
| 3 | 点击 **加载已解压的扩展程序** |
| 4 | 选择本仓库中的 `extension/` 文件夹 |

**3. 打开新标签页**

Tab Out 会替换 Chrome 默认新标签页。

<br>

## 使用 AI 编程助手安装

将本仓库地址发给 AI 编程助手:

```text
https://github.com/mrfoolish/tab-out
```

说 **"install this"**,它可以在约 1 分钟内引导你完成加载扩展。

<br>

## 技术栈

| 项目 | 实现 |
|------|------|
| 扩展 | Chrome Manifest V3 |
| 存储 | `chrome.storage.local` |
| 标签页 | `chrome.tabs` |
| 图标 | 标签页 favicon、Chrome `_favicon` endpoint、本地占位图 |
| 音效 | Web Audio API |
| UI | HTML、CSS、原生 JavaScript |

<br>

## 许可证

MIT。本 fork 由 [mrfoolish/tab-out](https://github.com/mrfoolish/tab-out) 维护。原始项目作者为 [Zara](https://x.com/zarazhangrui)。
38 changes: 38 additions & 0 deletions extension/app-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

(function initTabOutAppState() {
function createAppState() {
const state = {
openTabs: [],
importedSession: null,
domainGroups: [],
deferredItemsCache: [],
};

return {
getState() {
return state;
},
setOpenTabs(tabs) {
state.openTabs = Array.isArray(tabs) ? tabs : [];
return state.openTabs;
},
setImportedSession(session) {
state.importedSession = session || null;
return state.importedSession;
},
setDomainGroups(groups) {
state.domainGroups = Array.isArray(groups) ? groups : [];
return state.domainGroups;
},
setDeferredItemsCache(items) {
state.deferredItemsCache = Array.isArray(items) ? items : [];
return state.deferredItemsCache;
},
};
}

window.TabOutAppState = {
createAppState,
};
})();
40 changes: 40 additions & 0 deletions extension/app-state.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');

global.window = global.window || {};
require('./app-state.js');

const {
createAppState,
} = window.TabOutAppState;

test('createAppState owns shared dashboard data without a duplicate ui state branch', () => {
const appState = createAppState();
const state = appState.getState();

assert.deepEqual(Object.keys(state).sort(), [
'deferredItemsCache',
'domainGroups',
'importedSession',
'openTabs',
]);
assert.equal('ui' in state, false);
});

test('createAppState updates dashboard data through focused setters', () => {
const appState = createAppState();

assert.deepEqual(appState.setOpenTabs([{ id: 1 }]), [{ id: 1 }]);
assert.deepEqual(appState.setDomainGroups([{ domain: 'docs.example.com' }]), [{ domain: 'docs.example.com' }]);
assert.deepEqual(appState.setDeferredItemsCache([{ id: 'later-1' }]), [{ id: 'later-1' }]);
assert.deepEqual(appState.setImportedSession({ groups: [] }), { groups: [] });

assert.deepEqual(appState.getState(), {
openTabs: [{ id: 1 }],
importedSession: { groups: [] },
domainGroups: [{ domain: 'docs.example.com' }],
deferredItemsCache: [{ id: 'later-1' }],
});
});
Loading