macOS GUI: source-build fixes, robustness, packaging, bundled font, docs - #60
Merged
Conversation
Four fixes surfaced while running the GUI build from source: 1. Agent/shell PATH (SPC a a → claude not found) GUI apps launched from Finder/Dock inherit a minimal PATH, so `claude` (in /usr/local/bin) and version-manager shims were unreachable. `augment_path` now resolves the user's login+interactive shell PATH once (cached, timeout-guarded, sentinel-extracted to survive rc banners), like Emacs exec-path-from-shell, with the prior static home-dir augmentation kept as a fallback. 2. Manual KB help index (SPC h h → "no such KB node: index") The help system resolves nodes through the cozo-backed KbQueryLayer, but the ~380 seed nodes were only inserted into the in-memory KB — invisible to kb_get/help whenever no manual cozo was loaded (e.g. a source build). Now we always build an in-memory manual cozo: from the pre-built KB when found, else from the seed nodes. Also discover the workspace assets/mae-manual.cozo in source/dev builds (ancestor walk). 3. Manual KB is loaded read-only The pre-built KB is opened via a throwaway temp copy (sled writes recovery snapshots on open), so we never mutate a git-tracked asset or drift an install's checksum. 4. Unified Scheme eval path (SPC e b → "expected procedure, got void"; SPC b s → "undefined variable: format-before-save") Per the core rule that the human, MCP clients, and the AI peer call the same primitives: interactive eval (eval-buffer/line/region) now routes through the same drain_pending_scheme_evals path as MCP/AI, replacing the non-yielding eval_for_repl that could not drain hooks fired mid-eval (e.g. option-change from set-option!). The hook drain also falls back to command dispatch when a hook names an editor command (e.g. format-before-save) rather than a Scheme function. Verified live via the MCP socket; 1115 mae-shell/mae-scheme lib tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…unch A missing "fancy" font must never block the GUI. The font chain previously ended at the CSS-generic "monospace" family (which CoreText does not resolve on macOS) and then hard-errored "no monospace font found", so a fresh machine without JetBrains Mono / Fira Code / Cascadia Code could not open the GUI at all (only relevant to source/app launches; the cask CLI `mae` is TUI). Now each typeface (regular/bold/italic/bold-italic) resolves through: preferred families (configured override + Nerd Font variants + popular coding fonts) → system monospace families (Menlo/SF Mono/Monaco, DejaVu/Liberation/ Noto, Consolas, Courier New) → the platform default typeface (`legacy_make_typeface`), which always returns a usable font. If none of the preferred fonts are installed we log a warning and continue rather than fail. Verified: with JetBrains Mono removed, the GUI now logs the warning and initializes the window (previously it aborted on startup). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erflow Dropping a long list recursed one native stack frame per element (the derived drop of the `Rc<(Value, Value)>` cdr chain), overflowing the stack on long lists — surfaced first on macOS, whose smaller thread stacks hit it sooner (bench_list_operations aborted; it passes on Linux only by stack-size luck). Back `Value::Pair` with a tuple struct `ConsCell(Value, Value)` whose `Drop` walks the cdr chain iteratively, unlinking each solely-owned node so its own drop is O(1). Shared sublists (refcount > 1) are left intact and dismantled by their last owner. Field access stays `.0`/`.1`, so the change is confined to the two construction sites. The VM's proper tail calls already let Scheme build huge lists (verified by r7rs s3_5 tail-recursion tests); now dropping them is safe too. Regression tests build + drop 1M-element and shared lists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`mae` with no flags now opens the GUI on a desktop session and transparently falls back to the terminal UI when there is no graphics frontend — over SSH, on a bare tty, or on a headless server. `--gui` forces the GUI (used by the MAE.app launcher); `--no-gui`/`--tui`/`-nw` force the terminal UI. Previously a GUI-enabled binary always tried to open a window, which fails without a display. Detection is factored into pure, unit-tested policy functions (`display_available_from_env`, `should_use_gui`) covering the SSH/X11/Wayland/ macOS and force-flag matrices, so the behavior is verified in CI rather than by one-off manual checks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d keys macOS FSEvents reports canonical paths (`/private/var/...`) while callers hold the symlinked form (`/var/...`, `/tmp/...`). The watcher keyed its path→ids map on the caller's paths, so removal events never matched — dropped node ids were lost and stale KB nodes lingered (and emitted Upserted paths didn't match the app's). Normalize (canonicalize, with a fallback for already-removed files) at every boundary — seed, record_ids, and event handling — so paths compare equal regardless of symlinks. Affects KB file watching / auto-reload for org dirs under any symlinked path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First time running the full suite on macOS surfaced several environment fragilities (all real, none flaky once understood): - kb_graph_validation: used an on-disk sled CozoDB per test; sled's per-write fsync on APFS made it pathologically slow (~39s/test, 1092s total). Switch to in-memory CozoDB (open_mem) — same Datalog logic, 1092s -> 56s. - project_tests: compared a raw path against a canonicalized one; macOS's /var -> /private/var symlink made them diverge. Canonicalize both sides. - mcp_event_loop_integration: `ioctl(TIOCSCTTY)` failed to compile on macOS (request-arg type differs); cast to the inferred type. - canvas font tests: relied on the "monospace" generic family, which CoreText does not resolve on macOS. Extract the renderer's font resolution into testable functions and route the tests through it (also adds coverage that a missing preferred font never blocks launch). Full workspace suite now passes on macOS in ~187s (was 20+ min). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The macOS Homebrew formula shipped a TUI-only binary (`mae-macos-aarch64-tui.tar.gz`),
so `mae` from the command line never launched the GUI — only `MAE.app` (cask)
did, and the formula caveats ("mae file.rs # GUI mode") were misleading.
Now the formula ships the GUI-capable binary (same one in MAE.app). It launches
the GUI by default and falls back to the terminal UI over SSH / on a tty /
headless, or with `-nw` (see the display auto-detection in `feat(gui)`), so a
single binary serves both — matching the Linux formula. This makes the existing
caveats accurate.
Changes:
- Build only what's needed (shim, manual KB, daemon) then the GUI `mae`; drop
the separate non-GUI build and the `mae-tui` copy.
- Rename the formula artifact `mae-macos-aarch64-tui.tar.gz` ->
`mae-macos-aarch64.tar.gz` and ship the GUI binary; the cask's bundled CLI
`mae` uses it too. Updated all artifact references (upload, release files,
checksum, download table).
- The tap-update step now rewrites the formula `url` to the new filename so the
tap stays in sync at release time (changing it ahead of a release would 404
against the current release's assets).
Note: only runs on a release tag; validate on the next release (or a
workflow_dispatch dry run) before relying on it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a resolution tier between the preferred (installed) families and the system-monospace fallback: if MAE_FONT_DIR is set (the MAE.app launcher points it at the bundle's Resources/fonts), load the bundled JetBrains Mono static TTF for each style directly via the font manager. This lets the intended font render on machines without a system install, while a user-installed Nerd Font (with icon glyphs) still wins because it sits earlier in the chain. The mechanism is font-agnostic — it loads whatever JetBrainsMono-*.ttf files are bundled (base font now; a license-clean patched Nerd Font later). The warning about falling back to a system font now only fires when neither a preferred nor the bundled font is available. Tests cover the per-style filename mapping and that resolution never fails even with a nonexistent bundle dir. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundles JetBrains Mono patched with Nerd Font glyphs so the intended font AND icon glyphs render out of the box — no system font install required — while a user-installed Nerd Font still takes precedence. License: every bundled component is permissive/GPL-compatible (JetBrains Mono OFL-1.1; glyph sets MIT / Apache-2.0 / OFL-1.1 / CC-BY-4.0). The patch deliberately EXCLUDES the "Font Logos" set (unlicensed third-party brand/distro logos, also a trademark concern). Full attribution + the reproducible patch recipe are in assets/fonts/LICENSES.md; JetBrains Mono's OFL is in assets/fonts/OFL.txt. Powerline Symbols was confirmed MIT (upstream powerline/powerline), not the "vague" license the Nerd Fonts audit implied. - assets/fonts/: 4 patched static TTFs (Regular/Bold/Italic/BoldItalic) + OFL.txt + LICENSES.md. - release.yml: bundle assets/fonts into MAE.app/Contents/Resources/fonts and the Linux AppImage, with the launchers exporting MAE_FONT_DIR. - canvas.rs: the icon typeface also falls back to the bundled font, so icon glyphs render from the bundle when no system Nerd Font is installed. Verified locally: with system JetBrains Mono hidden and MAE_FONT_DIR pointing at assets/fonts, the GUI initializes using the bundled font (no system-fallback warning). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g surface Bring the docs in line with this branch's behavior changes: - New manual node concept:fonts — font_family/font_size/icon_font_family, the never-fail resolution order (preferred → bundled → system → default), the bundled license-clean JetBrains Mono Nerd Font, icon config, and MAE_FONT_DIR. Scheme-first examples (init.scm / :set), config.toml noted as legacy bootstrap. - concept:gui — `mae` now launches the GUI by default when a display is available and falls back to the terminal UI over SSH/tty/headless; `--gui` forces GUI, `--no-gui`/`--tui`/`-nw` force terminal. Updated backend-selection and font bullets; links to concept:fonts. - concept:options — added font-family / icon-font-family rows; explicit note that init.scm is the primary config surface and config.toml is legacy bootstrap (`:set-save` writes init.scm). - index — link the new fonts node. - README — corrected launch commands (mae = GUI by default; --no-gui/--gui). - CLAUDE.md — options regression-guard now references init.scm round-trip (not config.toml) since :set-save persists to init.scm. The prebuilt manual KB (assets/mae-manual.cozo) is regenerated from these .org sources at release time (build-manual-kb); run `make manual-kb` to refresh it locally. Verified the new node parses + ingests (235 nodes, no warnings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al-kb # Conflicts: # .github/workflows/release.yml
…sync) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 25, 2026
Closed
Closed
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First day building/running MAE's GUI from source on a Mac. This branch makes the GUI work from source, fixes everything that first macOS run surfaced (all regression-tested), updates packaging so the cask ships a GUI-by-default binary with a bundled font, and brings the docs in line.
Runtime / GUI
--guiforces GUI (MAE.app launcher),--no-gui/--tui/-nwforce terminal. Pure unit-tested detection.claude(and other tools) resolve when launched from the GUI.assets/mae-manual.cozo.SPC e b), MCP, and the AI peer all use the same primitive; hooks can name editor commands (e.g.format-before-save).macOS-surfaced bugs (all fixed + regression-tested)
ConsCellDrop(TCO itself verified sound via r7rs tests)./var↔/private/varpath canonicalization, libcioctlcast, font-resolver test refactor. Full suite green in ~187s.Packaging
mae(artifact renamedmae-macos-aarch64.tar.gz; caveats become accurate; tapurlauto-syncs at release).MAE_FONT_DIRfor text and icons. Recipe + attribution inassets/fonts/LICENSES.md.Docs
concept:fonts(font/icon config), correctedconcept:guilaunch behavior,concept:options(+font rows, init.scm-primary note), README launch commands, CLAUDE.md options note. Scheme-first framing (config.toml = legacy bootstrap).Release notes / caveats
release.ymlchanges only execute on a release tag — validate on the first release or aworkflow_dispatchdry run..appis unsigned/un-notarized; the cask'sxattr -crpostflight strips quarantine (Gatekeeper may still prompt on a pristine Mac).heimdall@<host>, not the project email — can rebase-fix if desired.🤖 Generated with Claude Code