Architecture¶
jott uses a mixin-based architecture to keep files under 300 lines while providing extensive functionality.
Package Layout¶
jot/
├── __init__.py # Dynamic import bridge to jot.py
├── app.py # Main App class + dispatch table
├── _app_navigation_mixin.py # App navigation mixin
├── _dispatch_mixin.py # App dispatch mixin
├── core/ # Task management engine
│ ├── task_manager.py # TaskManager base (8 mixins)
│ ├── id_manager.py # ID generation/management
│ ├── constants.py # Shared constants
│ ├── archive_manager.py # Archive operations
│ └── _*_mixin.py # 10 mixins (crud, delete, persistence, ...)
├── commands/ # Command processing
│ ├── handler.py # CommandHandler base (11 mixins)
│ └── _*_mixin.py # 11 mixins (core, bulk, notes, gcal, ...)
├── ui/ # Display and input
│ ├── display*.py # Task, footer, help, project, archive display
│ ├── formatting.py # Text formatting utilities
│ ├── styles.py # Color and style definitions
│ ├── rendering.py # Buffered output for flicker-free rendering
│ ├── input.py # Keyboard input handling
│ └── picker.py # Fuzzy-search picker widget
├── projects/ # Multi-project support
│ ├── registry.py # ProjectRegistry (auto-discovery)
│ └── backup.py # ProjectBackup
├── categories/ # Category system
│ ├── manager.py # CategoryManager
│ ├── config.py # CategoryConfig
│ └── templates.py # CategoryTemplates
├── integrations/ # External services
│ ├── gcal/ # Google Calendar (auth, events, accounts)
│ └── keywords/ # Keyword automation (handler, config, handlers)
├── mcp/ # Model Context Protocol server
│ ├── server.py # FastMCP server
│ ├── handlers.py # Request handlers
│ └── schemas.py # Data schemas
├── cli/ # CLI subcommands
│ ├── archive.py # Archive management
│ ├── config.py # Configuration commands
│ └── views.py # Project views
└── utils/ # Shared utilities
├── text_utils.py # Text processing
├── date_utils.py # Date handling
└── validation.py # Input validation
Mixin Pattern¶
jott uses Python’s multiple inheritance to compose large classes from focused mixins. Each mixin file targets fewer than 300 lines.
TaskManager composes 10 mixins:
_CrudMixin— create, read, update operations_DeleteMixin— delete with smart current-task reassignment_PersistenceMixin— JSON file load/save_NavigationMixin— cursor movement through task list_MetadataMixin— priority, labels, status_SubtaskMixin— subtask sync from notes_CompressMixin— task list compression_ExportMixin— export to various formats_AgeBacklogMixin— aging and backlog management_IdMigrationMixin— ID scheme migration
CommandHandler composes 11 mixins covering core commands, bulk operations, notes, transfers, AI analysis, Google Calendar, and more.
App composes 2 mixins for navigation and dispatch, plus contains the main
event loop and the _QUICK_ADD_SIMPLE dispatch table that maps key codes to
CommandHandler methods.
Main Event Loop¶
The App class in app.py runs the main loop:
If
_needs_renderis set, clear screen and render the task display inside abuffered_output()context manager (all output captured in aStringIObuffer and written atomically in onesys.stdout.write()call to prevent tearing)Read a single keypress (with ~1s timeout for auto-refresh)
On timeout, check file mtime — only set
_needs_renderif file changedOn keypress, set
_needs_renderand dispatch to the appropriate handlerRepeat
This design eliminates idle flickering (no redraws when nothing changed) and partial-frame tearing (atomic writes).
In quick-add mode, printable characters accumulate in a buffer; Enter submits the task. Special keys (Shift+combos, Ctrl+combos) are dispatched directly.
In command mode, single keypresses map to commands (d for delete,
e for edit, etc.).
Data Model¶
Tasks are stored as JSON in .jot.json:
{
"tasks": [
{
"id": 1,
"text": "Task description",
"done": false,
"current": true,
"priority": "normal",
"labels": ["bug"],
"notes": "Detailed notes...",
"created_at": "2025-01-01T00:00:00"
}
],
"archived": []
}
The ProjectRegistry maintains ~/.jot-projects.json mapping project
names to filesystem paths. Auto-discovery scans ~/projects/ for
directories containing .jot.json files.
Dynamic Import Bridge¶
jot/__init__.py uses importlib.util to load the root-level jot.py
script as a module, bridging the standalone script entry point with the
package structure:
_jot_script_path = Path(__file__).parent.parent / 'jot.py'
_spec = importlib.util.spec_from_file_location("_jot_script", _jot_script_path)
_jot_script = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_jot_script)
main = _jot_script.main
This allows from jot import main to work while the actual main()
function lives in the standalone script. This is relevant for Sphinx autodoc
configuration — optional dependencies must be mocked before any import of the
jot package.