Technical documentation for developers or LLMs working on Ignat's Copy of TK's Tree Style Tab Outliner (ICTKTSTO) – a browser extension that combines tree-style tabs, bookmarks, notes, and session management.
TKTSTO is a Manifest V3 browser extension that provides:
- Tree-style tab management in a sidebar panel
- Persistent session storage across browser restarts
- Cross-browser compatibility (Chrome, Firefox, Edge, Brave, Vivaldi)
- Local backups and future sync capabilities
When working on tasks, fix obvious logic errors you notice along the way, and add tests for them when practical.
License: AGPL-3.0-or-later
Language: Vanilla JavaScript (ES modules, no build tools or npm dependencies)
┌─────────────────────────────────────────────────────────────────┐
│ Browser Extension │
├─────────────────────────────────────────────────────────────────┤
│ Service Worker (bkgd/) │ Side Panel UI (view/) │
│ ───────────────────────────── │ ─────────────────────────────│
│ • bkgd.js (main entry) │ • sidepanel.html │
│ • treestore.js (TreeStore) │ • view.js (entry point) │
│ • nodestore.js (NodeStore) │ • treeview.js (TreeView) │
│ • idb.js (IndexedDB) │ • nodeview.js (NodeView) │
│ • sidepanel.js │ │
│ • new-user.js (tutorial) │ │
├─────────────────────────────────┴───────────────────────────────┤
│ Shared Code (common/) │
│ • tree.js (Tree base class) • node.js (Node base class) │
│ • common.js (utilities) • events.js (event helpers) │
│ • dialog.js (modal dialogs) • mutex.js (async locks) │
│ • id-generator.js • base32.js (encoding) │
├─────────────────────────────────────────────────────────────────┤
│ api.js – Browser compatibility shim (chrome vs browser API) │
└─────────────────────────────────────────────────────────────────┘
-
Class Inheritance Chain:
Node(common/node.js) →NodeStore(bkgd/) → persists to IndexedDBNode(common/node.js) →NodeView(view/) → renders DOMTree(common/tree.js) →TreeStore(bkgd/) → manages background treeTree(common/tree.js) →TreeView(view/) → manages UI tree
-
Communication: Service worker and side panel communicate via
chrome.runtime.sendMessage()and ports. Theemit()function incommon/common.jshandles message passing. -
Storage: Tree data persists in IndexedDB (managed by
bkgd/idb.js). Configuration useschrome.storage.local.
/
├── api.js # Browser API shim (chrome vs browser)
├── manifest.json # Chrome/Chromium manifest
├── manifest-ff.json # Firefox-specific manifest
├── Makefile # Build commands
├── make-zip.sh # Packaging script
│
├── bkgd/ # Background service worker
│ ├── bkgd.js # Main entry point, event listeners
│ ├── treestore.js # TreeStore class (Tree + IndexedDB persistence)
│ ├── nodestore.js # NodeStore class (Node + DB save/load)
│ ├── idb.js # IndexedDB wrapper (TKTSTO database)
│ ├── sidepanel.js # Side panel lifecycle management
│ └── new-user.js # Tutorial node generation
│
├── common/ # Shared code (used by both bkgd and view)
│ ├── common.js # Logging, emit(), date formatting, utilities
│ ├── tree.js # Base Tree class
│ ├── node.js # Base Node class (1400+ lines)
│ ├── events.js # Event name builder for keyboard/mouse
│ ├── dialog.js # Modal dialog helpers
│ ├── mutex.js # Async mutex for concurrency control
│ ├── id-generator.js # Unique ID generation
│ └── base32.js # Base32 encoding for IDs
│
├── view/ # Side panel UI
│ ├── sidepanel.html # Side panel HTML
│ ├── sidepanel.css # Side panel styles
│ ├── view.js # Entry point, initializes TreeView
│ ├── treeview.js # TreeView class (UI tree, keyboard/mouse handling)
│ └── nodeview.js # NodeView class (DOM rendering for nodes)
│
├── options/ # Extension options page
│ ├── options.html
│ └── options.js
│
├── themes/ # CSS themes
│ ├── tk.css # Base theme
│ ├── tk-day.css # Light theme variant
│ └── tk-night.css # Dark theme variant
│
├── docs/ # Help pages
│ └── *.html
│
├── img/ # Icons (16, 32, 48, 64, 128 px)
│
├── bin/ # Development scripts
│ ├── firefox-sign.sh # Sign for Firefox (requires AMO credentials)
│ ├── update-version.sh
│ └── *.py # Utility scripts
│
├── tests/ # Unit tests (browser-based)
│ ├── dom-safety.test.html # DOM safety/XSS prevention tests
│ └── tree-node.test.html # Tree/Node behavior tests
│
└── build/ # Generated during build (git-ignored)
└── ...
The fundamental unit of the tree. Each node represents a tab, window, folder, or note.
Key Properties:
id– Unique identifier (base32 encoded)type–''(generic),'window', or'tab'parent/nodes[]– Tree structurelabel/note/title/url– ContentwindowId/tabId– Browser attachment (when loaded)expanded/loaded/active/marked– State flagscheckbox/checkboxPx– Task statusctime/mtime/atime/ltime– Timestamps
Key Methods:
toDict()/fromDict()– SerializationaddChild()– Create child nodedeleteSelf()– Remove node and close associated tabmoveTo()– Relocate in treeload()/unload()– Open/close browser tabsetMarked()– Mark for batch operations
Container for all nodes with tree-wide operations.
Key Properties:
root– Root nodenodes{}– Cache of all nodes by IDmarkedNodes[]– Currently marked node IDsdictable[]– Fields to serialize
Key Methods:
loadTreeFromBkgd()– Fetch tree state from service workerunmarkAll()– Clear all marksnodeMarkChanged()– Track mark state
Background tree with IndexedDB persistence. Extends Tree.
UI tree with DOM rendering and input handling. Extends Tree.
Key Features:
- Keyboard bindings (
keyBindingsmap) - Mouse bindings (
mouseBindingsmap) - Drag-and-drop support
- Cursor navigation
Specialized Node subclasses for background persistence and UI rendering respectively.
- Browser loads service worker (
bkgd/bkgd.js) Bkgd.init()runs:- Registers all event listeners immediately (before async work)
- Loads config from
storage.local - Creates
TreeStoreand loads from IndexedDB - Merges open browser windows/tabs into tree
- Generates tutorial nodes if first run
- When side panel opens:
view/view.jscreatesTreeViewTreeViewrequests full tree from service worker- Renders tree to DOM
Communication uses emit() which wraps chrome.runtime.sendMessage():
// From view to background
const response = await emit('bkgd_getTree');
// Message names follow pattern:
// - bkgd_* : requests to background
// - tree_* : tree state notificationsDatabase name: TKTSTO
| Object Store | Key | Purpose |
|---|---|---|
Nodes |
node ID | Individual node data (JSON) |
Snapshots |
session name | Full tree snapshots |
Transactions |
key | Transaction log (future) |
The api.js shim provides cross-browser support:
export const isFirefox = (typeof browser !== 'undefined');
export const isChrome = (!isFirefox);
export const api = isFirefox ? browser : chrome;Tested Browsers:
- Firefox ESR 115, 128
- Chromium 134+
- Edge 136+
- Vivaldi 7.3+
- Brave 1.78+
Key Differences:
- Firefox uses
browser.*API, Chromium useschrome.* - Firefox requires separate manifest (
manifest-ff.json) - Some APIs missing in older Firefox (e.g.,
windows.onBoundsChanged)
makeandzip(for Firefox builds)- Git (for source management)
# Build for both browsers
make all
# Build Firefox .zip only
make firefox-zip
# Build Chrome/Chromium .zip only
make chrome-zip
# Sign Firefox extension (requires AMO credentials in .env)
make firefox-sign
# Find TODOs in source
make todoCreate .env with AMO credentials:
JWT_ISSUER=your_api_key
JWT_SECRET=your_api_secret
Get credentials from https://addons.mozilla.org/developers/
Chrome/Chromium:
- Go to
chrome://extensions - Enable "Developer mode"
- Click "Load unpacked"
- Select repository root folder
Firefox:
- Run
make firefox-zip - Go to
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on"
- Select
dist/tktsto-*.zip
Stored in chrome.storage.local:
| Key | Type | Description |
|---|---|---|
clientId |
string | Alphanumeric identifier for this client; non-alphanumerics stripped |
theme |
string | Selected theme name |
localBackupInterval |
number | Auto-backup interval in minutes |
humanFriendlyBackups |
boolean | Pretty-print backup JSON |
backupOnStartup |
boolean | Run overdue backups on startup |
expandedRowPrefix |
boolean | Show expand/collapse indicators |
openWindowOnRootMove |
boolean | Open new window when moving subtree to root |
openWindowOnRootLoadTopmost |
boolean | Wrap top-most ungrouped parent when loading from root |
reorderTabsOnCreate |
boolean | Reorder new tabs to match tree layout |
reconcileIntervalMinutes |
number | Periodic reconcile interval in minutes (0 disables) |
opsBacklogWarnThreshold |
number | Pending ops threshold for backlog warning (0 disables) |
moveDownIntoExpandedSibling |
boolean | Shift+Down nests into expanded sibling when enabled |
moveUpIntoExpandedSibling |
boolean | Shift+Up nests into expanded sibling when enabled |
dropTextNoteMode |
string | Drop text into notes by prepending or appending (prepend/append) |
From manifest.json:
| Permission | Purpose |
|---|---|
sidePanel |
Show tree in browser sidebar |
storage |
Persist settings |
downloads |
Save backup files |
tabs |
Monitor and control browser tabs |
alarms |
Schedule periodic backups |
favicon |
Display tab favicons |
Optional:
clipboardRead/Write– Copy/paste operations<all_urls>– Access tab content (for notes feature)
-
Add binding in
view/treeview.js→keyBindingsobject:'Ctrl+X': 'myNewAction',
-
Implement handler method in
TreeViewclass:async myNewAction() { // implementation }
- Add property to
Nodeconstructor (common/node.js) - Add to
dictable[]array inTree(common/tree.js) - Update
NodeView.$render()if it affects display - Update
NodeStoreif it needs persistence
- Add UI in
options/options.html - Add load/save logic in
options/options.js - Read setting where needed via
api.storage.local.get()
-
Service Worker Logs: Open browser DevTools → Application → Service Workers → Inspect
-
Side Panel Logs: Right-click side panel → Inspect
-
IndexedDB Inspection: DevTools → Application → IndexedDB → TKTSTO
-
Enable Verbose Logging: The
debug()function incommon/common.jslogs to console -
Message Tracing: All
emit()calls log their message name except forbkgd_ping
This project uses vanilla JavaScript ES modules with no transpilation or bundlers. Follow these conventions to maintain consistency.
// Use strict mode in every file
"use strict";
// ES module imports at top of file, absolute paths from root
import { api, isChrome, isFirefox } from '/api.js';
import { log, debug, warn, error } from '/common/common.js';
// Class-based OOP pattern
export class MyClass {
constructor() {
// Initialize properties
this.myProperty = null;
}
async myMethod(args) {
// Implementation
}
}| Type | Convention | Example |
|---|---|---|
| Classes | PascalCase | TreeView, NodeStore |
| Functions/Methods | camelCase | loadTreeFromBkgd(), deleteSelf() |
| Variables | camelCase | markedNodes, windowId |
| Constants | camelCase or UPPER_SNAKE | jsonSchema, dbSchemaNum |
| DOM elements | $ prefix |
this.$row, this.$nodes |
| Private-ish | no prefix (JS has no private) | document intent via comments |
| Event handlers | on prefix |
onTabCreated(), onMessage() |
Each file should:
- Start with copyright header and SPDX license identifier
- Have
"use strict";directive - Import dependencies (absolute paths from extension root)
- Export classes/functions at definition
- Keep related functionality together
// example/myfile.js: brief description
// Copyright (C) 2025 Contributor Name
// SPDX-License-Identifier: AGPL-3.0-or-later
"use strict";
import { api } from '/api.js';
import { log } from '/common/common.js';
export class MyClass { ... }
export function myHelper() { ... }- Prefer
async/awaitover.then()chains - Use
Promisefor IndexedDB and browser API callbacks - Handle errors with try/catch or let them propagate
async loadData() {
try {
const result = await api.storage.local.get('key');
return result.key;
} catch (err) {
error('Failed to load:', err);
return null;
}
}- Use
//for single-line comments - Use JSDoc-style for complex functions (optional but encouraged)
- Mark incomplete work with
TODO:orFIXME: - Run
make todoto find all TODOs in the codebase
// TODO: implement undo functionality
// FIXME: race condition when rapidly clicking
/**
* Move node to a new parent at specified index.
* @param {Node} destParent - Target parent node
* @param {number} destIndex - Position in parent's children
* @param {Object} args - Reason and metadata
*/
async moveTo(destParent, destIndex, args) { ... }No automated formatter is configured. Follow these manual guidelines:
- 2 spaces for indentation (no tabs)
- Space after keywords:
if (,for (,while ( - Space around operators:
a + b,x === y - No space before function parens:
myFunc() - Opening brace on same line
// Good
if (condition) {
doSomething();
} else {
doOther();
}
// Bad
if(condition){
doSomething();
}- Soft limit: ~80-100 characters
- Break long lines at logical points
// Break long function calls
const result = await someVeryLongFunctionName(
firstArgument,
secondArgument,
{ option: value }
);
// Break long conditions
if (condition1 &&
condition2 &&
condition3) {
// ...
}- Prefer single quotes for strings:
'hello' - Use template literals for interpolation:
`Value: ${x}` - Use backticks for multi-line strings
No linter is currently configured. If adding one, consider:
# Install (if npm is added later)
npm init -y
npm install --save-dev eslint
# Suggested .eslintrc.js
module.exports = {
env: {
browser: true,
es2022: true,
webextensions: true,
},
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
rules: {
'no-unused-vars': 'warn',
'no-undef': 'error',
'semi': ['error', 'always'],
'quotes': ['warn', 'single'],
},
globals: {
chrome: 'readonly',
browser: 'readonly',
},
};Before committing, verify:
- No
console.log()left in production code (usedebug(),log(), etc.) - All variables declared with
constorlet(nevervar) - Async functions have proper error handling
- Browser APIs accessed via
api.*shim (notchrome.*orbrowser.*directly) - New files have copyright header and
"use strict";
Browser-based tests live in tests/. Run them with make test (starts a local HTTP server and opens the pages), or open the test pages via http:// (module-based tests won't run from file:// due to browser CORS rules).
| Test File | Purpose |
|---|---|
dom-safety.test.html |
Tests for XSS prevention - verifies that DOM rendering uses safe methods (textContent, createElement) instead of innerHTML |
tree-node.test.html |
Tests for Tree/Node behavior, plus shared utilities and options form sanitization |
client-id-flow.test.html |
Tests for the background client ID handler, storage updates, and IdGenerator state |
merge-open-windows.test.html |
Tests for mergeOpenWindowsIntoTree matching across browsers |
treeview-actions.test.html |
Tests for TreeView actions (delete/unwrap/move behavior) |
tree-node.test.html, merge-open-windows.test.html, and treeview-actions.test.html support ?env=firefox and ?env=chrome to force browser-specific rules; otherwise they auto-detect via user agent.
tests/node/ provides a Node-based runner for core modules with API stubs.
- Run
make test-nodefor Node-based checks; the Make target wires intests/node/loader.mjsso extension-root imports like/api.jsresolve correctly under Node. - Run
make coverageto collect V8 coverage and update the badge inreadme.md.
Tests use a simple framework defined in each test file:
test('description of what is being tested', () => {
// Arrange
const $elem = document.createElement('div');
// Act
$elem.textContent = '<b>should be escaped</b>';
// Assert
assert($elem.querySelector('b') === null, 'HTML should not be parsed');
assertEqual($elem.textContent, '<b>should be escaped</b>');
});For browser tests that touch api.*, mock window.browser/window.chrome before importing modules (see tests/client-id-flow.test.html).
For Node-based tests, follow tests/node/run-tests.mjs and update coverage if you add new core cases.
Guidelines for test payloads:
- Avoid using actual
alert()calls in test strings - When testing
<script>tag escaping, split the string:'<scr' + 'ipt>'to prevent the browser from interpreting it as closing the main script block - Use harmless HTML tags (
<b>,<em>,<div>) to test escaping behavior - Use global flags (
window.testFlag) to verify code didn't execute
Before releasing:
-
Basic Functionality
- Extension loads without errors
- Side panel opens and displays tree
- Client ID sanitizes on blur and rejects empty input in Options
- Can create, edit, delete nodes
- Can load/unload tabs
- Drag-and-drop works
- Keyboard shortcuts work
-
Persistence
- Tree survives browser restart
- Backups can be created and downloaded
- Data loads correctly from IndexedDB
-
Cross-Browser
- Test in Chrome/Chromium
- Test in Firefox (both ESR versions)
- Test in at least one other browser (Edge, Brave, Vivaldi)
-
Edge Cases
- Many tabs (100+) performs acceptably
- Deep nesting works correctly
- Special URLs handled gracefully
// In side panel console - inspect tree state
const tree = document.querySelector('#tree-view').__tree;
console.log(tree.nodes);
console.log(tree.markedNodes);
// In service worker console - inspect background state
console.log(bkgd.tree.nodes);Areas that could benefit from additional test coverage:
- Unit Tests - Expand core class coverage in Node-based tests
- Integration Tests - Grow message-passing coverage beyond client ID flows
- E2E Tests - Use Puppeteer/Playwright for browser automation
New test files should follow the pattern established in tests/dom-safety.test.html:
- Self-contained HTML file with embedded
<script type="module"> - Uses the simple
test(),assert(),assertEqual()framework - Renders results to the page and logs to console
- Can be run by simply opening in a browser (use
http://if importing modules)
VS Code Recommended Extensions:
- ESLint (if configured)
- EditorConfig (if
.editorconfigadded) - Debugger for Chrome/Firefox
Recommended settings.json:
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"files.eol": "\n",
"javascript.preferences.quoteStyle": "single"
}- Make changes to source files
- Reload extension:
- Chrome: Click refresh icon on
chrome://extensions - Firefox: Click "Reload" on
about:debuggingpage
- Chrome: Click refresh icon on
- Reload side panel: Close and reopen, or right-click → Inspect → Ctrl+R
- Check for errors in both DevTools consoles (service worker + side panel)
- Service worker changes require extension reload
- Side panel CSS/JS changes often only need panel reload
- IndexedDB schema changes may require clearing extension data
-
Service Worker Lifecycle
- Service workers can be killed by browser when idle
- Use
emit()with retry logic (already implemented) - Register event listeners synchronously in
init()
-
Message Serialization
- Objects with methods can't be sent via
sendMessage() - Always use
toDict()before sending Node/Tree objects - Restore with
fromDict()on receiving end
- Objects with methods can't be sent via
-
Cross-Browser Differences
- Always use
api.*instead ofchrome.*orbrowser.* - Check for API existence before using:
if (api.windows.onBoundsChanged) - Firefox manifest differs from Chrome manifest
- Always use
-
IndexedDB Transactions
- Transactions auto-close when no pending operations
- Can't reuse transactions across await boundaries
- Each operation creates a new transaction (by design)
// Check extension state
api.storage.local.get(null).then(console.log);
// Force backup
emit('bkgd_backupSession');
// Get all node IDs
Object.keys(tree.nodes);
// Find node by partial label
Object.values(tree.nodes).filter(n => n.label?.includes('search'));- Open DevTools → Performance tab
- Record while performing action
- Look for long tasks, layout thrashing
- Key areas to watch:
- Tree rendering (
$render()) - IndexedDB operations
- DOM updates during drag-and-drop
- Tree rendering (
trunk- Main development branch- Feature branches as needed
Follow the Conventional Commits specification:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
| Type | Description |
|---|---|
feat |
New feature or functionality |
fix |
Bug fix |
docs |
Documentation changes only (README, ChangeLog, docs/, and tutorial copy in bkgd/new-user.js when user-facing) |
style |
Code style (formatting, whitespace, semicolons) |
refactor |
Code change that neither fixes a bug nor adds a feature |
perf |
Performance improvement |
test |
Adding or updating tests |
build |
Build system or external dependencies |
ci |
CI configuration files and scripts |
chore |
Other changes that don't modify src or test files |
revert |
Reverts a previous commit |
Use to specify what area of the codebase is affected:
bkgd- Background service workerview- Side panel UItree- Tree/Node core logicidb- IndexedDB storageoptions- Options pagethemes- CSS themesmanifest- Extension manifestbuild- Build scripts
# Feature
feat(view): add keyboard shortcut for marking all siblings
# Bug fix
fix(bkgd): prevent duplicate tabs when restoring crashed session
# Bug fix with issue reference
fix(tree): handle orphaned nodes on startup
Closes #42
# Breaking change (use ! or BREAKING CHANGE footer)
feat(idb)!: change IndexedDB schema to v2
BREAKING CHANGE: Existing data requires migration.
Run the migration script before updating.
# Documentation
docs: update AGENTS.md with testing guidelines
# Refactor with scope
refactor(node): extract checkbox logic into separate method
# Style change
style: fix inconsistent indentation in treeview.js
# Chore
chore: update manifest version to 0.0.2.0- Use imperative mood in description: "add feature" not "added feature"
- Don't capitalize first letter of description
- No period at the end of the subject line
- Keep subject line under 72 characters
- Separate subject from body with a blank line
- Use body to explain what and why (not how)
- Reference issues in footer when applicable
- Do not hard-wrap body lines; keep paragraphs as single lines unless a list item or code block
For complex changes, include a body:
fix(tree): prevent data loss when moving nodes between windows
The previous implementation could lose child nodes when moving a parent node to a different window during high tab activity.
This fix:
- Adds mutex lock during move operations
- Saves all affected nodes before emitting events
- Validates parent-child relationships after move
Closes #123
- Code follows style conventions
- No debug statements left in
- Tested in at least one browser
-
make todochecked for any new TODOs added - Build still works:
make all
- Pinned tabs not supported (for now)
- Tab groups not supported (for now)
- Vivaldi stacked tabs incompatible
- Extension shortcuts affect all open views in same window
- Firefox cannot access
file:,about:, or other extension URLs
- README: Project overview, installation, usage
- ChangeLog.md: Version history and known issues
- docs/: User-facing help documentation
- Original inspiration: Tabs Outliner (proprietary, now replaced)