AI News
Loading...

Jotty

On-Device AI macOS 26+ By Sol AI November 2026 ~18 min read

Jotty is a native macOS writing app with an AI co-author that never phones home. It's a real SwiftUI + AppKit application — Markdown editor, book/chapter library on disk, a chat pane called Ask Jotty built on Apple's FoundationModels framework, and a Tab-to-fix proofreader that runs a fast local pass before it ever touches the model. No API keys, no accounts, no cloud inference.

✍️ 🍎

Jotty

Native macOS Writing App · Apple FoundationModels · MIT Licensed

What is Jotty?

Jotty is a desktop Markdown word processor for macOS 26 and later, written in Swift 6 with SwiftUI for the shell and AppKit (NSTextView) for the actual text editing surface. It's built around one idea: the AI should be a co-author in the room, not a chatbot in a separate tab.

Under the hood, every AI feature — the proofreader, the copyeditor in Review mode, and Ask Jotty — talks to SystemLanguageModel from Apple's FoundationModels framework. That model runs entirely on the Apple Silicon Neural Engine. No inference server, no API token, no bill.

Your library is plain files on your disk. A book is a folder. A chapter is a JSON metadata file plus a Markdown file. If Jotty disappeared tomorrow, your writing would still be readable in any text editor. That's not a feature bolted on — it's the architecture.

📚 Books & Chapters

A real library, not a flat document list. Books contain chapters; each chapter is a .md file plus a .json metadata sidecar, stored under ~/Documents/Jotty.

🤖 Ask Jotty

A chat pane that reads your current chapter for style, tone and POV, then returns up to 5 distinct example passages with a one-line note on each. Pick one, insert it.

⌨️ Tab to Fix

Put the cursor in a sentence, hit Tab. A fast deterministic proofreader fixes the obvious things, then Apple Intelligence handles grammar, spelling and tone.

🔍 Review Mode

An in-depth copyeditor pass. Up to 5 suggestions across 8 categories — spelling, grammar, punctuation, style, clarity, word choice, tone, readability — each with a plain-language reason.

🧰 Tool Calling

Ask Jotty can call addText (insert at start, end, after cursor, or replace all) and fetchURL (pull the body of a public http(s) page, capped at 100 kB).

🅰️ Dyslexia-Friendly Fonts

Research-backed faces (BDA + UPF study) and purpose-built dyslexia fonts, with sizes from 12pt to 32pt. Every choice carries its own attribution.

📤 Five Export Formats

PDF (built in), DOCX, EPUB, Plain Text and Markdown. Export the current chapter or the entire book in one go.

🖼️ Gallery & Stats

A card gallery of every chapter, plus a stats popover with word count, sentence count, reading time, punctuation breakdown and most-used words.

Privacy Promise

Every AI call goes to SystemLanguageModel.default — Apple's on-device model. Your chapters never leave the machine. No account, no API key, no telemetry, no analytics endpoint. The only network activity Jotty can perform is the fetchURL tool, which fires only when the model decides it needs a reference, and only for http(s) URLs it was asked to look up.

Installation

Jotty ships as source. There is no prebuilt .app download — the build is two commands, and the project file is generated by xcodegen from project.yml.

What You Need

  • macOS 26 or later — the deployment target is macOS 26.0, and the AI features need FoundationModels, which requires Apple Intelligence.
  • Xcode 26+ — the project targets Swift 6.
  • xcodegen — brew install xcodegen. It turns project.yml into Jotty.xcodeproj.
  • pandoc (optional) — brew install pandoc. Only needed for DOCX, EPUB and Plain Text export. PDF and Markdown export work without it.
# Install the only hard dependency
brew install xcodegen

# Optional, for DOCX / EPUB / TXT export
brew install pandoc

Build It

# Clone
git clone https://github.com/TheSolai/jotty.git
cd jotty

# Build the Release app (this also runs xcodegen generate)
bash build.sh

# Or: build, then run the 209-test suite
bash build.sh all

The finished app lands at build/Build/Products/Release/Jotty.app. The script ad-hoc signs it (codesign --force --deep --sign -) and prints the open command for you.

# Launch the build you just made
open build/Build/Products/Release/Jotty.app

Run the Tests

Jotty has 209 unit tests across 8 test files. The test target uses @testable import Jotty and is hosted by the app, so it must run in Debug configuration — the Release build is closed to testability.

# Via the build script (recommended — sets Debug + ENABLE_TESTABILITY)
bash build.sh test

# Or directly
xcodebuild test -scheme JottyTests -destination 'platform=macOS'

What's actually under test

LibraryStoreTests (42), RegressionGuardTests (40), LocalProofreaderTests (27), CopyEditorTests (25), AskJottyTests (22), StatsTests (20), SentenceDetectorTests (19) and EditorCursorTests (14). The suite covers the library's persistence rules, the deterministic proofreader's checks, stats maths and the sentence detector's boundary handling.

First Launch

On launch, Jotty creates its library folder and, if there's nothing there, drops you into an empty state with one button: Create your first book.

Jotty
┌─ Sidebar ─────────────┐┌─ Editor ────────────────────────────┐
│ [ + New book  ⇧⌘N ]  ││ Chapter title            ● saved     │
│                       ││ 1,204 words · 6 min read              │
│ ▸ List  Cards  Review ││                                      │
│ ▸ Ask Jotty           ││ The road out of town was wet and…    │
│                       ││                                      │
│  My Novel             ││ Ln 12 · Col 4 · 8,412 chars           │
│   └ Chapter One       │└──────────────────────────────────────┘
└───────────────────────┘

Make a Book, Make a Chapter

  1. Click New book (or press ⇧⌘N). Jotty creates the book folder, and a first chapter inside it.
  2. Press ⌘N to add another chapter. Right-click any chapter to Rename, Duplicate, Open in Review, Open in Ask Jotty, Copy as Markdown, Reveal in Finder or Delete.
  3. Start typing. That's it — the editor is live immediately.

The Magic: It Names Itself

Leave a book called "Untitled book" and the sidebar becomes a sea of identical grey rows. Jotty fixes that automatically. As soon as a chapter has a usable first line, the chapter title becomes that line (trimmed, markdown symbols stripped, capped at 60 characters with an ellipsis). Empty chapters get a date stamp like Notes — 15 Oct, 07:02; empty books get Book — 15 Oct 2026.

Crucially, this only ever replaces Jotty's own placeholder titles. A title you typed is never overwritten.

Four Ways to Look

The sidebar toolbar switches the browsing mode:

Mode What you see Good for
List The classic sidebar tree of books and chapters Normal writing
Cards A masonry gallery of every chapter, with title, preview snippet, book name and relative date Seeing the whole project at a glance
Review The copyeditor's panel — a temporary detour, with a Back to Write button Polish passes
Ask Jotty The chat pane Ideation and drafting

Saving, Visibly

The editor header carries a live SavingIndicator: a tiny coloured dot plus a label that reflects LibraryStore.saveStatus — saved, unsaved, saving, fixing, or an error message. When everything is quiet it fades to invisible so the header doesn't shift as you type.

Saves are debounced by 700 ms. Jotty flushes any pending save before it swaps chapters, so switching chapters mid-sentence never loses the outgoing chapter's last words.

Where the files live

~/Documents/Jotty/library.json holds the book list. Chapters live at ~/Documents/Jotty/books/{bookId}/chapters/{chapterId}.md with a matching {chapterId}.json metadata file. Right-click a chapter → Reveal in Finder to jump straight there, or Reveal Library in Finder (⇧⌘R) for the whole folder.

The Ask Jotty Workflow

Ask Jotty is the co-author sitting next to you. It's not a generic chat window bolted onto a text editor — it reads the chapter you're writing and is instructed to match its style, tone and point of view before it says anything.

Opening It

  • Click the Ask Jotty mode button in the sidebar toolbar.
  • Or right-click a chapter → Open in Ask Jotty, which selects that chapter first so the AI has the right context.
  • Press Esc or click Back to Write to return.

What Happens When You Send a Request

  1. Jotty builds a prompt containing the current chapter's text (capped at 4,000 characters, with [...truncated for length...] appended if it overflows), your request, and any style hints.
  2. The model generates a structured response via FoundationModels' @Generable types: a short explanation (1–3 sentences) plus up to 5 examples.
  3. Each example is a self-contained passage (40–200 words) paired with a one-sentence styleNotes note explaining what makes that take distinct and how it matches your request.
  4. You click Insert on the example you like. It's appended to the end of the current chapter and the chat confirms the insert with a character count.
Ask Jotty
┌─ Ask Jotty ────────────────────────────────────────────┐
│  1,204 words — Jotty reads this for style/tone/POV      │
│                                                        │
│  You: Give me three ways to open chapter two.          │
│                                                        │
│  Jotty: Three angles that all keep the clipped,        │
│  present-tense voice of chapter one:                   │
│                                                        │
│  Example 1 of 3 · doc.text                             │
│  "The road out of town was wet and the headlights…"   │
│  Opens on movement rather than place.                  │
│  [ Insert ]                                            │
│                                                        │
│  Example 2 of 3 · doc.text                             │
│  "Nobody had said the name out loud in eleven years."  │
│  Opens on the silence instead of the scene.             │
│  [ Insert ]                                            │
│                                                        │
│  Example 3 of 3 · doc.text                             │
│  "There is a version of this story where nobody…"      │
│  Opens on withheld information.                        │
│  [ Insert ]                                            │
│                                                        │
│  [ Stop ]                            [ Send ⌘↩ ]        │
└────────────────────────────────────────────────────────┘

The Two Tools

Ask Jotty can reach for two tools, and it decides for itself when to use them:

Tool What it does Guards
addText Inserts text into the current chapter at start, end, afterCursor or replaceAll, with sensible blank-line padding. Rejects any position outside the four legal values, and refuses empty text.
fetchURL Fetches the body of a public page so the AI can ground a factual passage in real reference material. http(s) only, 100 kB response cap, 5 second timeout.

The AI's own instructions are strict about one thing: never invent sources or URLs. If it cites something, it had to fetch it. And it won't call addText just to show you an example — you pick and insert.

Conversation Controls

  • New chat (the ➕ button) — drops the session so the next message starts with a clean slate.
  • Stop (⌘.) — cancels the in-flight generation. The Send button turns into a red Stop button while it's working.
  • Send (⌘↩) — sends the message.
  • Esc — back to the editor.

Context overflow is handled — but be nice to the model

Jotty detects the FoundationModels "too many tokens" error, resets the session and retries once automatically. If even a fresh session overflows, that means the chapter alone is too long to send at once — Jotty tells you to ask about a specific section. Chapter context is capped at 4,000 characters for exactly this reason.

Tab-to-Fix and the Local Proofreader

This is the feature Jotty is built around. Put the cursor inside a sentence. Press Tab. The sentence gets fixed.

How the Keypress Reaches the App

Plain Tab inside the editor is intercepted by a custom NSTextView subclass that overrides insertTab(_:) — AppKit calls that method specifically for the Tab key, which is far more reliable than a raw keyDown listener. Jotty also installs a local key-event monitor as a fallback for any case the override misses.

Note that Tab is deliberately not bound in the menu system. A system-level Tab shortcut would shadow the editor's handler and silently swallow the keystroke — a bug that would be genuinely baffling to debug. The menu offers ⌥⌘Tab ("Fix Current Sentence") as an explicit alternative, which posts the same notification.

The Two-Stage Pipeline

One Tab press runs two stages, in order:

Stage 1 — The Local Proofreader (microseconds)

LocalProofreader is a deterministic, no-AI engine. It runs in a fixed-point loop (up to 5 passes, so fixes that create new problems get settled) and catches the things a machine should never send to a neural network:

  • Double spaces — hello world → hello world. Leading indentation is preserved; tabs and newlines are untouched.
  • Double words — the the → the.
  • Standalone lowercase "i" — i went → I went, including contractions (i'm, i've, i'll, i'd).
  • Space before punctuation — hello . → hello.
  • Trailing whitespace — trimmed before a hard newline or end of string.
  • Common typos — a built-in table covering teh, yesturday, recieve, definately, occured, seperate, untill, wich, thier, alot and a long tail of contractions (dont, cant, wont, isnt, arent…).
  • Missing space after punctuation — hello.World → hello. World. Conservative by design: only after ., ! and ?.

If the local pass changed something, it's applied to the live text immediately — and then the flow continues into the AI stage with the corrected sentence. One Tab press, full suite.

Stage 2 — Apple Intelligence (the slow, smart bit)

If the local pass found nothing, the sentence goes straight to the AI Proofreader — a FoundationModels LanguageModelSession whose first instruction is "preserve every word". That rule is there because of a real bug: without it, the model silently dropped words and users concluded that Tab was deleting their text.

The AI stage also receives up to 300 characters of surrounding context, purely for resolving ambiguity — never for rewriting.

The bulletproof guards

AI fixes are asynchronous, so the text could change while the model is thinking. Jotty captures the chapter ID, book ID, full text and selection before the request, then re-validates all four after it returns. If anything moved, the fix is aborted and you get "Text changed — fix cancelled. Press Tab again."

The AI Proofreader's Rules

  • Preserve every word from the input. Nothing gets removed, however redundant it looks.
  • Correct only misspellings and clear grammatical errors.
  • Fix capitalisation and obvious typos like teh → the.
  • Don't add new words. Don't expand contractions the author didn't write out.
  • Preserve the author's voice, names, slang, code identifiers and URLs.
  • When in doubt, return the input unchanged.
  • Output only the corrected sentence — no preamble, no quotes, no labels.

Tab doesn't insert a tab character

Because insertTab is overridden, pressing Tab in the editor triggers the fix instead of indenting. Jotty's fix is undoable with ⌘Z like any other edit. If you genuinely need a literal tab, the copyeditor's suggestions are applied through NSTextStorage rather than the Tab path — and Review mode works fine without Tab at all.

Review Mode — The Deep Copyedit

Review is where Jotty stops fixing sentences and starts editing. It's a separate view mode, reached from the sidebar or a chapter's right-click menu, and it runs the CopyEditor — a different FoundationModels session from the proofreader, with its own structured output schema.

What It Returns

Up to 5 high-impact suggestions. Each one carries a category, an action, a severity, the exact text it flagged, the replacement, and a plain-language explanation of why it's a problem.

Category Action Severity
spelling · grammar · punctuation
style · clarity · wordChoice
tone · readability
replace · insert · delete · rephrase info · suggestion · warning

How It Applies Changes

The model never returns character offsets — that's unreliable. Instead it returns the exact substring it wants to change, and Jotty searches the source text for that substring to find the real range. If the anchor can't be found, the suggestion is dropped rather than applied at a guessed offset.

Applying a suggestion runs through the same NSTextStorage path the Tab fix uses, wrapped in an undo group so ⌘Z reverts it in one step. The cursor lands at the end of the replacement so you can keep writing.

Working With Suggestions

  • Accept (↩) applies the suggestion to your chapter.
  • Dismiss (⌫) throws it away — the counter at the top of the panel tracks how many you've applied and dismissed.
  • Accept all / Dismiss all for the sweep.
  • Re-run (⌘R) re-analyses after you've made changes.
  • Back to Write (Esc) returns you to wherever you came from.

The empty state is a feature

The copyeditor is explicitly instructed never to invent problems to fill its quota. If your chapter is clean, Review says "Looking good" rather than manufacturing five nitpicks. It also refuses to flag code identifiers, URLs, deliberate stylistic choices, fictional names and clearly-quoted foreign words.

Exporting

Jotty exports from the File → Export menu, in two flavours: the current chapter, or the current book (every chapter, in order).

Format How it's made Needs pandoc?
PDF Built in. Markdown is parsed into blocks, rendered to an attributed string with real typography (headings scaled down, bold and italic mapped onto the body face, monospace preserved), then paginated by CoreText. No
Word Document (DOCX) pandoc, -t docx Yes
EPUB eBook pandoc, with title and author metadata injected Yes
Plain Text (TXT) pandoc, -t plain --wrap=none Yes
Markdown (MD) Built in — a straight copy of the chapter source No

Exporting a Chapter

  1. Open the chapter you want.
  2. File → Export → Current Chapter, then pick your format.
  3. A save panel opens, pre-named after the chapter title (with / and : replaced by dashes) and pre-pointed at your Jotty library folder.
  4. On success you get a confirmation with the file size and a Reveal in Finder button.

Exporting a Whole Book

File → Export → Current Book collects every chapter in display order and writes them into a single document. If the book has no chapters, Jotty tells you rather than exporting an empty file.

"Could not find /opt/homebrew/bin/pandoc"

DOCX, EPUB and TXT export shell out to pandoc. Jotty looks for it at /opt/homebrew/bin/pandoc, /usr/local/bin/pandoc, /opt/local/bin/pandoc and /usr/bin/pandoc, in that order. Install it with brew install pandoc and try again — PDF and Markdown export keep working regardless.

Accessibility & Fonts

Jotty's font picker is the most carefully researched part of the app. Every choice in the list carries an attribution — the evidence behind it — right in the tooltip.

Three Tiers of Font

  1. Research-backed — Arial, Helvetica, Verdana, Comic Sans MS, Tahoma, Trebuchet MS, plus the monospaced Courier and Courier New. Tagged with the British Dyslexia Association style guide and the Universitat Pompeu Fabra reading study.
  2. Purpose-built dyslexia fonts — OpenDyslexic and OpenDyslexic Mono, Atkinson Hyperlegible (and Mono), the full Lexend family, Comic Neue, Lexie Readable and Andika.
  3. Other clean sans-serifs — Iowan Old Style, Charter, Georgia, New York, Palatino, Source Serif Pro, Lora, Menlo, JetBrains Mono and SF Pro Text. Not dyslexia-tested, but readable.

Jotty only lists fonts that actually resolve on your Mac — anything that isn't installed is filtered out of the picker and surfaced instead as a "Get …" link that takes you to the canonical download page (opendyslexic.org, the Braille Institute, Google Fonts, Lexie Publishing, or SIL for Andika).

Installed fonts that aren't in the curated list are picked up automatically too, so anything you add via Font Book shows up in the picker without Jotty shipping a hardcoded list of every face in existence.

Sizes

The toolbar has − and + buttons that step through a fixed ladder: 12, 14, 15, 17, 18, 20, 24, 28 and 32 pt. The default is 17 pt. The value is clamped to a safe 8–96 range on load so a corrupted preference can never brick the editor.

Where Your Choices Live

Font family and size are stored in UserDefaults, not in your library — so switching fonts never touches your chapters:

  • jotty.editor.fontFamily
  • jotty.editor.fontSize

If the font family you selected later disappears (uninstalled), Jotty validates on load and falls back to the first installed research-backed face rather than rendering nothing.

Other accessibility touches

The saving indicator carries an explicit accessibilityLabel describing the current state for screen readers. The editor footer shows a live Ln X · Col Y cursor position alongside a total character count. The whole window has a minimum size of 800×500, so the layout never collapses into unreadable slivers.

Privacy — Where Your Words Actually Go

Jotty's privacy story isn't a policy document — it's an architectural fact. There is no server to send your writing to.

The AI Layer

  • Proofreader — LanguageModelSession from FoundationModels.
  • CopyEditor (Review) — its own LanguageModelSession.
  • Ask Jotty — a LanguageModelSession with two tools attached.
  • All three run on SystemLanguageModel.default — Apple's on-device model, on your Mac, on your Neural Engine.

The One Exception, Deliberately Narrow

The fetchURL tool is the only thing in Jotty that can touch the network. It fires only when the model decides your request needs real-world reference, only for http:// and https:// URLs, with a 100 kB response cap and a 5 second timeout. file:// and local schemes are rejected outright.

Your Library Is Plain Files

  • ~/Documents/Jotty/library.json — the book list.
  • ~/Documents/Jotty/books/{bookId}/chapters/{chapterId}.md — the prose.
  • ~/Documents/Jotty/books/{bookId}/chapters/{chapterId}.json — title, IDs and timestamps (ISO 8601).
  • ~/Documents/Jotty/.snapshots/{chapterId}/ — manual snapshot history, capped at 30 per chapter with older ones pruned automatically.

Privacy Promise

No cloud inference. No API keys. No accounts. No telemetry, no analytics, no crash reporting. Jotty is MIT licensed and the whole source is on GitHub — go read it. The proofreader, the copyeditor, the Ask Jotty prompt, every tool guard and every file-write path is in the open.

Keyboard Shortcuts

Shortcut Action Where
Tab Fix the sentence under the cursor (local pass, then AI) Editor
⌥⌘Tab Fix Current Sentence (explicit menu equivalent of Tab) Edit menu
⌘B Bold — wraps the selection in ** Toolbar / editor
⌘I Italic — wraps the selection in _ Toolbar / editor
⌘N New Chapter File menu
⇧⌘N New Book File menu / sidebar
⌘S Save now (skips the 700 ms debounce) File menu
⌘D Duplicate Chapter Edit menu
⇧⌘R Reveal Library in Finder File menu
⌘↩ Send the Ask Jotty message Ask Jotty
⌘. Stop generating Ask Jotty
⌘R Re-run the review Review
Esc Back to Write / leave the current mode Ask Jotty, Review
↩ Accept a suggestion Review
⌫ Dismiss a suggestion Review
⌘Z Undo the last fix or applied suggestion Editor

The Markdown Toolbar

The toolbar gives you B, I and a ••• menu holding Heading 1, Heading 2, Heading 3, bulleted list, block quote and inline code. Headings and lists are applied as line prefixes (# , ## , ### , - , > ); bold, italic and inline code wrap the current selection.

The right-hand button

The ✨ Fix sentence button at the far right of the toolbar is the visible equivalent of Tab. Its tooltip tells you the shortcut (⌘⌥Tab) and doubles as the Apple Intelligence availability indicator — hover it to see whether the model is ready.

Troubleshooting

"Apple Intelligence unavailable"

Jotty's AI features need SystemLanguageModel. If the model isn't ready, turn it on in System Settings → Apple Intelligence & Siri. You need macOS 26+, a supported Apple Silicon Mac, and Apple Intelligence enabled for your locale. Jotty surfaces this as an explicit empty state in Review mode rather than failing silently.

Tab does nothing

  • Check the saving indicator in the editor header — when a fix is running it shows fixing.
  • Make sure the cursor is actually inside a sentence, not in empty space.
  • Try ⌥⌘Tab from the Edit menu. If that works but Tab doesn't, something is swallowing the raw key event.
  • If the header shows an error like "Text changed — fix cancelled", the fix was aborted on purpose because you typed while the model was working. Press Tab again.

Ask Jotty says the chapter is too long

The chapter context is capped at 4,000 characters, and Jotty also detects FoundationModels' own token-limit error and retries with a fresh session once. If it still can't fit, ask about a specific section instead of the whole chapter.

Export fails with "Could not find pandoc"

brew install pandoc
which pandoc   # should print a path

# If it's installed somewhere unusual, symlink it into a path Jotty checks
ln -s "$(which pandoc)" /opt/homebrew/bin/pandoc

PDF and Markdown export don't need pandoc at all — they'll keep working.

The build fails immediately

  • xcodegen not found — brew install xcodegen.
  • Xcode too old — Jotty targets macOS 26 with Swift 6; it needs Xcode 26 or later.
  • Stale generated project — the Jotty.xcodeproj is regenerated from project.yml on every build, so editing project.yml is the right way to change build settings.
  • Tests won't compile — tests need ENABLE_TESTABILITY, which the Debug configuration sets. Use bash build.sh test, not a Release test run.

Gatekeeper blocks the built app

build.sh ad-hoc signs the app, so the first launch may need one approval: right-click Jotty.app → Open → confirm. Or System Settings → Privacy & Security → Open Anyway. Once approved, it launches normally.

My chapters vanished

They haven't — Jotty's library lives in ~/Documents/Jotty and Jotty has no telemetry or sync. If the sidebar looks empty, right-click the library in Finder (or ⇧⌘R) and check that the books folder is still there.

One last sanity check

Run the test suite. bash build.sh test executes 209 tests across 8 files. If they all pass, the library, the proofreader, the stats maths and the sentence detector are all behaving.

Credits & Links

Jotty is open source and MIT licensed. Written in Swift, built by Sol AI.

Built With

  • Apple FoundationModels — the on-device SystemLanguageModel behind every AI feature
  • SwiftUI — the app shell, toolbars, sidebar, gallery and panels
  • AppKit — the NSTextView editor, NSSavePanel exports and Finder integration
  • Foundation — JSONEncoder persistence with ISO 8601 dates
  • XcodeGen — generates the Xcode project from project.yml
  • pandoc — DOCX, EPUB and Plain Text export
  • Core Text & Core Graphics — the built-in PDF renderer

Font Research & Sources

Thank you

Jotty was built on a simple conviction: the best AI writing assistant is one that never sends your manuscript anywhere. Tap a key, fix a sentence, keep the words. If it makes your writing day a little easier — or a little more private — that's the whole point.

💬 Join the Discussion

Share your thoughts. Requires a free GitHub account.