Complete reference for the EEditor application and the EELisp language. Covers every feature, every builtin function, and every keyboard shortcut.
When you first launch EEditor you see an empty editor with a sidebar. Three steps to begin:
Use ⌘⇧O to open a single file from anywhere on disk. The file’s parent directory becomes the active folder.
EEditor provides a distraction-free writing environment with real-time syntax highlighting for Markdown and 24+ programming languages.
YYYY-MM-DD HH:mm at the cursor.[[filename]] and press ⌘O to open that file.The sidebar shows a hierarchical view of your folder. Directories sort first, then files alphabetically.
subfolder/file.md to create nested files..el or .lisp file to execute it.The sidebar has three modes via the segmented toggle: Files, Calendar, and Agenda.
Each file opens as a tab at the top of the editor.
| Action | Shortcut | Notes |
|---|---|---|
| Next tab | ⌘⌥→ | Wraps around |
| Previous tab | ⌘⌥← | Wraps around |
| Close tab | ⌘W | Auto-saves if dirty |
| Close all unpinned | ⌘⌥W | |
| Pin / Unpin | ⌘⇧K | Pinned tabs persist across sessions |
Pinned tabs show a pin icon, appear first (sorted alphabetically), and cannot be closed until unpinned. They are automatically restored on relaunch.
Place your cursor on a [[filename]] link and press ⌘O to open that file directly.
YYYY-MM-DD.md in the project root, pre-filled with a date heading.Week of YYYY-MM-DD.md (Monday of the current week).Toggle the sidebar to Calendar mode using the segmented control.
Toggle to Agenda mode via the clipboard icon. The panel shows your upcoming schedule in three sections:
:when date before today.Each item shows a priority dot (red = urgent, orange = high, yellow = medium, gray = normal), text, categories, and formatted date. Tap refresh to reload after adding items in the REPL.
Toggle to REPL mode via the terminal icon in the sidebar segmented control. This embeds a full EELisp REPL directly in the sidebar panel, so you can evaluate expressions while keeping your editor visible.
Embed executable EELisp code directly in your Markdown files using fenced code blocks:
```eelisp
(+ 1 2 3)
```
```eelisp block.```result block immediately below.```eelisp
(+ 1 2 3)
```
```result
6
```
Re-running replaces the existing result. Use cases include inline calculations, adding agenda items from journal entries, querying data, and file operations:
```eelisp
(add-item (smart-parse "tomorrow review quarterly report !!"))
```
```eelisp
(write-file "todo.txt" "Buy groceries\nCall dentist")
```
Press ⌘? in the app to see the full shortcut sheet. Here is the complete reference:
| Action | Shortcut |
|---|---|
| New file | ⌘N |
| Open folder | ⌘O |
| Open file | ⌘⇧O |
| Save | ⌘S |
| Refresh tree | ⌘⇧R |
| Daily note | ⌘D |
| Weekly planner | ⌘⇧W |
| Action | Shortcut |
|---|---|
| Quick Open | ⌘P |
| Search in files | ⌘⇧F |
| Find & Replace | ⌘F |
| Focus editor | ⌘\ |
| Open wiki-link | ⌘O |
| Action | Shortcut |
|---|---|
| Next tab | ⌘⌥→ |
| Previous tab | ⌘⌥← |
| Close tab | ⌘W |
| Close all unpinned | ⌘⌥W |
| Pin / Unpin tab | ⌘⇧K |
| Action | Shortcut |
|---|---|
| Insert timestamp | ⌘I |
| Undo | ⌘Z |
| Redo | ⌘⇧Z |
| Action | Shortcut |
|---|---|
| Preview | ⌘⇧P |
| Export PDF | ⌘⇧E |
| Open REPL | ⌘⇧L |
| Send to REPL | ⌘R |
| Run EELisp Block | ⌘⇧Return |
| Shortcuts help | ⌘? |
EEditor includes a built-in Lisp interpreter called EELisp — designed for personal data management, calculations, and scripting.
> prompt and press Enter... and waits for more. Press Escape or the × button to cancel.Select text in the editor and press ⌘R. The REPL opens with your selection pre-filled in the input field, ready for you to press Enter to evaluate.
Use :load path/to/file.el to load and evaluate an EELisp script. On iOS, tap the Load button in the toolbar or right-click a .el file in the sidebar.
On iOS/iPadOS, a toolbar appears with: history navigation (↑/↓), quick snippets (query, deftable, defn), Load File, and Clear.
Colon-prefixed commands control the REPL itself (not EELisp expressions):
| Command | Alias | Description |
|---|---|---|
:help | :h | Show command reference and quick examples |
:load <file> | :l | Load and evaluate a .el / .lisp file |
:db | Show current database path (:memory: or file path) | |
:db <path> | Switch to a different database file | |
:db new <name> | Create a new database in the current folder | |
:db memory | Switch to an in-memory database | |
:env | :e | List all defined symbols in the environment |
:clear | :c | Clear REPL output history |
:reset | :r | Reset interpreter (fresh environment, same database) |
eelisp.db file, the REPL automatically connects to it for persistent storage. Data created with deftable and insert is saved to disk.
All arithmetic operators are variadic (accept multiple arguments) and work with integers and floats.
| Function | Signature | Description |
|---|---|---|
+ | (+ num ...) | Addition. (+ 1 2 3) → 6 |
- | (- num ...) | Subtraction or negation. (- 10 3) → 7, (- 5) → -5 |
* | (* num ...) | Multiplication. (* 2 3 4) → 24 |
/ | (/ num num ...) | Division. (/ 100 3) → 33.333... |
mod | (mod a b) | Modulo. (mod 10 3) → 1 |
abs | (abs num) | Absolute value. (abs -42) → 42 |
min | (min num ...) | Minimum. (min 3 1 4) → 1 |
max | (max num ...) | Maximum. (max 3 1 4) → 4 |
floor | (floor num) | Round down. (floor 3.7) → 3 |
ceil | (ceil num) | Round up. (ceil 3.2) → 4 |
pow | (pow base exp) | Exponentiation. (pow 2 10) → 1024 |
round | (round num [places]) | Round. (round 3.14159 2) → 3.14 |
;; Arithmetic examples (+ 1 2 3 4) ; → 10 (* 6 7) ; → 42 (/ 100 3) ; → 33.333... (pow 2 10) ; → 1024 (round 3.14159 2) ; → 3.14 ;; Nesting (+ (* 3 4) (- 10 5)) ; → 17
| Function | Signature | Description |
|---|---|---|
= | (= a b) | Equality. Works with numbers, strings, bools |
!= | (!= a b) | Inequality |
< | (< a b) | Less than |
> | (> a b) | Greater than |
<= | (<= a b) | Less than or equal |
>= | (>= a b) | Greater than or equal |
These are special forms (short-circuit evaluation):
(and true false) ; → false (or false true) ; → true (not true) ; → false ;; Short-circuit: (or) returns first truthy value (or nil false 42 99) ; → 42
(def x 42) (def greeting "Hello, world!") (def pi 3.14159)
(defn square (x) (* x x)) (square 5) ; → 25 ;; Multi-expression body (last value returned) (defn greet (name) (def msg (str "Hello, " name "!")) (println msg) msg)
(map (fn (x) (* x x)) '(1 2 3)) ; → (1 4 9)
(defn sum (first . rest) (reduce + first rest)) (sum 1 2 3 4 5) ; → 15
(let ((x 1) (y 2)) (+ x y)) ; → 3
(if (> x 0) "positive" "non-positive")
(cond (< x 0) "negative" (= x 0) "zero" else "positive")
;; Execute body only if condition is true (when (> x 0) (println "positive!") x)
;; Sum from 0 to 10 (loop ((i 0) (acc 0)) (> i 10) (recur (+ i 1) (+ acc i))) ; → 55
;; Evaluate multiple expressions, return last (begin (def a 10) (def b 20) (+ a b)) ; → 30
| Function | Signature | Example → Result |
|---|---|---|
str | (str val ...) | (str "a" "b" "c") → "abc" |
str-len | (str-len s) | (str-len "hello") → 5 |
str-upper | (str-upper s) | (str-upper "hello") → "HELLO" |
str-lower | (str-lower s) | (str-lower "HELLO") → "hello" |
str-contains | (str-contains s sub) | (str-contains "hello" "ell") → true |
str-starts-with | (str-starts-with s prefix) | (str-starts-with "hello" "he") → true |
str-ends-with | (str-ends-with s suffix) | (str-ends-with "hello" "lo") → true |
str-split | (str-split s sep) | (str-split "a,b,c" ",") → ("a" "b" "c") |
str-join | (str-join sep list) | (str-join "-" '("a" "b")) → "a-b" |
str-trim | (str-trim s) | (str-trim " hi ") → "hi" |
str-replace | (str-replace s target repl) | (str-replace "hello" "l" "r") → "herro" |
str-matches | (str-matches s regex) | Returns list of capture groups or nil |
substr | (substr s start [end]) | (substr "hello" 1 3) → "el" |
| Function | Signature | Example → Result |
|---|---|---|
list | (list val ...) | (list 1 2 3) → (1 2 3) |
cons | (cons val list) | (cons 0 '(1 2)) → (0 1 2) |
car / head | (car list) | (car '(1 2 3)) → 1 |
cdr / tail | (cdr list) | (cdr '(1 2 3)) → (2 3) |
nth | (nth list n) | (nth '(a b c) 1) → b |
length | (length seq) | (length '(1 2 3)) → 3 |
append | (append list ...) | (append '(1) '(2 3)) → (1 2 3) |
reverse | (reverse list) | (reverse '(1 2 3)) → (3 2 1) |
map | (map fn list) | (map inc '(1 2 3)) → (2 3 4) |
filter | (filter fn list) | (filter even? '(1 2 3 4)) → (2 4) |
reduce | (reduce fn init list) | (reduce + 0 '(1 2 3)) → 6 |
range | (range [start] end [step]) | (range 1 6) → (1 2 3 4 5) |
flatten | (flatten list) | (flatten '(1 (2 3) (4))) → (1 2 3 4) |
sort-by | (sort-by fn list) | (sort-by id '(3 1 2)) → (1 2 3) |
zip | (zip list list) | (zip '(1 2) '(a b)) → ((1 a) (2 b)) |
empty? | (empty? val) | (empty? '()) → true |
;; Chaining list operations (def nums (range 1 11)) (filter even? nums) ; → (2 4 6 8 10) (map (fn (x) (* x x)) (filter even? nums)) ; → (4 16 36 64 100) (reduce + 0 (range 1 101)) ; → 5050
Dictionaries use keyword keys (:name) and are immutable — operations return new dicts.
| Function | Signature | Example → Result |
|---|---|---|
dict | (dict :key val ...) | (dict :a 1 :b 2) → {:a 1 :b 2} |
dict-get | (dict-get d key [default]) | (dict-get d :name) → value or nil |
dict-set | (dict-set d key val) | Returns new dict with key set |
dict-keys | (dict-keys d) | (dict-keys {:a 1 :b 2}) → (:a :b) |
dict-values | (dict-values d) | (dict-values {:a 1 :b 2}) → (1 2) |
dict-has | (dict-has d key) | (dict-has {:a 1} :a) → true |
dict-merge | (dict-merge d ...) | Merges dicts; later keys override |
(def person {:name "Alice" :age 30}) (dict-get person :name) ; → "Alice" (dict-set person :age 31) ; → {:name "Alice" :age 31} (dict-merge person {:city "NYC"}) ; → {:name "Alice" :age 30 :city "NYC"}
| Function | Returns true when |
|---|---|
string? | Value is a string |
number? | Value is a number |
bool? | Value is a boolean |
list? | Value is a list |
nil? | Value is nil/null |
symbol? | Value is a symbol |
keyword? | Value is a keyword |
fn? | Value is a function |
dict? | Value is a dictionary |
date? | Value is a date |
record? | Value is a database record |
item? | Value is an agenda item |
(type 42) ; → "number" (type "hello") ; → "string" (type '(1 2)) ; → "list"
| Function | Description |
|---|---|
->string | Convert any value to its string representation |
->number | Parse string to number, bool to 0/1 |
->bool | Truthiness: nil, false, 0, "" → false; everything else → true |
| Function | Description |
|---|---|
(print val ...) | Print values without trailing newline |
(println val ...) | Print values with trailing newline |
| Function | Signature | Description |
|---|---|---|
now | (now) | Current date/time object |
today | (today) | Today as ISO string "YYYY-MM-DD" |
date-format | (date-format date [fmt]) | Format a date. Default: "yyyy-MM-dd HH:mm" |
date-add | (date-add date n :unit) | Add days/weeks/months. Units: :days, :weeks, :months |
date-diff | (date-diff d1 d2) | Difference in days between two ISO date strings |
(today) ; → "2026-02-23" (date-add (today) 7 :days) ; → "2026-03-02" (date-add "2026-01-31" 1 :months) ; → "2026-02-28" (date-diff "2026-01-01" "2026-12-31") ; → 364
These functions are defined in EELisp itself and loaded automatically at startup:
| Function | Description | Example |
|---|---|---|
inc | Increment by 1 | (inc 5) → 6 |
dec | Decrement by 1 | (dec 5) → 4 |
even? | True if even | (even? 4) → true |
odd? | True if odd | (odd? 3) → true |
zero? | True if zero | (zero? 0) → true |
pos? | True if positive | (pos? 5) → true |
neg? | True if negative | (neg? -3) → true |
first | First element | (first '(a b c)) → a |
second | Second element | (second '(a b c)) → b |
third | Third element | (third '(a b c)) → c |
last | Last element | (last '(a b c)) → c |
take | Take first n | (take 2 '(a b c)) → (a b) |
drop | Drop first n | (drop 1 '(a b c)) → (b c) |
some? | Any match? | (some? even? '(1 2 3)) → true |
every? | All match? | (every? pos? '(1 2 3)) → true |
pipe | Threading | (pipe 5 inc inc square) → 49 |
compose | Compose functions | ((compose inc square) 5) → 26 |
partial | Partial application | ((partial + 10) 5) → 15 |
id | Identity function | (id 42) → 42 |
| Function | Signature | Description |
|---|---|---|
http-get | (http-get url) | GET request. Returns {:status N :body "..."} |
http-post | (http-post url body [:content-type "..."]) | POST request with body |
json-parse | (json-parse string) | Parse JSON to EELisp dict/list |
json-stringify | (json-stringify value) | Convert EELisp value to JSON string |
;; Fetch data from an API (def resp (http-get "https://api.example.com/data")) (def data (json-parse (dict-get resp :body))) ;; Post JSON (http-post "https://api.example.com/items" (json-stringify {:name "test"}) :content-type "application/json")
| Function | Description |
|---|---|
(eval expr) | Evaluate an unevaluated expression |
(parse string) | Parse a string as EELisp code |
EELisp can read and write files in your open folder. All paths are relative to the current workspace directory.
| Function | Description |
|---|---|
(read-file path) | Read file contents as a string |
(write-file path content) | Write content to a file (creates or overwrites) |
(append-file path content) | Append content to an existing file |
(file-exists? path) | Check if a file exists |
(list-files [path]) | List filenames in a directory (default: workspace root) |
(current-dir) | Get the workspace root path |
;; Read a file (def content (read-file "notes.md")) ;; Write results to a file (write-file "output.txt" (str "Report: " (today))) ;; Append to a log (append-file "log.txt" (str (today) " - Done\n")) ;; List workspace files (list-files) ; → ("README.md" "notes.md" ...) (list-files "src") ; → ("main.el" "utils.el" ...)
These builtins let EELisp code interact with the active editor buffer.
| Function | Description |
|---|---|
(cursor-pos) | Get current cursor position (character offset) |
(set-cursor-pos pos) | Move cursor to a position |
(insert-at pos text) | Insert text at a position |
(replace-range start end text) | Replace text between start and end |
(selection) | Get the currently selected text (or nil) |
(current-file) | Get the path of the active file |
(buffer-text) | Get the full text of the active editor |
;; Insert text at the cursor (insert-at (cursor-pos) (str "Updated: " (today))) ;; Get what's selected (def sel (selection)) ;; Read the entire buffer (def text (buffer-text)) (println (str "Length: " (str-len text)))
| Function | Description |
|---|---|
(clipboard-get) | Read text from the clipboard |
(clipboard-set text) | Copy text to the clipboard |
;; Copy a result to clipboard (clipboard-set (str "Total: $" (reduce + 0 '(10 20 30)))) ;; Paste and process clipboard contents (str-upper (clipboard-get))
EELisp includes a built-in SQLite database engine. Tables are defined with typed schemas.
(deftable contacts (name:string email:string age:number))
| Type | Description | Example values |
|---|---|---|
string | Text data | "Alice", "hello@example.com" |
number | Integer or float | 42, 3.14 |
bool | Boolean | true, false |
date | ISO date string | "2026-02-23" |
memo | Long text | Multi-line content |
choice | Dropdown picker | Predefined options list |
| Function | Description |
|---|---|
(tables) | List all table names |
(describe table) | Show table schema |
(drop-table table) | Delete a table and all its data |
(pack table) | Permanently remove soft-deleted records |
;; Full example (deftable tasks (title:string priority:number done:bool due:date)) (tables) ; → (tasks) (describe tasks) ; → schema details
(insert tasks {:title "Write docs" :priority 1 :done false}) (insert tasks {:title "Fix bug" :priority 2 :done false :due "2026-03-01"})
;; All records (query tasks) ;; With WHERE clause (query tasks :where "priority <= ?" :params (list 2)) ;; Sorted and limited (query tasks :order "priority" :limit 5) ;; Select specific columns (query tasks :select "title, done") ;; Descending order (query tasks :order "priority" :asc false)
;; Update by ID (update tasks 1 {:done true}) ;; Soft-delete (dBASE-style, can be recovered) (delete tasks 1) ;; Permanently remove soft-deleted records (pack tasks) ;; Count records (count-records tasks) (count-records tasks :where "done = ?" :params (list false))
| Function | Description |
|---|---|
(field-get record :field) | Get a field value from a record |
(field-set record :field val) | Return new record with field updated |
(record-id record) | Get the record’s numeric ID |
(records result-set) | Extract list of records from a query result |
;; Working with query results programmatically (def results (query tasks :where "done = ?" :params (list false))) (def recs (records results)) (map (fn (r) (field-get r :title)) recs) ; → ("Write docs" "Fix bug")
EELisp can render interactive UI components directly in the REPL output.
;; Browse all records in a scrollable grid (browse tasks) ;; With filters and sorting (browse tasks :where "done = ?" :params (list false) :order "priority")
;; Open an interactive form for create/read/update/delete (edit tasks) ;; With filters (edit tasks :where "priority = ?" :params (list 1))
The form shows one record at a time with Prev/Next navigation, inline editing, Save, New, and Delete buttons.
;; Standalone calculator form with computed fields (defform loan-calc (principal:number rate:number years:number) :computed ( (monthly-payment (let ((r (/ rate 1200)) (n (* years 12))) (round (/ (* principal r (pow (+ 1 r) n)) (- (pow (+ 1 r) n) 1)) 2))) (total-paid (round (* monthly-payment (* years 12)) 2))))
Computed fields update automatically when input fields change.
Use the (name:choice "opt1" "opt2" ...) syntax to create a dropdown picker:
(defform new-task (title:string (priority:choice "low" "medium" "high" "urgent") (status:choice "todo" "in-progress" "done") notes:memo))
Choice fields render as a dropdown menu in the form UI.
Link a form to an existing database table with :source to enable full CRUD:
;; Create a table first (deftable projects (name:string status:string budget:number)) (insert projects {:name "Alpha" :status "active" :budget 50000}) ;; Form backed by the table with dropdown for status (defform project-editor (name:string (status:choice "planning" "active" "completed") budget:number) :source projects)
The form loads records from the source table with Prev/Next navigation, Save, New, and Delete buttons — like (edit table) but with custom field types.
eelisp.db, the REPL auto-connects to it.| Command | Description |
|---|---|
:db | Show current database path |
:db <path> | Switch to a different database file (relative to folder) |
:db new <name> | Create a new database in the current folder |
:db memory | Switch to an in-memory database |
:db mydata.db rather than typing the full path.
The agenda system is a Lotus Agenda-inspired personal information manager. Items are free-form text entries with optional metadata.
(add-item "Finish quarterly report") ;; With metadata (add-item "Call dentist" :when "2026-03-01" :priority 2) (add-item "Buy groceries" :category "personal" :notes "milk, eggs") ;; Add with today's date (add-item-today "Review pull requests" :priority 1)
(items) ; all items (table view) (items :category "work") ; filter by category (items :search "quarterly") ; text search (items :priority 1) ; filter by priority (items :when-before "2026-03-01") ; due before date
| Function | Description |
|---|---|
(item-get id) | Fetch a single item by ID |
(item-edit id) | Open item in form view for editing |
(item-set id :field val ...) | Update item fields |
(item-done id) | Mark item as done (soft-delete) |
(item-count) | Count all items |
(item-count :category "work") | Count items in a category |
Categories are hierarchical (use / separators) and optionally exclusive (item can be in only one child).
;; Hierarchical categories (defcategory work) (defcategory work/projects) (defcategory work/meetings) (defcategory personal) (defcategory personal/errands) ;; Exclusive: item can only be in one child (defcategory priority :exclusive true :children (high medium low)) ;; Assign / unassign (assign 1 "work/projects") (assign 1 "priority/high") (unassign 1 "personal") ;; View category tree (categories)
:exclusive true, assigning one child (e.g., priority/low) automatically removes siblings (e.g., priority/high). Perfect for status, priority, or any single-choice grouping.
Rules automatically categorize items based on their content — the signature feature of Lotus Agenda.
;; Text-matching rules (defrule urgent-flag :when (str-contains text "URGENT") :assign "priority/high") (defrule meeting-detect :when (or (str-contains text "meeting") (str-contains text "call with")) :assign "work/meetings") ;; Regex with date extraction (defrule date-extract :when (str-matches text "\\b(\\d{4}-\\d{2}-\\d{2})\\b") :action (item-set id :when (match 1))) ;; Apply rules (apply-rules) ; batch-apply to all items (apply-rules 42) ; apply to item #42 ;; Auto-apply on every add-item (auto-categorize true)
| Function | Description |
|---|---|
(rules) | List all defined rules |
(drop-rule name) | Delete a rule |
(auto-categorize bool) | Toggle auto-apply on insert |
text, notes, categories, all item properties, plus helpers like has-category and overdue?. Combine with and/or/not.
Views are saved queries that filter, sort, and group items dynamically.
(defview work-board :source items :filter (has-category "work") :group-by category :sort-by when) (defview urgent :source items :filter (= priority "1") :sort-by when) (defview inbox :source items :filter (= (length categories) 0)) ;; Show a view (show work-board) (show urgent)
| Function | Description |
|---|---|
(views) | List all defined views |
(drop-view name) | Delete a view |
;; Items for a specific date (items-on "2026-02-24") ;; Items in a date range (items-between "2026-02-24" "2026-02-28") ;; Using date helpers (items-on (today)) (items-between (today) (date-add (today) 7 :days))
The add command parses natural language to extract dates, priorities, and people automatically.
(add "Meet Alice tomorrow for coffee !!") ; → :when = tomorrow, :priority 2, :who "Alice" (add "Call Bob next Monday about the project") ; → :when = next Monday, :who "Bob" (add "URGENT fix server crash") ; → :priority 1 ;; Preview without creating (smart-parse "email Sarah March 15 about renewal !!") ; → {:text "email Sarah about renewal" :when "2026-03-15" :priority 2 :who ("Sarah")}
tomorrow/today/yesterday, next Monday, this weekend, in 3 days, end of week/month, March 15URGENT/ASAP, !!!/!!/!, high/low priority@name, with/for/from Name, call/email/meet Name;; Built-in intervals (add-item "Team standup" :when (today) :recur :daily) (add-item "Weekly review" :when (today) :recur :weekly) (add-item "Monthly report" :when (today) :recur :monthly) ;; Custom interval (add-item "Check filters" :when (today) :recur (every 3 :months)) ;; When you mark done, the next occurrence is auto-created (item-done 1) ; creates next recurrence automatically
;; Define a reusable item blueprint (deftemplate weekly-review :text "Weekly review" :category "work" :priority 2 :recur :weekly) ;; Create from template with overrides (from-template weekly-review :when "2026-03-01") ;; Manage templates (templates) ; list all (drop-template weekly-review) ; delete
Open, switch between, and manage multiple agenda databases. Useful for separating work and personal agendas.
;; Open a new agenda (open-agenda "work.db") (open-agenda "personal.db") ;; Switch between agendas (use-agenda "work") ;; List all open agendas (agendas) ; shows active marker (*) ;; Close an agenda (close-agenda "personal")
;; Export for backup or migration (export-agenda "work" :format :json :path "work-backup.json") ;; Import from a previous export (import-agenda "work-backup.json")
The EEditor GUI integrates with EELisp through the interpreter’s eval() method. All data flows through EELisp expressions — the GUI never accesses SQLite directly.
┌─────────────────────────┐
│ SwiftUI Views │
│ (Sidebar, Calendar, │
│ Agenda, REPL) │
├─────────────────────────┤
│ ViewModels │
│ (call interpreter.eval)│
├─────────────────────────┤
│ EELisp Interpreter │
│ (builtins, prelude) │
├─────────────────────────┤
│ SQLite Database │
│ (eelisp.db) │
└─────────────────────────┘
(items), (items-on), (items-between) to load items for display.(items-between start end) to count agenda items per day.browse and edit return .tableView / .formView values that the REPL renders as SwiftUI components.;; 1. User adds items in the REPL (add-item "Review PRs" :when (today) :priority 1) ;; 2. Switch to Agenda sidebar → GUI calls: ;; interpreter.eval("(items-between startKey endKey)") ;; Parses results into AgendaDisplayItem structs ;; 3. Switch to Calendar → GUI calls: ;; interpreter.eval("(items-on \"2026-02-23\")") ;; Counts items per day for dot indicators