A native Mac notes app. You pick a folder, you write Markdown, you file notes into folders. That is the product.
Publishing through Boris to a static site (eventually Cloudflare Pages) is a preference and a menu item — never required to take a note.
The destination is a small AAA app that looks like Notes and files like Markdown, Textile, or Cooklang — notes and recipes, same folder. Read docs/THESIS.md.
BANAL is not Solipsist, not a knowledge graph, and not a hosting dashboard.
- macOS 14+
- Full Xcode 16+ / Swift 6 (
swift --version). The standalone Command Line Tools toolchain is not enough — without a full Xcode selected (xcode-select -s /Applications/Xcode.app), the build dies with an obscureSwiftUIMacros plugin not founderror. - Optional for building from source: Oliver and Boris sibling Zig checkouts (
../oliver,../boris, or~/t3/zig/<name>).make appcompiles them into the bundle; without them the app still builds, edits, and publishes with the builtin compiler.
swift test
swift build
swift run banal-cli # despite the name, this launches the GUI app and blocks while it runsswift test is the source of truth for the storage contract and the publisher. The app target is a native SwiftUI + AppKit executable; it does not use Electron or a webview for editing.
make appThat writes dist/BANAL.app (version 1.0), sandbox on, and signs it. This machine has no Developer ID certificate, so the signature is ad-hoc (codesign -s -). Drag it to /Applications. Gatekeeper will warn; right-click → Open the first time, or xattr -cr dist/BANAL.app.
It is not notarized. To sign with a Developer ID you have locally:
make app SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"Still not notarized unless you run notarytool yourself.
swift run banal-cli is not sandboxed. Use make app when you want the real bookmark / picker sit. Oliver and Boris are bundled inside Contents/Helpers (universal binaries built from source / GitHub main by Scripts/helpers.sh); debug overrides stay available via BANAL_OLIVER_BIN / BANAL_BORIS_BIN.
Default folder: ~/Documents/BANAL Notes.
Each note is a .md, .textile, or .cook file. The extension is the language. Markdown and Textile use lightweight frontmatter (title, created, updated, tags, published). Cooklang files stay Cooklang (>> title). Media lives in ./assets/. Edits made in Finder, Vim, or VS Code are observed via FSEvents and NSFilePresenter.
| Shortcut | Action |
|---|---|
| ⌘N | New note, focus editor |
| ⌘⌫ | Move note to Trash |
| ⌘F | Search the note list |
| ⇧⌘F | Find in the current note |
| ⇧⌘P | Publish marked notes to .publish/ |
Mark notes Published, then File → Publish Site….
- Notes with
published: trueare staged as Boris pages (status: published). - If
borisis available it compiles HTML. Otherwise the builtin compiler writes HTML +feed.xml. - A dry-run
wrangler.tomlis written next to the artifact. Cloudflare credentials are never required for local notes.
swift run banal vault [--json] # resolved notes folder + count
swift run banal notes [--json] # every note: id, title, language, published, tags
swift run banal show <id> [--json] # one note file to stdout (--json parses it)
swift run banal publish # the same pipeline as ⇧⌘P, no pixels
swift run banal doctor # vault, Boris/Oliver presence, identity contractbanal is a read-mostly window for scripts and agents over the same code paths as the app (--vault DIR to point at any folder). It never creates or edits notes — the editor is the app. banal-cli remains the GUI launcher.
BANAL has an AppleScript dictionary (Resources/BANAL.sdef). Writes go through the same store the app uses and land as ordinary files on disk:
tell application "BANAL"
create note given title:"Risotto Bianco", body:"Toast the rice.", folder:"Recipes", published:true
list notes -- JSON array of records
read note "Recipes/Risotto Bianco.md" -- JSON record incl. body
update note "Risotto Bianco.md" given body:"Stir, add stock."
set published "Risotto Bianco.md" to true
publish site -- "Published 1 note with builtin."
end tellRecord-shaped answers come back as JSON text. There is no second database behind the dictionary — the folder is still the truth.
| File | Role |
|---|---|
AGENTS.md |
Binding rules for anyone (or any model) writing code here |
docs/NORTH-STAR.md |
How it should feel. Wins when docs disagree. |
docs/THESIS.md |
Languages and engines (do not grow this) |
docs/cards/README.md |
What to build next (one card per PR) |
docs/MISSION.md |
One-page version |
docs/QUALITY.md |
AAA finish bar |
docs/PREFERENCES.md |
Settings + Publish pane (Deploy is live) |
docs/ROADMAP.md |
Gas stations: Folders → … → Close → Fluency → Type / System / Files |
docs/HOPE-CHEST.md |
The route to M99. Tucson before California. Not a board. |
docs/HORIZON.md |
Looking forward. Not a board. Furniture, pickers, import-as-files. |
docs/STATUS.md |
What works now |
docs/TESTING-NOTES-FOLDER.md |
How to sit the notes-folder picker (first run, missing, vanish while open) |
docs/ARCHITECTURE.md |
Boris / Solipsist boundary |