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:

  1. If _needs_render is set, clear screen and render the task display inside a buffered_output() context manager (all output captured in a StringIO buffer and written atomically in one sys.stdout.write() call to prevent tearing)

  2. Read a single keypress (with ~1s timeout for auto-refresh)

  3. On timeout, check file mtime — only set _needs_render if file changed

  4. On keypress, set _needs_render and dispatch to the appropriate handler

  5. Repeat

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.