pub struct Server {
pub router: Router,
pub port: u16,
pub ip: [u8; 4],
pub gui_mode: bool,
/* private fields */
}Fields§
§router: Router§port: u16§ip: [u8; 4]§gui_mode: boolTrue when a native GUI window fronts this server. Only affects how the
startup banner is announced; see Server::announce_listening.
Implementations§
Source§impl Server
impl Server
Sourcepub fn init(config: ServerConfig) -> Result<Self, ServerError>
pub fn init(config: ServerConfig) -> Result<Self, ServerError>
Initialize a new server instance with the given configuration.
Sourcepub fn reload_templates(&self) -> bool
pub fn reload_templates(&self) -> bool
Re-reads the templates from .mbr/ (or the template folder), as the
desktop watcher does when an .html file there changes. For an
embedder without a watcher (the iOS app’s refresh). A template that
does not parse keeps the previous set and is logged; false then.
Sourcepub fn restored_files(&self) -> Option<usize>
pub fn restored_files(&self) -> Option<usize>
How many files Server::init restored from
EmbeddedStorage::snapshot; None when it scanned instead (no
snapshot, or one taken by another build or configuration).
Sourcepub async fn refresh(&self) -> RepoDiff
pub async fn refresh(&self) -> RepoDiff
Brings the index up to date with the repository’s storage when there
is no watcher to do it: re-lists the vault, compares every file with
what the index recorded (Repo::diff_vault), and applies the
differences exactly as a batch of watcher events would be applied —
the same invalidation, index updates and cache drops. Returns what it
found; an empty diff touches nothing.
Needs EmbeddedStorage (only then does the repository record the
stamps the diff compares against). Call after the initial scan, which
Repo::wait_for_media signals.
pub async fn start(&self) -> Result<(), ServerError>
Sourcepub async fn start_with_ready_signal(
&self,
ready_tx: Option<Sender<()>>,
) -> Result<(), ServerError>
pub async fn start_with_ready_signal( &self, ready_tx: Option<Sender<()>>, ) -> Result<(), ServerError>
Starts the server and optionally signals when ready to accept connections.
If a sender is provided, it will receive () once the server is bound and listening.
Sourcepub async fn start_with_port_retry(
&mut self,
ready_tx: Option<Sender<u16>>,
max_retries: u16,
) -> Result<(), ServerError>
pub async fn start_with_port_retry( &mut self, ready_tx: Option<Sender<u16>>, max_retries: u16, ) -> Result<(), ServerError>
Starts the server with automatic port retry on address-in-use errors.
If the configured port is already in use, this method will try incrementing
the port (up to max_retries times) until it finds an available port.
A warning is printed to stderr when the port is incremented.
If a sender is provided, it will receive the actual bound port once the server is listening.
Sourcepub async fn websocket_handler(
ws: WebSocketUpgrade,
__arg1: State<ServerState>,
headers: HeaderMap,
) -> Response
pub async fn websocket_handler( ws: WebSocketUpgrade, __arg1: State<ServerState>, headers: HeaderMap, ) -> Response
WebSocket handler for live reload file change notifications.
§Security
WebSocket handshakes are exempt from the same-origin policy, so without
this check any page the user happens to visit could open
ws://127.0.0.1:<port>/.mbr/ws/changes and watch the private
file-change feed in real time. Upgrades must therefore be same-origin,
and a handshake with no Origin is rejected as well (browsers
always send one on a WebSocket upgrade).
Sourcepub async fn get_site_info(
__arg0: State<ServerState>,
) -> Result<impl IntoResponse, StatusCode>
pub async fn get_site_info( __arg0: State<ServerState>, ) -> Result<impl IntoResponse, StatusCode>
Serves /.mbr/site.json from a cached body.
The body only changes when the repository does, so it is built once and
reused until invalidate_derived_caches drops it. Building it is
blocking CPU work on a large repo, so the (rare) rebuild runs on the
blocking pool rather than stalling an async worker.
Sourcepub async fn get_media_info(
__arg0: State<ServerState>,
) -> Result<impl IntoResponse, StatusCode>
pub async fn get_media_info( __arg0: State<ServerState>, ) -> Result<impl IntoResponse, StatusCode>
Returns media metadata (other_files) as JSON.
Waits for both the initial scan and media metadata population to complete before returning, ensuring rich metadata (dimensions, duration, etc.) is available.
Sourcepub async fn search_handler(
__arg0: State<ServerState>,
__arg1: Json<SearchQuery>,
) -> impl IntoResponse
pub async fn search_handler( __arg0: State<ServerState>, __arg1: Json<SearchQuery>, ) -> impl IntoResponse
Search endpoint for finding files by metadata and content.
POST /.mbr/search
Request body (JSON):
{
"q": "search query",
"limit": 50, // optional, default 50
"scope": "all", // "metadata", "content", or "all"
"filetype": "markdown",// optional filter
"folder": "/docs" // optional folder scope
}Response (JSON):
{
"query": "search query",
"total_matches": 42,
"results": [...],
"duration_ms": 15
}Sourcepub async fn tasks_handler(
__arg0: State<ServerState>,
__arg1: Json<TaskQuery>,
) -> Response<Body>
pub async fn tasks_handler( __arg0: State<ServerState>, __arg1: Json<TaskQuery>, ) -> Response<Body>
Task query endpoint for the task browser.
POST /.mbr/tasks
Every request field is optional, so {} means “all incomplete tasks in
the repository, grouped by file”:
{
"q": "report #work",
"folder": "/docs/",
"statuses": ["open"],
"priorities": [],
"due": "any",
"mode": "category",
"limit": 500
}Returns 404 when tasks_enabled is off. Server/GUI only — static
builds have no task endpoint at all.
The first request builds the index (one sequential read pass over the
repository’s markdown); later ones reuse it, and the watcher keeps it
fresh. Like search, this does not wait for the repository scan: partial
results with scan_in_progress: true beat a hung panel.
Sourcepub async fn raw_markdown_handler(
__arg0: Path<String>,
__arg1: State<ServerState>,
__arg2: ConnectInfo<SocketAddr>,
headers: HeaderMap,
) -> Response
pub async fn raw_markdown_handler( __arg0: Path<String>, __arg1: State<ServerState>, __arg2: ConnectInfo<SocketAddr>, headers: HeaderMap, ) -> Response
GET /.mbr/raw/{*path} — returns the raw markdown source of an existing
file plus an X-MBR-Content-Hash header for optimistic concurrency.
Sourcepub async fn save_markdown_handler(
__arg0: Path<String>,
__arg1: State<ServerState>,
__arg2: ConnectInfo<SocketAddr>,
headers: HeaderMap,
__arg4: Json<EditRequest>,
) -> Response
pub async fn save_markdown_handler( __arg0: Path<String>, __arg1: State<ServerState>, __arg2: ConnectInfo<SocketAddr>, headers: HeaderMap, __arg4: Json<EditRequest>, ) -> Response
POST /.mbr/edit/{*path} — overwrites an existing markdown file with the provided content, guarded by an optimistic-concurrency hash check and an atomic temp-file-then-rename write.
Sourcepub async fn task_toggle_handler(
__arg0: State<ServerState>,
__arg1: ConnectInfo<SocketAddr>,
headers: HeaderMap,
__arg3: Json<TaskToggleRequest>,
) -> Response
pub async fn task_toggle_handler( __arg0: State<ServerState>, __arg1: ConnectInfo<SocketAddr>, headers: HeaderMap, __arg3: Json<TaskToggleRequest>, ) -> Response
POST /.mbr/task — flips the status of a single task line.
{ "path": "docs/guide.md", "line": 42,
"expected": "- [ ] write the report !!", "to": "done" }{ "line": 42, "text": "- [x] write the report !! @done(2026-08-04 14:32)" }Gated on edit_enabled and the same Self::check_edit_access policy
as every other write endpoint — this writes to the user’s files, so it
answers to the editing switch, not to tasks_enabled (in-document
checkboxes exist whether or not the task browser does).
§expected and base_hash
expected alone is not enough. It proves that line line still reads
the way the client thinks, not that it is the same task: add two lines
above a list of identical - [ ] call back items and a page rendered
before the edit would toggle a different one, silently. So the client
also sends base_hash, the version of the file its line numbers come
from — the page’s own render (markdown_source_hash) or its
/.mbr/raw read — and any other version on disk is a 409. The
response carries the new version, so a page’s consecutive writes chain.
base_hash is optional: without it the line check is all there is, as
before.
| Status | Cause |
|---|---|
403 / 401 | Self::check_edit_access (disabled, CSRF, cross-origin, Host, token) |
404 | The path is not an editable markdown file, or it could not be read |
503 | The note is not downloaded (a cloud provider’s placeholder) and did not arrive within a few seconds; Retry-After |
400 | Path outside the root, a file that is not UTF-8, no such line, or the line is not a task |
409 | The line no longer matches expected, or base_hash names another version of the file |
422 | The body is not a well-formed request (axum’s Json rejection, before this handler runs) |
500 | The write failed |
Sourcepub async fn flashcard_review_handler(
__arg0: State<ServerState>,
__arg1: ConnectInfo<SocketAddr>,
headers: HeaderMap,
__arg3: Json<FlashcardReviewRequest>,
) -> Response
pub async fn flashcard_review_handler( __arg0: State<ServerState>, __arg1: ConnectInfo<SocketAddr>, headers: HeaderMap, __arg3: Json<FlashcardReviewRequest>, ) -> Response
POST /.mbr/flashcard-review — appends one review to a flashcard’s history.
{ "path": "notes/french.md", "line": 12,
"expected": "What is the capital of France?", "rating": "good",
"at": "2026-10-06 13:45" }{ "entry": "2026-10-06 13:45 - Good", "line": 15,
"inserted_at": 14, "inserted": [": ___Review History___", " * 2026-10-06 13:45 - Good"] }Same gate, same expected guard and same atomic write as
Self::task_toggle_handler; the source surgery is
mbr_core::flashcards::append_review.
The entry is stamped with the reviewer’s wall clock (at), not the
server’s: the format carries no offset and the deck replays entries as
browser-local time, so a server in another time zone would put every
review hours away from where FSRS’s 1m/10m steps expect it. at is
held to the exact entry format and to within
mbr_core::flashcards::REVIEW_TIME_WINDOW_HOURS of the server’s UTC
clock — any real time zone passes, garbage and far past/future do not.
| Status | Cause |
|---|---|
403 / 401 | Self::check_edit_access |
404 | The path is not an editable markdown file, or it could not be read |
503 | The note is not downloaded (a cloud provider’s placeholder) and did not arrive within a few seconds; Retry-After |
400 | Path outside the root, or a file that is not UTF-8 |
409 | base_hash names another version of the file; the line is gone, changed, or no longer a top-level term — the client’s copy is stale — or no insertion keeps every other card intact |
422 | Malformed body, including an unknown rating or a missing, malformed or implausible at |
500 | The write failed |
Sourcepub async fn create_markdown_handler(
__arg0: Path<String>,
__arg1: State<ServerState>,
__arg2: ConnectInfo<SocketAddr>,
headers: HeaderMap,
__arg4: Json<CreateRequest>,
) -> Response
pub async fn create_markdown_handler( __arg0: Path<String>, __arg1: State<ServerState>, __arg2: ConnectInfo<SocketAddr>, headers: HeaderMap, __arg4: Json<CreateRequest>, ) -> Response
POST /.mbr/create/{*path} — creates a new markdown file with the given
contents. {*path} is a repo-relative filesystem path with extension.
Sourcepub async fn mkdir_handler(
__arg0: Path<String>,
__arg1: State<ServerState>,
__arg2: ConnectInfo<SocketAddr>,
headers: HeaderMap,
) -> Response
pub async fn mkdir_handler( __arg0: Path<String>, __arg1: State<ServerState>, __arg2: ConnectInfo<SocketAddr>, headers: HeaderMap, ) -> Response
POST /.mbr/mkdir/{*path} — creates a directory (idempotent). {*path} is
a repo-relative filesystem path. No request body.
Sourcepub async fn upload_handler(
__arg0: State<ServerState>,
__arg1: ConnectInfo<SocketAddr>,
headers: HeaderMap,
__arg3: Query<UploadParams>,
body: Bytes,
) -> Response
pub async fn upload_handler( __arg0: State<ServerState>, __arg1: ConnectInfo<SocketAddr>, headers: HeaderMap, __arg3: Query<UploadParams>, body: Bytes, ) -> Response
POST /.mbr/upload?dir=<>&name=<> — writes a raw binary asset (the
editor’s image uploader) into the repo next to the note being edited and
returns a URL the editor and rendered site both resolve. The body is the
raw file bytes; the body-size cap comes from upload_max_bytes
(oversized bodies are rejected with 413 by DefaultBodyLimit).
Sourcepub async fn move_markdown_handler(
__arg0: Path<String>,
__arg1: State<ServerState>,
__arg2: ConnectInfo<SocketAddr>,
headers: HeaderMap,
__arg4: Json<MoveRequest>,
) -> Response
pub async fn move_markdown_handler( __arg0: Path<String>, __arg1: State<ServerState>, __arg2: ConnectInfo<SocketAddr>, headers: HeaderMap, __arg4: Json<MoveRequest>, ) -> Response
POST /.mbr/move/{*path} — moves/renames a markdown file, rewriting
inbound links repo-wide and (on stem change) bare [[Name]] links.
{*path} is the source repo-relative filesystem path with extension.
Sourcepub async fn serve_media_viewer(
__arg0: State<ServerState>,
__arg1: OriginalUri,
__arg2: Query<MediaViewerQuery>,
) -> impl IntoResponse
pub async fn serve_media_viewer( __arg0: State<ServerState>, __arg1: OriginalUri, __arg2: Query<MediaViewerQuery>, ) -> impl IntoResponse
Media viewer endpoint for video, PDF, audio, and image content.
GET /.mbr/videos/?path=<encoded_path> GET /.mbr/pdfs/?path=<encoded_path> GET /.mbr/audio/?path=<encoded_path> GET /.mbr/images/?path=<encoded_path>
Renders the media_viewer.html template with the appropriate media type and validated media path. The path query parameter must be URL-encoded and point to a valid file within the repository.
Sourcepub async fn serve_mbr_assets(
__arg0: Path<String>,
__arg1: State<ServerState>,
) -> Result<impl IntoResponse, StatusCode>
pub async fn serve_mbr_assets( __arg0: Path<String>, __arg1: State<ServerState>, ) -> Result<impl IntoResponse, StatusCode>
Serves assets from /.mbr/* path.
Priority:
- If template_folder is set, serve from there (js/ for components, rest from root)
- Otherwise, check .mbr/ directory in base_dir
- Fall back to compiled-in DEFAULT_FILES
§Security
Path traversal attacks are blocked by safe_join_asset which validates
that resolved paths remain within the intended directory, and
[is_servable_mbr_asset] restricts what may be read out of that
directory at all — the template folder also holds config.toml, which
carries the Argon2 edit_token_hash and must never be served.
Auto Trait Implementations§
impl !RefUnwindSafe for Server
impl !UnwindSafe for Server
impl Freeze for Server
impl Send for Server
impl Sync for Server
impl Unpin for Server
impl UnsafeUnpin for Server
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);