Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Starf An activity monitor for Apple silicon that never invents a number Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Elyra
Architecture

Architecture

notr (GPUI app) ──► notr-store (SQLite) ──► notr-core
        └────────────────────────────────► notr-core

Notr is three crates. notr-core is the notebook model and all editing, with no UI or storage dependencies. notr-store persists a workspace in SQLite. notr is the macOS app built with GPUI. For how this maps onto NoteSpace, see Porting notes.

Source map

File Responsibility
notr-core/src/model.rs Workspace, Notebook, SectionGroup, NoteSection, NotePage, NoteBlock, TextFormat, TextMark, InkStroke, DeletedPage, PageVersion, Rect; .notespace serialization details (dates, base64)
notr-core/src/validate.rs Every limit and consistency rule, for a page and for a workspace
notr-core/src/session.rs Session: transactions, undo/redo, change reports, and every organizing operation
notr-core/src/rich_text.rs Formatting runs over UTF-16 ranges: resolve, apply, minimal-diff replace, Replace All
notr-core/src/search.rs Literal search with case folding and whole words, scopes, filters, snippets
notr-core/src/table.rs Table geometry, row and column operations, TSV copy and paste
notr-core/src/outline.rs The page outline: levels, subtrees, visible pages
notr-core/src/groups.rs Section-group validation, paths, subtrees and the navigation projection
notr-core/src/ink.rs Stroke bounds, eraser hit testing, stroke simplification
notr-core/src/interchange.rs .notespace load and save, Markdown and HTML export, text import, safe links and file names
notr-core/src/sample.rs The first-launch notebook, and block helpers used by templates
notr-store/src/lib.rs Store: schema migrations, load, incremental save, conflict detection; see Storage
notr/src/main.rs Startup, key bindings, menus, the window
notr/src/actions.rs GPUI actions for shortcuts and menus
notr/src/workbench.rs Workbench: the session, saving, navigation, layout of the panes
notr/src/commands.rs Cmd and Workbench::run: every ribbon, menu and context-menu command
notr/src/ribbon.rs The ribbon's tabs and groups, and rendering them
notr/src/panes.rs Title bar, notebook pane, page list (with drag and drop), section tabs, notices, status bar, File view, context menus
notr/src/surface.rs The page: painting, pointer tools and gestures, editing notes, titles and table cells, note commands
notr/src/layout.rs Rich-text layout shared by the page, the editor and PNG export
notr/src/editor.rs Editor: the text editor for notes, titles, cells and fields
notr/src/search_pane.rs The search pane and the title bar's search field
notr/src/dialogs.rs Prompts, lists, color, font, table editor and Replace dialogs
notr/src/files.rs Import, export, pictures and attachments
notr/src/png.rs PNG export through SVG and resvg
notr/src/theme.rs Palettes, fonts, colors, Button, icons, tooltips
notr/src/assets.rs Icons compiled into the binary

The model

A Workspace holds notebooks, the recycle bin (trash) and view settings. A notebook holds a flat list of sections and a flat list of section groups; a section's group_id and a group's parent_id make the tree, so page enumeration never depends on grouping. A section's pages are a flat list too, with a level of 0 to 2; the outline (outline::build) reads it as a tree where each page's subtree is a contiguous range.

A page's blocks are its note containers, each with a kind (text, heading, to-do, table, picture, attachment, divider), a position and size in page points, and content. Text formatting is a base format plus marks: ranges in UTF-16 code units, the unit the .notespace format uses. rich_text resolves marks into non-overlapping runs (later marks win, as in NoteSpace) and always writes canonical runs back. rich_text::byte_runs converts them to byte ranges for drawing and editing.

Attachment bytes are Arc<Vec<u8>>, so snapshots for undo share them instead of copying.

The editing session

Session owns the workspace. Nothing else mutates it.

  • execute(label, structure, |workspace| …) runs a change on the whole workspace. It keeps a clone as the undo snapshot, validates the result, and restores the clone if the change fails or the result is invalid. A change that leaves the workspace equal to before records nothing.
  • edit_page(page_id, label, structure, |page| …) runs a change on a copy of one page and commits it only if it is valid and different. Its undo snapshot is just the page. This is the path for typing, formatting, drawing and moving notes.
  • Undo swaps the current state with the snapshot and keeps the swapped-out state for redo. View settings are carried over, so undo never changes the theme or zoom. The history holds 100 entries and an estimated 64 MiB.
  • Every commit, undo and redo pushes a Change naming the page it touched, or none for a workspace change. The app takes these with take_changes() to decide what to save and what to redraw.

All organizing operations (add, rename, delete, restore, duplicate, move pages and subtrees, indent, promote, groups, sections, versions, Replace All) are methods on Session, each one transaction.

The app

GPUI renders the whole window from Workbench on every notify, so there is no separate view model: panes read the session directly each frame.

Commands. Ribbon buttons, menu items, context menus and shortcuts all end in Workbench::run(cmd, entity). It first commits any text being edited (except for formatting commands, which apply to the editor's selection), runs the command, shows errors in a native alert, and calls after_change.

Saving. after_change takes the session's changes into a Dirty set (pages, or all) and schedules a save 650 ms later; view setting changes mark settings. Saving runs on the main thread: the store writes only what changed, so a save after typing is a handful of rows. The workbench also saves when the window closes and when the app quits.

The page. render_surface is a stack: a canvas that paints everything, and, when text is being edited, the editor placed over the note, title or cell. The canvas's prepaint reads the workbench and builds a display list of Ops (quads, paths, text layouts, shaped lines, images), culled to the visible area; paint executes it. Page coordinates map to the window with the zoom and the scroll offset. Pictures are painted with Image::use_render_image, so they keep their place under the ink.

Pointer input starts on the surface and continues at the window's root while a gesture (ink, erase, move, resize, pan) is running, so drags that leave the page still finish. A gesture draws a preview and commits one edit_page when the pointer is released.

Text layout. layout::layout flows a note's text the way NoteSpace does: words and spaces are separate pieces, each shaped in its own style with WindowTextSystem::shape_line, so a line can mix font sizes; pieces wrap at the container width and share a baseline. Bullets and numbers take a 24-point indent. The same function lays out notes on the page, in the editor and in PNG export, so a note wraps identically everywhere.

The editor. Editor is one GPUI entity implementing EntityInputHandler (input methods, dead keys, the character palette). It works in five modes: a note, a page title, a table cell, a one-line field and a multi-line field. For notes it carries the note's style runs and shifts them as you type, so formatting shows while editing. The workbench commits its text after a 450 ms pause, on blur, and before any other command; for notes the commit goes through rich_text::replace, which finds the smallest changed range and keeps the formatting around it.

Dialogs. Messages and confirmations are native alerts (Window::prompt). Prompts, lists, colors, fonts, the table editor and Replace are GPUI overlays built from Editor fields; each holds its continuation, run when the dialog is accepted.

Icons. The icons are NoteSpace's own 24-point line paths, as SVG files in assets/icons/, compiled in by assets.rs and tinted by GPUI.