The shared shell of Paladin (Servo) and Nautilus (Chromium): the browser model, the egui chrome, and the winit application, written once and built on top of any engine that implements a small trait. Named after the nautilus shell's iridescent inner layer, which is also the paladin's armour.
A browser built on osmena is four things:
- a
Brandconstant (name, data directory, homepage, icon, update source, and the Google client id for the Launch Panel's Drive tile, if the product has one); - an
EngineInfoconstant (engine name and version, its data directory, what it can do); - an engine crate that implements
Engine,TabHost, andSurfacefromosmena-engine; - a
mainthat parses its command line and callsosmena_shell::run.
Everything else, including the multi-tab selection, the tab moves between windows and profiles, the native menus, the profile colours, the settings window, and the keymap, is here and shared.
Dependencies point only downwards.
| Crate | Tier | Holds | Depends on |
|---|---|---|---|
osmena-core |
model | Browser/Window/Tab, Command and dispatch, EngineEvent and apply, EngineInput, keymap, settings, profiles, Brand, EngineInfo. Pure and unit-tested. |
serde, url, keyboard-types |
osmena-engine |
engine contract | The Engine, TabHost, and Surface traits, PagePaint, Waker, EngineError; with the glutin feature, a Surface for engines that paint into memory. |
osmena-core, glow, raw-window-handle |
osmena-chrome |
chrome | The egui tab strip, toolbar, status overlay and crash panel; the theme (Inter, bundled under assets/fonts with its SIL Open Font License, and the shared style); the window, tab and page menus and the macOS menu bar as data and as native menus; pointer and key routing; the icon and the fatal-error dialog. |
osmena-core, egui, winit, muda |
osmena-shell |
shell | The winit App, BrowserWindow, SettingsWindow, the process glue, and run. Generic over Engine. |
osmena-chrome, osmena-engine, egui_glow |
osmena-xtask |
tool | icons: renders a browser's assets/logo.svg into its icon set, the .icns from assets/logo-macos.svg when the browser has one (macOS icons sit on Apple's grid, inset in a transparent canvas, which would look small on Windows). |
resvg |
An engine crate depends on osmena-core and osmena-engine only; it never
sees egui or winit. A browser's binary depends on osmena-shell and its
engine crate.
use osmena_core::update::{UpdateSource, build_number};
use osmena_core::{Brand, ChromeColor, EngineInfo, ExtensionSupport};
static BRAND: Brand = Brand {
name: "Nautilus",
binary: "nautilus",
data_dir: "Nautilus",
homepage: "https://www.wikipedia.org/",
chrome_color: ChromeColor::Shell,
icon_png: include_bytes!("../../../assets/icons/nautilus-256.png"),
version: env!("CARGO_PKG_VERSION"),
// CI sets OSMENA_BUILD_NUMBER when it packages a build; a local
// build has none and never updates itself.
build: build_number(option_env!("OSMENA_BUILD_NUMBER")),
// Where newer builds are announced (see "Updates" below), or None.
updates: Some(UpdateSource {
manifest_url: "https://github.com/thomasmost/nautilus/releases/latest/download/latest.toml",
}),
};
// In the engine crate:
pub static INFO: EngineInfo = EngineInfo {
name: "Chromium",
version: "154",
data_dir: "chromium",
supports_stop: true,
wheel_line_px: 100.0,
// `None` for an engine without extensions.
extensions: Some(ExtensionSupport {
store_name: "Chrome Web Store",
store_url: "https://chromewebstore.google.com/",
store_item_id: web_store_item_id,
}),
};
// In main, after the engine-specific preamble (helper-process dispatch,
// logging) and clap parsing:
osmena_shell::run(osmena_shell::Launch {
brand: &BRAND,
engine: &nautilus_engine::INFO,
args: cli.base,
start_engine: Box::new(|dir, waker| nautilus_engine::Engine::new(dir, waker)),
})The engine contract, in osmena-engine:
Engine:spin,drain_events,discard_pending,open_surface(a GL context on a native window),open_host(the tabs of one window), and, for engines with extensions,install_extension,set_extension_enabled, anduninstall_extension(defaulted to nothing);configure_downloads(for engines whoseEngineInfo::downloadsis set) andconfigure_blocked_sites, the policies the shell hands down at start and on every change (after an unblock the new list comes before the navigations that return tabs to their pages, so it must take effect before the nextnavigate; both defaulted: an engine's before-navigate hook is to cancel a main-frame navigation to a blocked site before any request (an engine that cannot tell frames apart may cancel those too) and reportEngineEvent::NavigationBlocked; the model blocks what the user types whether or not the engine has a hook).TabHost: create, adopt, close, navigate, history, show and hide, focus, resize, input (every pointer event carries the keyboard modifiers held at that moment, from the window's own state; an engine never infers them from the key events it saw),paint_page, which hands the shell either a blit function (the engine painted into GL) or a frame of BGRA pixels with the rectangles that changed (the engine paints into memory; the shell uploads those and draws either under the toolbar),open_extension_actionandclose_extension_popups(defaulted to nothing), andpage_commandandcontext_menu_closedfor the page's context menu: the engine reports what is under the pointer withEngineEvent::ContextMenuRequested(aPageContext: link, media, selection, editable state, misspelling), the chrome shows the menu, and what the user picks that only the engine can do (copy, paste, save, print, inspect, …) comes back as aPageCommand.EngineInfo::page_toolssays which of those the engine offers.replace_with_blankreplaces the tab's current history entry withabout:blankwhen the page it shows has just been blocked, so Back skips it (defaulted to a plain navigation, which adds an entry instead, so Back from a tab blocked in place stays on the block page; an engine that can replace an entry should). Forward after Back from a block page then lands on the bareabout:blankthe block page sat over; the model keeps no per-entry state, so the page cannot be restored and this is accepted.Surface: make current, resize, the glow handle, begin frame, present.
-
A look of its own: the theme. Inter for every label, pills for the address bar and the Launch Panel's field, widgets and tabs rounded at 8 px, cards at 14 and popups at 12 on a wide soft shadow. Each profile's colour gives the chrome an accent, the deep shade of the swatch's family (or its pastel in a dark system theme): the ring around a focused field, the text selection, the picked suggestion, the lit chips and the links. The toolbar's Back, Forward, Reload and Stop are painted glyphs in round buttons that fill only under the pointer, faded while they cannot act, like every other toolbar button; the address bar is a filled pill a shade darker than the toolbar, the page's colour with the accent ring while it has the keyboard. Every popup sits on a surface tinted a little by the swatch.
-
A frameless window. Browser windows open without the system title bar: the tab strip is the title bar (drag its empty space to move the window, double-click it to maximize, right-click it on Windows for the system menu) and, except on macOS, ends in minimize, maximize, and close controls; the edges still resize. On macOS the title bar turns transparent and the strip leaves room for the traffic lights. The setting "Use the system title bar and borders" (
system_title_barinsettings.toml) restores the system frame; on macOS it applies to windows opened afterwards. -
A new tab asks first: the Launch Panel. Ctrl+T and the "+" button open a wide panel over the window instead of a blank page. At its top, a large field: type an address or a search and press Enter, or pick a page from the history and bookmark matches that drop down under it; Escape or a click outside opens nothing. Before the first space the field completes the typing to the most visited page whose address starts with it (
gibecomesgithub.com, the rest selected after the caret, so typing on replaces it and Enter opens the page); Backspace drops the completion and leaves the typing, and a space makes the text a phrase, so Enter then searches. The first row of the dropdown is always what Enter opens (the completed page, the typed address with "Open", or the words with "Search Google"), and the engine's name sits in a chip at the field's right end while the text is a search. Under the field, a mosaic of tiles: Recent Drive files (the five files the connected Google account opened last; "Connect Google Drive" signs in through Google in a tab, only in a build whose brand has a Google client id), Weather (today's conditions, high and low where this device is: the panel asks the system for the location, which is what puts the question to the user, names it through BigDataCloud and asks Open-Meteo for the forecast, both without a key; a place can be named on the tile instead, and is then used without asking the system anything. A report stands fifteen minutes and a location an hour), Ask Claude and Ask ChatGPT (a question typed there opens the service with it in a new tab), and Most visited (a shelf across the panel of the five hosts with the most visits, each a disc with its favicon, or its initial, and the host under it; a close control at the disc's corner, under the pointer, hides that host). The panel fades and rises in, on a surface tinted by the profile's colour; a row of tiles is as tall as its tallest tile, and the field, a filled pill with a magnifier, turns the page's colour with the profile's accent around it while it has the keyboard. The gear at the bottom right opens the Launch Settings in the panel: the search engine as presets or a custom template (the same setting the address bar uses), the two question templates, the weather location and whether temperatures read in Fahrenheit or Celsius (unchosen, they follow the country the tile is showing), the Google account, and the tiles in edit mode, dragged to reorder and closed to remove, with "Restore default widgets". The same matches drop down under the address bar of an existing tab as you type. The history (history.tomlin the profile) keeps every page that finished loading, with its title and visit count, up to 5000 entries. -
Tabs that read at any width. Favicons are fitted to the pixels their slot covers and drawn on pixel boundaries, so they are crisp at every tab width and display scale; a loading tab shows a turning arc there, a page without an icon a globe. As tabs narrow they give up parts in Chromium's order: the active tab keeps its close control and then its icon, an inactive one keeps its icon (centred when it is all that fits) and shows the close control only under the pointer; a title that does not fit fades out rather than ending in an ellipsis, and the tooltip has the whole of it. The active tab opens into the toolbar with no line between them; the strip's edge runs under the others.
-
Tabs that drag. A tab (or the selected tabs) dragged along the strip slides between the others, which move out of its way, and lands where it is let go; dropped between two tabs of a folder it joins the folder, dropped anywhere else it leaves one. Dragged out of the window it moves to the window under the pointer, or to a new one. Tabs also slide to their places after a close or a reorder.
-
Chromium's shortcuts, with Cmd for Ctrl on macOS: tabs (
Ctrl+T,Ctrl+W/Ctrl+F4,Ctrl+Shift+Treopens the last closed,Ctrl+TabandCtrl+PgDn/Ctrl+PgUpcycle,Ctrl+1–Ctrl+8andCtrl+9jump), navigation (Alt+Left/Alt+RightorCmd+[/Cmd+],Ctrl+R/F5,Ctrl+.stops,Alt+HomeorCmd+Shift+Hgoes home),Ctrl+L/Alt+Dfor the address bar,Ctrl+NandCtrl+Shift+Wfor windows,F11orCtrl+Cmd+Ffor full screen (the page alone),Ctrl+,for settings,Ctrl+Qto quit. Chords use physical keys, so they are the same on every layout. -
Remappable shortcuts. The settings window lists every action with a primary and a secondary slot, like a game's controls screen: click a slot, press the new chord (Esc keeps the old one, Backspace clears it, right-click clears it); a chord in use moves to the action it is given to, and a note says what lost it. "Reset to defaults" restores the platform's bindings. The result is
keymap.tomlin the profile:[bindings] new-tab = ["Ctrl+KeyT"] reload = ["Ctrl+KeyR", "F5"] stop = [] # unbound
Chords are modifiers (
Ctrl,Alt,Shift,Meta;Cmd,Win,Optionare accepted) and a W3C key code joined with+. An action left out keeps its default; a chord that does not parse is a startup error, like a malformedsettings.toml. -
Tab folders. A tab's context menu (which acts on the selection when the tab is part of it: Ctrl-click adds, Shift-click extends) offers "Close all selected", "Move to folder…", and "Bookmark all selected…". A folder is named in a prompt ("Folder A", "Folder B", … by default, or an existing folder) and its tabs sit together in the strip behind one chip: collapsed, the chip shows the name and count and a click lists the tabs; expanded (from its menu), its tabs follow it. The chip's menu also renames, ungroups, or closes the folder. Folders are not kept across launches.
-
Session restore. The windows and tabs are written to
session.tomlin the profile a moment after they change and at exit (their place on screen, their order, folders, the active tab, the closed-tab list). With "Continue where you left off" in Settings (restore_windows_on_launch), the next start opens them again where they were: each window's active tab loads; the others wait, drawn faint, until first shown, but are warmed one after another half a second apart (each loaded out of sight for its title and favicon, then let go); a URL on the command line opens as a tab in the last window. "Restart to update" comes back the same way whatever the setting says (--restore). After a crash (the profile's claim names no live process), a bar under the toolbar offers the last session: Restore brings it back and closes the home-page window, Dismiss forgets it. "Reopen closed tab" works across a restart. -
Downloads, when the engine downloads (
EngineInfo::downloads). A file goes to the downloads directory (the system's Downloads folder, or the one Settings names) under its suggested name, made unique, or the system's save dialog asks when "Ask where to save each file" is on. A button appears at the toolbar's right end while the list is not empty, with a ring that fills as the downloads in progress do; it (orCtrl+J,Cmd+Shift+Jon macOS, or the ☰ menu's Downloads) opens a popover listing them newest first with their size, progress and speed, and the buttons for each one's state: Pause, Resume and Cancel while it goes, Open and Show in folder when done, Retry after a failure, and × to forget; "Clear all" and "Open folder" below. Quitting with a download going asks first. The list isdownloads.tomlin the profile (the newest 200 finished ones); what was still going when the browser closed is marked interrupted. -
Page zoom.
Ctrl+Plus(Ctrl+Equal, or the numpad's plus),Ctrl+MinusandCtrl+0(Cmdon macOS),Ctrlwith the wheel over the page, the ☰ menu's Zoom submenu or the macOS View menu zoom the page through Chromium's steps (25% to 500%). Every tab on the same host follows, the site remembers its level inzoom.tomlin the profile, and a chip beside the address bar shows the level while it is not the default, with a popover for −, +, and Reset. "Page zoom" in Settings (page_zoominsettings.toml) is the level of sites without one of their own. -
Blocked sites. A profile keeps a list of websites it refuses to show, keyed by registrable domain, so blocking
youtube.comcoverswww.youtube.comandm.youtube.com(the public suffix list keepsbbc.co.uka site andco.uknot; IP addresses andlocalhoststand whole;about:,data:andview-source:pages have no site). "Block on this profile" in a tab's menu asks first and warns that only the settings window unblocks; the "Blocked sites" section there lists the sites with Remove on each and a field that takes a host or a URL. The model stops what the user types before any request, and the engine's before-navigate hook stops links, scripts and redirects before any request where it sees them. A load the hook misses (an engine without one, or a redirect, a popup's first page, or a step Back or Forward it does not see) may be requested once, and is then replaced by the block page through theUrlChangedrule. The tab shows a block page the chrome paints overabout:blank, one of three lines on the profile's colour with the site named beneath; the address bar keeps the attempted address, Back returns to the page before, Reload re-rolls the line. Blocking replaces the history entry of every open tab on the site; unblocking sends them back. The list isblocked.tomlin the profile. -
Find in page, when the engine searches pages (
EngineInfo::find).Ctrl+F(or "Find in page…" in the ☰ menu, "Find…" in the macOS Edit menu) opens a pill at the top right of the page: the text is searched as it is typed, every match is highlighted and the active one scrolled into view, the count reads "3 of 41" or "No matches", Enter andF3(Ctrl+G) step forward, Shift+Enter andShift+F3(Ctrl+Shift+G) back, wrapping at the ends, "Aa" matches case, and Escape or × closes the bar leaving the active match selected. Switching tabs runs the search again in the new tab. An engine without it has the entries disabled. -
Bookmarks. The star at the address bar's right end (or
Ctrl+D) bookmarks the page into the group the last bookmark went to ("Bookmarks bar" to begin with) and opens a popover under the star to name it, move it to another group (or a new one), or remove it; the star is filled on a bookmarked page, and clicking it again edits. "Bookmark all selected…" in the tab menu (and "Bookmark all tabs…" in the Bookmarks menu) asks for a group name and saves the pages together. Bookmarks live inbookmarks.tomlin the profile. The bookmarks bar (Ctrl+Shift+B, or the Bookmarks menu's check item, or Settings) sits under the toolbar: the "Bookmarks bar" group's pages as chips with the site's favicon (a click loads the page, a middle click opens it behind, a right click offers open in new tab or window, copy, edit and delete, and a drag reorders), a "»" chip for the ones that did not fit, and the other groups as drop-down chips at the right end. The Bookmarks menu (in the ☰ menu and the macOS menu bar) has the same actions, import and export of an HTML bookmarks file (the format every browser reads and writes), and a submenu per group with "Open all", the pages, rename and delete. Bookmarks also appear among the suggestions under the address bar and in the launcher, with a star, ranked above history matches of the same kind. Favicons of visited sites are kept as PNGs underfavicons/in the profile so the bar shows them before a site is open. -
Updates over the air, when the brand names a source and the build has a number. The browser checks shortly after it starts and every six hours, downloads a newer build in the background, and offers "Restart to update" in the toolbar, the window menu, and the settings window; the restart installs it and reopens the same profile. See "Updates" below for what a browser's CI publishes.
-
Extensions, when the engine runs them (
EngineInfo::extensions). The toolbar ends in a puzzle-piece button: its menu lists the installed extensions (click one to open its action's popup), has a "Pin to toolbar" submenu with a check per extension, offers to install the extension on the active tab's store page, opens the store, loads an unpacked directory, and opens the settings window, whose Extensions section switches each extension on or off, pins it, removes it, and installs by store URL or id. Pinned extensions sit as icons beside the address bar; clicking one opens the popup under it. The model only holds the list (Browser::extensions) and the pinned ids (pinned_extensionsinsettings.toml); installing, enabling, and running them is the engine's, throughEngine::install_extensionand its siblings, and the engine reports every change withEngineEvent::ExtensionsChanged. -
The page's context menu, shaped like Chromium's: a link opens in a new tab (behind the current one, beside it) or a new window, is saved, or has its address copied; an image opens in a new tab, is saved, copied, or has its address copied; a video or audio loops, shows its controls, is saved, or has its address copied; selected text is copied, searched, or printed; an editable field gets spelling suggestions, Undo, Redo, Cut, Copy, Paste, Paste as plain text, Delete, and Select all; a plain page gets Back, Forward, Reload, Save as, Print, and View page source; Inspect comes last. What the engine cannot do is left out. Middle-click and Ctrl-click open a link in a background tab beside the current one, Shift-click in a new window.
-
Search. Address-bar text that is not a URL is searched, with Google by default;
[search]insettings.toml(and the settings window) names the provider and its URL, where{query}stands for the text, so another engine, or something other than a search engine, can take its place. -
Profiles. A profile is a directory of its own (settings, bookmarks, history, the engine's cookies and storage; `--profile
` picks one, `default` otherwise) with a name, a colour and an avatar. The toolbar ends in the avatar; its menu, the same Profiles menu the macOS menu bar has, is shaped like Chromium's: the current profile and "Edit…", the other profiles under an "Other Profiles" heading (each starts a browser for it: a profile is a process), then "Add Profile…", "Open Guest Profile" and "Manage Profiles…" (a dialog with a row per profile: open, edit or delete it, and the two ways to add one). The dialog asks for the name, the theme colour and the avatar (the name's initial, or a flat monochrome glyph from three tabs: sea creatures, the 24 most used emoji, and an assorted set; the colour is one of seven hues, each in a pastel, a middle and a deep shade) and, for any profile but the default one, whether to share the default profile's bookmarks (one list both keep) and whether to copy its extensions (installed here, then independent; only when the engine runs extensions). History is never shared. A new profile's directory is the slug of its name (`work-stuff`, then `work-stuff-2`); deleting one asks first, refuses a profile open in another browser, and for the current profile hands over to the default one and removes the directory on the way out. A guest profile is a throwaway: "Open Guest Profile" makes one (`guest`, then `guest-2`, marked by an empty `guest` file; named "Guest", grey, a ghost avatar, nothing shared) and starts a browser for it, which removes the directory when it exits; a guest is listed in no menu, cannot be edited, and one a crash left behind is swept at the next start once it is a minute old and no browser claims it. default one and removes the directory on the way out. -
The default browser (macOS). While the browser is not the system's default for web links, a bar under the toolbar offers to make it so (Make default, Don't ask again, Not now); the settings window's Default browser section shows the status with the same button and the switch for the bar. "Make default" asks the system, which asks the user. Links clicked in other apps arrive as
GURLApple Events and pages opened from the Finder asodoc: each opens as a tab of the focused window, brought to the front, and a link the browser was launched for takes the start's home-page tab instead. A browser gets all this by claiminghttpin its bundle'sInfo.plist(CFBundleURLTypes; alsohttps, and HTML pages inCFBundleDocumentTypesfor the system to list it); without the claim, or outside a bundle, nothing of it shows. Not on Windows yet. -
Settings (
settings.toml): homepage, new-tab URL, the search provider, a chrome colour per profile, the title bar switch, the pinned extensions, the session-restore switch, the default-browser offer (check_default_browser), the bookmarks bar switch and the group the last bookmark went to, the default page zoom,[downloads](the directory and whether to ask each time),[launch](the panel's widgets in order, the question templates, the weather location and units, hidden sites), and[profile](name, avatar, shared bookmarks, extensions to copy at the next start).
The shell updates a browser over the air from a manifest its CI publishes
with every build. The browser's side is three fields of its Brand: the
crate version, the build number CI stamped (OSMENA_BUILD_NUMBER in the
environment of the release build, read with option_env! through
osmena_core::update::build_number; a build without one never updates
itself), and the manifest's URL. The manifest is a TOML document,
osmena_core::update::Manifest:
version = "0.1.0"
build = 150
commit = "0123abcd"
published = "2026-10-29T12:00:00Z"
notes_url = "https://github.com/owner/repo/releases/tag/build-150"
[assets.windows-x86_64]
name = "nautilus_0.1.0_x64-setup.exe"
url = "https://github.com/owner/repo/releases/download/build-150/nautilus_0.1.0_x64-setup.exe"
sha256 = "…"
size = 123456789
[assets.macos-aarch64]
name = "Nautilus-macos-arm64.zip"
url = "…"
sha256 = "…"
size = 456789012A build is newer when its version is higher, or its build number is
within the same version. The asset keys are <os>-<arch> as Rust names
them (windows-x86_64, macos-aarch64, macos-x86_64); a platform
without an asset gets nothing from that build. On Windows the asset is
an NSIS installer that takes /S (cargo-packager's does); on macOS it is
a zip of the application bundle (ditto -c -k --keepParent). The
browsers publish a GitHub release per green push to main, tagged
build-<run number>, with the installers, the bundles, the symbols and
latest.toml; releases/latest/download/latest.toml is then the
manifest URL, which GitHub redirects to the newest release.
The shell checks 15 seconds after the engine starts and every six hours
after that (check_for_updates in settings.toml turns the automatic
checks off; "Check for updates…" still works by hand), downloads into
<brand data dir>/updates/<build>/, verifies the SHA-256, and shows the
"Restart to update" pill. The restart quits and installs: on Windows a
hidden PowerShell waits for the process to exit, runs the installer with
/S (which closes every other window of the browser too) and starts
the same profile again; on macOS the running bundle is moved into the
updates directory, the unpacked one takes its place, and a shell reopens
it for the profile once the process is gone. A check that finds the
browser up to date removes what the updates directory holds for builds
up to the running one. To try the flow against a local server, build
with OSMENA_BUILD_NUMBER=1 and start the browser with the hidden
--update-manifest http://127.0.0.1:8000/latest.toml.
The Drive tile signs in with OAuth 2.0 for installed apps (PKCE, a
loopback redirect served by the shell for one request, the consent
page in an ordinary tab). The browser's side is one field of its
Brand, google_client_id: the id of a Google Cloud "Desktop app"
OAuth client, which is not a secret. A brand reads it at compile time
from an environment variable (option_env!) so CI stamps it from a
repository variable and nothing is committed; None leaves the Drive
tile out of the panel and the Google row out of the Launch Settings.
The refresh token lives in the profile's google.toml
(Profile::google_path), never shared between profiles, revoked and
removed by Disconnect. The scopes are drive.metadata.readonly and
userinfo.email, nothing else.
Browsers pin a revision, never a branch, so any checkout builds the same:
[workspace.dependencies]
osmena-core = { git = "https://github.com/thomasmost/osmena", rev = "<sha>" }
osmena-engine = { git = "https://github.com/thomasmost/osmena", rev = "<sha>" }
osmena-shell = { git = "https://github.com/thomasmost/osmena", rev = "<sha>" }The four crates version together; bump all pins at once. To work on osmena
and a browser side by side, patch the git source to a checkout in
~/.cargo/config.toml (config patches override manifests):
[patch."https://github.com/thomasmost/osmena"]
osmena-core = { path = "/Users/you/vibes/browser-project/osmena/crates/osmena-core" }
osmena-engine = { path = "/Users/you/vibes/browser-project/osmena/crates/osmena-engine" }
osmena-chrome = { path = "/Users/you/vibes/browser-project/osmena/crates/osmena-chrome" }
osmena-shell = { path = "/Users/you/vibes/browser-project/osmena/crates/osmena-shell" }
osmena-xtask = { path = "/Users/you/vibes/browser-project/osmena/tools/osmena-xtask" }Cargo still resolves the pinned revision from GitHub even when every crate
is patched, so the repository must be reachable. Then commit in osmena,
push, and move the browser's rev to the new sha.
cargo test --workspace --features osmena-engine/glutincargo clippy --workspace --all-targets --features osmena-engine/glutin -- -D warningscargo fmt --all
Nothing here needs an engine, so the whole workspace builds in under a minute. The pins on winit, egui, and glow are exact because the engines share a GL context and window handles with them; a browser resolves to the same versions through its dependency on these crates.
MIT or Apache-2.0, at your option. osmena-chrome/src/keyutils.rs is
adapted from Servo's reference shell and keeps its MPL-2.0 header.