TabMD is a Chrome MV3 extension that replaces the default new tab page with a local-first Markdown editor. Every new tab gets its own note, identified by a UUID in the URL hash, and all note data stays in chrome.storage.local.
The current implementation is intentionally narrow:
- Local-first by default
- No automatic sync or background upload
- Optional manual Google Drive backup/restore
- One note per tab
- Real-time local note persistence across open UI surfaces
- Popup for recent notes
- Full list page for search, rename, and delete
- Markdown editing with EasyMDE
- Preview mode using EasyMDE's native preview surface with GitHub-flavored Markdown via
marked - Editor and preview surfaces that fill the remaining workspace area inside the new-tab writing canvas
- Preview mode constrained to one active scroll surface so the hidden editor scroller never duplicates the preview scrollbar
- Syntax-highlighted fenced code blocks via
highlight.js, with deterministic plaintext fallback for unknown or unlabeled fences - Automatic note titles derived from the first meaningful line
- Browser tab titles that follow the resolved note title
- Manual title overrides
- Copy current note text directly from the toolbar
- Export current note as a
.mdfile namedtitle-<timestamp>.md - Focus mode that expands the editor to the full workspace while keeping an explicit exit control visible
- Theme setting with
os,light, anddarkmodes - Optional manual Google Drive backup/restore with retention, delete, and restore pagination
- Recent-notes popup limited to a small configurable set of the most recently edited notes
- Recent-notes popup titles truncated with a single-line ellipsis instead of horizontal scrolling
- Full notes page with client-side search across titles and body content
Each note is addressed by the hash on the new tab page:
newtab.html#<uuid>
If the hash is missing, TabMD generates a new UUID with crypto.randomUUID() and updates the current URL in place.
TabMD stores note data in chrome.storage.local and keeps a few additional local-only keys for optional Drive backup metadata:
const STORAGE_KEYS = {
settings: 'tabmd:settings',
notes: 'tabmd:notes'
} as const;
const DRIVE_STORAGE_KEYS = {
driveBackupIndex: 'tabmd:driveBackupIndex',
installId: 'tabmd:driveInstallId',
retentionCount: 'tabmd:driveRetentionCount'
} as const;Notes are stored as an object map rather than an array:
type NoteRecord = {
id: string;
content: string;
title: string | null;
createdAt: number;
modifiedAt: number;
};That shape keeps note load, upsert, and delete operations simple and effectively constant-time by note ID.
Drive backups stay isolated per extension install. Each install gets a stable local installId, and Drive files are written under:
tabmd_backups/<installId>/
Each backup run creates one snapshot folder there, and each note inside that snapshot is uploaded as its own Markdown file using the same title-<timestamp>.md naming pipeline as local export.
That separation lets multiple TabMD installs coexist without mixing backup files in the same Drive folder.
Path: entrypoints/newtab/
Responsibilities:
- Resolve or create the current note ID from
location.hash - Load the note from storage
- Initialize the EasyMDE editor
- Save content and title changes immediately on editor or title edits
- Reconcile with
chrome.storage.onChangedso open surfaces stay in sync - Toggle Editor and Preview tabs through EasyMDE's native preview mode while keeping both surfaces stretched to the remaining workspace area
- Keep preview mode on a single scroll surface so the hidden editor scroller does not remain visible behind rendered Markdown
- Copy the current editor text to the system clipboard with inline feedback
- Export the current note
- Open the options page
Path: entrypoints/popup/
Runtime page: popup.html
Responsibilities:
- Load all notes on popup open
- Rerender when note storage changes while the popup is open
- Select the configured recent-notes popup cap without fully sorting the complete collection
- Render the configured recent-notes popup cap
- Truncate oversized recent-note titles with a single-line ellipsis so the popup never grows a horizontal scrollbar
- Open the selected note in a new tab
- Navigate to the full list page or options page
Path: entrypoints/list/
Runtime page: list.html
Responsibilities:
- Load and sort all notes
- Rerender when note storage changes while the list page is open
- Search across derived titles and note content using cached normalized metadata per page load, while deferring full body normalization until a content query needs it
- Show a snippet for each matching note
- Rename notes
- Delete notes
- Open notes or create a brand-new note
Path: entrypoints/options/
Responsibilities:
- Read and write the theme setting
- Apply the chosen theme immediately
- Show a snackbar after saves
- Connect/disconnect Google Drive for manual backups
- Upload snapshot folders of per-note Markdown files to Drive and restore them on demand
- Load the restore list lazily into a dialog
- Delete individual Drive backups from that dialog
- Manage retention and explicit restore-dialog pagination
Path: entrypoints/background/index.ts
The background worker is intentionally minimal. The popup is wired from the manifest, so there is no action-click routing logic in the worker.
entrypoints/
background/ background service worker
drive/ Google Drive auth, REST API, and backup orchestration
list/ note management page
newtab/ editor surface
options/ settings page
popup/ recent-notes popup
shared/ storage, note, title, and utility helpers
ui/ shared UI helpers
tests/
helpers/ Chrome API mocks and async test helpers
integration/ entrypoint bootstrapping checks
unit/ pure logic and storage tests
docs/
architecture.md
storage.md
CWS_LISTING_DRAFT.md
- WXT for extension bundling
- TypeScript
- EasyMDE
- marked
- highlight.js
- Vitest
- JSDOM
- Playwright
- Biome
- Node.js 18+
pnpm
pnpm installpostinstall runs wxt prepare, so extension types are generated automatically.
pnpm devpnpm buildpnpm zippnpm compile
pnpm lint
pnpm smoke
pnpm testpnpm test:e2eCurrent automated coverage focuses on:
- Title derivation
- UUID generation
- Search and snippet selection
- Large-note search, preview, and markdown-import regressions
- Notes storage CRUD behavior
- Export filename sanitization
- Google Drive auth, Drive REST helpers, backup orchestration, and options-page backup flows
- Background entrypoint loading
- Options entrypoint initialization
See TEST_PLAN.md and TESTING.md for the test plan and local workflow notes.
The extension currently requests:
storageunlimitedStorageidentityhttps://www.googleapis.com/host permission
Google Drive backup is manual and optional. TabMD now uses its own fixed manifest key so it can coexist with nufftabs as a separate unpacked extension. The current baked-in OAuth client ID is still the reference one, so Drive auth must be re-provisioned for TabMD's extension ID before authentication will succeed.
Current TabMD extension ID from the baked-in key:
npgocjgphlckhehmcghiokimajkmmdef
- Open the options page.
- Click
Connect to Google Driveand approve access. - Set
Retentionto the number of backups you want to keep. - Click
Backup nowto upload all notes. - Click
Restore from backupto browse Drive snapshots lazily in the restore dialog. - Use
Previous,Next, and the page-size selector to page through backups. - Click
Restoreto overwrite local notes with the selected snapshot, orDeleteto remove a Drive backup file without restoring it.
Local export uses the same file naming pipeline:
<title>-<timestamp>.md
Example:
test-2026-03-09T13-42-14-254Z.md
- Open TabMD surfaces reflect title and content updates as soon as the note is persisted.
- Multiple tabs pointed at the same note use last-write-wins behavior.
- Search is client-side over the in-memory note list loaded for the page.
- Notes are local to the browser profile unless you explicitly run a manual Drive backup.
- Drive backup is manual only. It is not sync, merge, or background replication.
The repository is no longer a generic WXT scaffold. It now contains a working TabMD implementation centered on a local-first Markdown new tab workflow with optional manual Google Drive backup.