A real IDE that runs entirely on an iPad. Editor, git, terminal, npm, live preview and a coding agent — no Mac, no server, no remote runner.
TL;DR — Odete is a native SwiftUI IDE for iPadOS 26. It has a real git repository per project (libgit2), a shell that actually runs
npm installandnpm run dev, a Node-compatible runtime on JavaScriptCore, esbuild compiled to WebAssembly, and an AI agent that reads your files, runs commands and proposes patches you accept hunk by hunk. Nothing is compiled in the cloud. Nothing is uploaded. Close the lid — there is no lid.
New in 1.5 — no output cap on cloud models and parameters discovered per model (new releases just work), conversation compaction at 80% of the context window, local history for every file, a git that no longer reports a rejected push as "ok", Tailwind CSS v4 the official way,
npm run devthat installs what's missing, and Next.js and Astro end to end. Full changelog →
An iPad is a fast computer with a terrible development story. The usual answers are a remote VM, a web IDE, or a Mac in the next room. All three mean: no plane, no subway, no signal, no work.
Odete takes the other road. Everything that has to run, runs here.
|
|
|
Runestone with tree-sitter. Git gutter marks (green, blue, red — tap one to open the hunk
with Discard), the file's symbols one Tabs, split view, diff view, image/PDF/SQLite viewers. Unsaved buffers survive a restart. If a
file changes on disk while its tab has unsaved edits, the editor shows a conflict bar (keep mine,
reload, view diff) instead of overwriting either side. CRLF files stay CRLF, Tab/⇧Tab indent a
selection, and Local history: before any write — your save, the agent, git, the terminal, Replace all, a delete — the previous version is kept on the device (outside the project and iCloud). Touch and hold a tab or a file to browse, diff and restore; deleted files can be brought back too. |
libgit2, not a wrapper. Status, stage by file or by hunk, commit, branch, merge with conflict resolution, stash, remotes over HTTPS, blame and per-file history. GitHub pull requests live in the pane: open one, read the Markdown, check CI, comment, merge. ✨ writes the commit message from the staged diff. It doesn't lie and doesn't lose work: a push the server rejects is reported as rejected, Commit and Commit & push are separate, discarding asks first and sends files to the project trash with Undo, and aborting a merge touches only the merge's files. |
|
Odete's own shell — pipes, redirects, Anything that opens a port becomes a job ( |
A WKWebView pointed at your dev server, reloading every time you save. iPhone/iPad/desktop viewports, the page's own console, and one tap to open it in Safari. With no server running, |
|
Bring your own account — Claude (subscription OAuth), Codex and Grok (device code), or any OpenAI-/Anthropic-compatible endpoint by key and base URL. Tokens live in the Keychain, never in a file. Eight tools run against the real project: read, write, replace, list, grep, read the terminal,
run a shell command, and talk to the GitHub API. Three modes — Chat reads, Plan writes
only Every edit arrives as a patch you accept, reject, or accept hunk by hunk. A checkpoint is written before each turn, so "undo last turn" is one tap. Cloud models get no artificial output cap: context window, output limit, reasoning and effort
levels are discovered per model (provider APIs plus the public models.dev catalog), so a model
released yesterday works today. When a conversation passes 80% of the model's context window
it is compacted the way opencode does it — old tool outputs are pruned first, then the beginning
is summarised and the turn carries on. |
|
This is the part people don't believe, so here is the honest mechanism:
| Piece | How | Caveat |
|---|---|---|
| Node | JavaScriptCore + a hand-written Node layer: fs, path, events, buffer, stream, timers, crypto, fetch, http, require/import() |
Not V8 (a stack-trace shim keeps express & co. happy). No native addons, no Web Streams yet. |
| npm | Real registry, real semver resolution, package.json and package-lock.json byte-identical to npm's, npm: aliases, peers, GitHub/tarball/file:/workspaces |
Native binaries and install scripts are skipped |
| Bundler | esbuild-wasm running inside JavaScriptCore, browser-condition resolution, @/ aliases, CSS Modules, Sass/Less |
Transform and dev server; build for Vite only |
| Tailwind | The tailwindcss v4 compiler running in the same engine, with a JS class scanner instead of oxide |
No lightningcss prefixing |
| HTTP server | Network.framework, bound to 127.0.0.1 |
Local to the device, by design |
| git | libgit2 1.9.7 as an XCFramework, SecureTransport, no SSH | HTTPS + token |
| Swift preview | An interpreter for a subset of SwiftUI, rendered as real SwiftUI | No compiler — see Swift on the iPad |
Dev servers that work today: Vite (dev, build, preview), Astro (dev, including
content collections and MDX), Next (dev, App Router with CSS and Tailwind), Nest (via node).
npm run dev installs missing dependencies first, and the first start after an install takes about
0.2 s because the dev server is warmed up in the background.
There is no Swift compiler on iOS, and Odete does not pretend otherwise.
What it does instead: the Preview interprets a useful subset of SwiftUI and renders it as
genuine SwiftUI — stacks, List/Form/Section, ScrollView, NavigationStack/Link, Text,
Button, Toggle, TextField, Slider, Stepper, Image(systemName:), Label, ForEach,
if/else, @State/@Binding, functions, string interpolation and the common modifiers.
State survives a save; "reset" starts it over. Anything outside the subset becomes a dashed
placeholder plus a warning in Problems, with the line number.
For code that must actually compile, the Swift Playground template produces a .swiftpm
package and a button hands it to Swift Playgrounds.
The whole app — screens, terminal output, git messages, lint warnings, and the language the agent answers in — is translated into 🇧🇷 Português · 🇺🇸 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español.
By default Odete follows the iPad. Change it in Settings → Language and the interface switches immediately, without reopening the app. Dates, file sizes, relative times and the speech recogniser follow the same choice.
1,278 strings live in a single String Catalog. make i18n compares it against the source and
fails if a phrase was added without translations — which is how it stays that way.
- Xcode 26.6 or newer, with the iOS 26.5 simulator runtime
brew install xcodegen swiftformat swiftlint
git clone https://github.com/OkamiOps/odete-ide.git
cd odete-ide
make runmake run generates the Xcode project, builds it, installs it on the iPad Air 11-inch (M4)
simulator and launches it. For another device:
make run SIM="iPhone 17"| Command | What it does |
|---|---|
make gen |
Generate Odete.xcodeproj from project.yml |
make build |
Compile for the simulator |
make run |
Build, install and launch |
make unit |
Unit tests for all 15 packages (1,130 tests) |
make test |
UI tests (XCUITest) |
make lint |
swiftformat --lint + swiftlint |
make format |
swiftformat |
make i18n |
Check the translation catalog against the source |
make check |
Everything CI runs, in one go |
Fifteen local SwiftPM packages, wired by XcodeGen. Nothing is fetched from the network at build time except the pinned libgit2 XCFramework, which is vendored.
| Package | Responsibility |
|---|---|
OdeteI18n |
The String Catalog, the chosen language, tr() and locale-aware formatting |
OdeteCore |
Pure models, themes, ignore rules, language detection, lint, persisted state |
OdeteFiles |
Projects in Documents/Projects, file tree, operations, watcher, search, templates |
OdeteEditor |
CodeEditorView (Runestone + tree-sitter), editor theme, keyboard bar |
OdeteGit |
libgit2: Repository actor, diff/hunks, branches, merge, stash, HTTPS remotes |
OdeteAccounts |
Keychain, per-host accounts, GitHub device flow and REST API |
OdeteRuntime |
JavaScriptCore with the Node layer, real HTTP on 127.0.0.1 |
OdeteNpm |
Registry, semver, package-lock v3 identical to npm's, tarballs, .bin, aliases, workspaces |
OdeteBundler |
esbuild-wasm inside JSC: TS/ESM transform, build, dev server with reload |
OdeteShell |
Parser (| > >> < && || ; & $VAR), builtins, git, npm, node, jobs |
OdetePreview |
The Preview WKWebView, the odete://static/ scheme and the console bridge |
OdeteAgent |
Providers, streaming, the tool loop, patches, checkpoints, conversations, skills |
OdeteSwift |
SwiftUI subset parser and interpreter, native rendering, .swiftpm packaging |
OdeteUI |
Design system: Theme, Rail, PaneHeader, EditorTabs, Splitter, FileGlyph |
OdeteApp |
The screens: hub, workspace, panes, sheets, settings |
Dependencies flow one way: OdeteApp → {OdeteUI, OdeteGit, OdeteAccounts, OdeteShell, OdetePreview, OdeteAgent, OdeteSwift},
OdeteShell → {OdeteGit, OdeteNpm, OdeteRuntime, OdeteBundler}, OdeteBundler → OdeteRuntime,
everything on top of OdeteCore and OdeteI18n.
Design specs and implementation plans for all six phases live in
docs/superpowers/.
| What | Where |
|---|---|
| Projects | Documents/Projects/<name>/ — visible in the Files app |
| Project metadata | <project>/.odete/project.json |
| Conversations, patches, checkpoints, plans | <project>/.odete/ (excluded from git) |
| Layout and preferences | Application Support/Odete/state.json |
| Local file history | Application Support/Odete/Historico/ — on the device only, never synced |
| Tokens and keys | Keychain — never a file, never a backup |
.odete/ is added to .git/info/exclude, so the agent's history never lands in your commits.
Projects can live in iCloud Drive instead (Settings → System), which is the difference between "uninstalling loses everything" and "uninstalling loses nothing".
Projects show their detected stack, the first line of their README, and when you last opened them. Open folder adopts a folder from anywhere via a security bookmark (marked external), Clone pulls from a URL, New project offers: blank, Vite + React, Astro, plain HTML, SwiftUI view, or a Swift Playground package.
Shortcuts are wired up too: Open project, Run command, Ask Odete, New project.
Stated plainly, because a README that only lists wins is a brochure:
astro buildandnext builddon't run yet — Astro and Next are dev-mode only- Astro islands with
client:*hydration and custom collection loaders don't run yet - Next runs the App Router (dynamic segments, catch-alls, route groups, route handlers,
metadata,not-found,error) and the Pages Router, hydrates'use client'components, and runs middleware,next/fontand Server Actions. What's missing:useActionStateshows its initial state until the island hydrates (renderToStringcan't receive the postback), file uploads in a form action,'use server'written inside a component instead of at the top of a file, parallel routes (@slot), and client-side navigation — a link is a full reload - No SourceKit, so no Swift autocomplete;
GeometryReaderandCanvasaren't in the preview subset - Completion doesn't navigate with arrow keys — Tab/Enter takes the first item, or tap
- Two agents in parallel, MCP and voice are not built
- A pure
for(;;){}that never calls into the app can't be interrupted by Ctrl+C - iCloud Drive needs the app signed with the
iCloud.com.okamiops.odetecontainer - GitHub device flow needs
OdeteGitHubClientIdfilled in; until then, a personal token - Signing in with a Claude or Codex subscription uses those CLIs' OAuth clients, which Anthropic and OpenAI restrict to their own apps. That is your call to make, not ours.
make check is the gate: format, lint, translation catalog, build, 1,130 tests. CI runs the same
thing on every push.
Two rules that aren't obvious:
- Any new user-facing string goes through
tr("…"), written in Portuguese — the Portuguese is the key. Then add the four translations toPackages/OdeteI18n/Sources/OdeteI18n/Resources/Localizable.xcstrings.make i18nwill tell you if you forgot. - Never translate a string that's compared anywhere. If some code does
text == "x","x"is a sentinel, not a label.
| IBM Plex | Sans and Mono — OFL |
| Runestone + tree-sitter | Editor and highlighting — MIT |
| libgit2 | Git — GPL2 with linking exception |
| esbuild | Bundling and transform — MIT |
Odete is named after a mouse. She is in the icon, and in the corner of the Git pane.



