Snowbound is one core with thin hosts around it. The file format, sync, the page editor and the renderer are identical everywhere. What changes per platform is the window, the input plumbing, and how much of the interface is drawn by Snowbound versus the operating system.
| 1 | macOS Linux Windows iOS |
| 2 | shell snowbound + ui snowbound + ui snowbound + ui UIKit (apps/ios) |
| 3 | glue snowbound/src/macos snowbound/src/linux snowbound/src/windows crates/mobile (C ABI) |
| 4 | page canvas ─────────────────────────────────────────────────────────────────────► |
| 5 | paint draw (Metal or GL) draw (Vulkan or GL) draw (D3D12, D3D11, GL) draw (Metal) |
| 6 | data notebook::session + embedded SMB client ────────────────────────────────────► |
| 7 | format onestore ───────────────────────────────────────────────────────────────────► |
snowboundThe desktop app is a winit window with the ui kit's chrome and a canvas
page inside it. snowbound picks a platform module at compile time
(macos.rs, linux.rs or windows.rs, each mounted as platform), and
everything else in the crate is shared: library and settings, the sidebar,
menus, page and section management, templates, and screenshot and replay
support. Accessibility goes through AccessKit's winit adapter on all three:
the interface's tree, with the page's grafted into it.
Each platform draws through one of several backends (the table's paint row; in a
browser WebGPU, WebGL 2 or the 2D canvas). By default the first that starts draws;
Options' Renderer, the settings' renderer, SNOWBOUND_RENDERER or --renderer
(each over the last) picks one, and one that fails to start falls back to the
default and says so. Choosing another in Options starts it at once: the renderer and
surface go and new ones begin, the window and everything in it staying as they are.
A panic writes a crash report beside the settings before the process ends (crash.rs):
the build, system, renderer, thread, uptime, panic and backtrace, with paths and the
open notebooks' and sections' names hidden; the browser keeps it in local storage. The next
launch asks whether to send it to the site's POST /crash, shows the exact text on Show
Report, and sends nothing unless Send Report is chosen; either answer deletes it. A run
with no settings of its own, as a screenshot or replay, writes and reads none.
commands.rs is the one table of commands: each one's title, its chords on
macOS and elsewhere, when it is enabled or checked, and what it does. The
keyboard, the toolbar and the macOS menu bar all run commands from it.
NSToolbar, unified compact
from macOS 11, with the title hidden makes the title bar 38 pt, and AppKit
centres the traffic lights in it beside the row's buttons; the row ends 4 pt
short of it, so the tabs stand as near the buttons as a tab bar would. A
press in the row's empty space drags the window and a double press zooms or
minimizes it, as Desktop & Dock says; presses in a group of buttons do
neither, so AppKit is told the view never moves the window. The title is still set, for the Window menu, Mission Control and
VoiceOver. --screenshot paints the lights where the hidden window's AppKit
put them. The app draws the row as part of the same frame as the rest of the
chrome. Under the whole
window lies AppKit's title bar material, an NSVisualEffectView, and the
chrome is drawn transparent over it, so the title bar, toolbar, tab row and
sidebar take the system's desktop tint, appearance and focus state exactly.NSSpellChecker, which the
spelling thread calls on the main thread (10.6 has it too).menubar.rs) is laid out as OneNote for Mac's. Its items are
the command table's, validated from the statuses each frame publishes, and
their key equivalents are the table's chords, so AppKit takes a chord the
menu enables before winit sees the key. Linux has no menu bar.tools/canvas/build_macos.py builds and ad-hoc signs target/Snowbound.app.button-layout places them, kept current through the settings portal. On
GNOME they are libadwaita's, sized and coloured as the release that came
with the running GNOME Shell draws them; elsewhere they are the toolbar's
own buttons, not an imitation of a theme Snowbound can't read. Under that
frame on GNOME the window erases its bottom corners to transparent pixels
and the frame draws libadwaita's window edge round it: radius, shadows and
outline. KWin rounds Breeze's corners itself.kdeglobals, as KWin paints its title bars, and on GNOME libadwaita's
header bar's. A settings portal signal re-reads them when the scheme changes.XDG_CURRENT_DESKTOP; elsewhere the kit's own.desktop_linux.rs). The
window is net.paperclover.snowbound to Wayland and X11 alike. Where a
Wayland compositor lacks xdg-toplevel-icon, as GNOME's does, it finds the
icon only through an entry of that name, so a run that isn't installed
writes a hidden one pointing at an icon in the runtime folder, marked with
its process ID, and removes both on exit or at the next start after a crash.
KWin takes the icon from the window. Install on the welcome copies the
executable to ~/.local/bin and writes the visible entry under the same
name, so the menu never lists two; Uninstall in Options removes it.msvcrt.dll, which every Windows has
(platform/windows/cargo.sh): x86_64 on nightly's tier-3
x86_64-win7-windows-gnu, whose standard library avoids Windows 8's
imports, and aarch64 for Windows 11 on Arm. The few imports of Windows 8
and later that dependencies still name are answered by
platform/windows/rt/shims.c; everything newer is looked up at run time.draw builds three backends on Windows, tried in turn by default:
Direct3D 12 through wgpu where Windows has it (10 and 11), otherwise
Direct3D 11, as on Windows 7, on WARP where no device reaches feature
level 10_0, and OpenGL 2.1 on a WGL context only where neither starts
(surface_windows.rs). Direct3D 11 presents through a blit-model DXGI
swap chain, as 7 has no flip model, and through DirectComposition on 10,
whose window has no redirection surface; OpenGL drivers such as Intel's
place their frames below the caption the system would draw even where the
row takes its place.SNOWBOUND_W7_CHROME tries the alternatives, pills for a face per group
of tools over the glass, frost for a strip the glass tints through, and
tint and tint-tiles for one panel or a tile per group in the glass's
colour at a lightness text keeps its contrast on, as Mica tints.
The theme and material follow the system's colour mode as it changes.
With Windows 7's basic or classic theme the system draws the title bar
and the row lies beneath it, as on KDE.onestore opens a
section as OneNote does (a reader shares it with everyone, a writer denies
other writers) and takes OneNote's coordination bytes with byte-range
locks, so both apps can have a section open on one machine.
ReadDirectoryChangesW reports changes below a notebook, including other
clients' on a share. Open Notebook from Server still uses the embedded
client, its passwords in the Credential Manager.snowbound, built for wasm32-unknown-unknown: web.rs is its platform
module, and web/index.html and web/glue.js are the page around it. glue.js brings the
canvas's pointer, wheel and touch, and a hidden text area's keys, composition and paste, as
the ui::Events winit would; web.rs stands in for winit's window and event loop and runs
State a turn per animation frame. tools/release_web.py builds it and deploys it to https://snowbound.paperclover.net.notebook::task), and work the desktop gives a thread runs once
the frame is done (spawn).notebook::fs's: std's elsewhere, here held in memory, with SQLite's VFS over
the same files, so replicas and notebooks live side by side under /Notebooks and
/Cache. A storage worker keeps them in the origin's private file system, writing the
byte ranges each burst changed through OPFS's synchronous handles, which only workers get.
One tab at a time holds them./Folders, its handle kept in IndexedDB, other apps' writes
read every few seconds. A browser takes no locks, so a commit there stands only once the
page has written the file and found nothing else wrote it since it was read; until then
it is uncertain, as one whose answer was lost, and it goes again on top of another app's
write. The sync popup says the folder isn't locked.spellbook, each fetched beside the module the
first time a word in its language is checked, text no run tags counting as the browser's
language; Add to Dictionary keeps its words in the browser's files. Text the bundled faces
lack takes Noto's, fetched by script the first time a page holds it (a CJK face cut to
the national standards' characters by tools/web/subset_cjk.py; emoji in Noto Color
Emoji's COLRv1 outlines, which draw paints itself), and the page is laid out again.?renderer= in the address picks one, as
--renderer does. The canvas draws each of draw's quads with its own calls (paths,
drawImage from the glyph atlas, glyphs of a colour from a sheet coloured once), and
corrects translucent colours' opacity for blending sRGB-encoded. A popup leaning in as it
opens draws into a canvas of its own laid over the page and leaned by a CSS 3D transform
of the same perspective, which the browser's compositor draws. A canvas keeps the first
kind of context it gives, so each renderer starts on a canvas of its own, which then takes
the page's place and its input. Only where even the 2D canvas fails does start reject,
with a NoGraphicsError.index.html names another build's folder than the one its module came from; finding
one, it fetches that build's module, JavaScript and fonts into the HTTP cache, then
reloads at a quiet moment (idle 90 seconds, or when the user comes back to the tab, with
no popup or dialog open and its files written), back on the same page and scroll. A
reload that didn't bring the build isn't tried again.On iOS the split moves. Touch text editing depends on affordances that users know by feel and that are expensive to imitate: the loupe, selection handles, the edit menu, autocorrect, dictation and hardware keyboard commands. So UIKit owns everything around the page:
UIScrollView), the keyboard and text
input (UITextInput), caret, selection highlight and handles, the format
bar, and connecting to servers.canvas draws the page into a CAMetalLayer and makes every editing
decision. The C surface exposes the active outline's text as the flat UTF-16
model UITextInput speaks. That is the same unit onestore ops measure
text in, so autocorrect and dictation become ordinary text ops with no
translation layer.crates/mobile is that surface: a static library with a C header,
covering libraries, shares, sections and views, built by an Xcode build
phase (apps/ios/build-rust.sh). All views share one GPU device and one
renderer, and text engines are pooled across views.No ui crate ships on iOS. Everything below the view is the desktop's code:
notebook::session with its replica and background publishing, the embedded
SMB client (credentials kept in the Keychain, as Files keeps its own), conflict
pages, search, spelling (through UITextChecker), and page management through
the same ops. Notebooks from Files
are read and written under NSFileCoordinator, so file providers see every
change.
Record Audio and Record Video record through AVFoundation and hand crates/mobile the
WAV file, or the camera's pictures and sound, which it stores as the desktop does. Audio
keeps recording in the background and with the screen locked, under the audio
background mode, as a lecture outlasts the screen's timeout; a call pauses it until the
system says to go on. The camera stops in the background, which ends a video recording
and saves it. A tap on a recording plays it with AVPlayer in a bar over the page's foot.
canvas or notebook. A host that reimplements one
of them has made a bug.draw everywhere, so a page
looks the same on a phone as on the desktop.