From 3b6e26daa969770f86fe6375cbd8edaecdafb179 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 18 Oct 2026 22:10:16 +0300 Subject: [PATCH 01/27] Render Markdown task checkboxes and enable missing-glyph fallback --- .github/workflows/build-full.yml | 26 ++++++ .github/workflows/release-kosmo.yml | 34 +++++++- CHANGES.md | 7 ++ README.md | 11 +++ docs/releasing-kosmo.md | 6 ++ install.sh | 9 ++ merenda.nimble | 2 +- packaging/macos/build-text-libraries.sh | 59 +++++++++++++ packaging/macos/bundle-text-libraries.sh | 46 ++++++++++ packaging/macos/install-local.sh | 8 +- packaging/windows/x64-windows-staticcrt.cmake | 4 + src/merenda/nimkit/drawing/drawing.nim | 2 + src/merenda/nimkit/text/markdownviews.nim | 79 +++++++++++++++--- src/merenda/nimkit/text/textlayout.nim | 15 ++-- tests/nimkit/font_layout.nim | 33 +++++++- tests/nimkit/markdownviews.nim | 83 +++++++++++++++++++ tests/nimkit/monotextviews.nim | 21 +++++ tests/nimkit/renderfragments.nim | 7 +- tests/nimkit/textlayout.nim | 7 +- 19 files changed, 436 insertions(+), 23 deletions(-) create mode 100644 packaging/macos/build-text-libraries.sh create mode 100644 packaging/macos/bundle-text-libraries.sh create mode 100644 packaging/windows/x64-windows-staticcrt.cmake diff --git a/.github/workflows/build-full.yml b/.github/workflows/build-full.yml index 85975ad6..68187eb1 100644 --- a/.github/workflows/build-full.yml +++ b/.github/workflows/build-full.yml @@ -58,6 +58,10 @@ jobs: - name: Check shared test runner coverage run: nim e --skipParentCfg:on --skipProjCfg:on .github/scripts/check_test_imports.nims + - name: Install macOS text rendering libraries + if: runner.os == 'macOS' + run: brew install harfbuzz fribidi + - name: Setup software OpenGL (Mesa) if: runner.os == 'Windows' uses: ssciwr/setup-mesa-dist-win@v3.0.0 @@ -87,6 +91,8 @@ jobs: libgl1-mesa-dev \ libgles2-mesa-dev \ libegl1-mesa-dev \ + libharfbuzz-dev \ + libfribidi-dev \ mesa-utils \ vulkan-tools \ mesa-vulkan-drivers \ @@ -260,6 +266,8 @@ jobs: libgl1-mesa-dev \ libgles2-mesa-dev \ libegl1-mesa-dev \ + libharfbuzz-dev \ + libfribidi-dev \ mesa-utils \ vulkan-tools \ mesa-vulkan-drivers \ @@ -308,6 +316,24 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Install macOS text rendering libraries + if: runner.os == 'macOS' + run: brew install harfbuzz fribidi + + - name: Install Linux text rendering libraries + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends libharfbuzz-dev libfribidi-dev + + - name: Install Windows text rendering headers + if: runner.os == 'Windows' + shell: pwsh + run: | + & "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" install harfbuzz:x64-windows + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + "CPATH=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/include/harfbuzz" >> $env:GITHUB_ENV + - name: Cache packages uses: actions/cache@v4 with: diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index ed64c885..934faa61 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -81,6 +81,8 @@ jobs: "libegl1-mesa-dev:$APT_ARCH" \ "libgl1-mesa-dev:$APT_ARCH" \ "libgles2-mesa-dev:$APT_ARCH" \ + "libharfbuzz-dev:$APT_ARCH" \ + "libfribidi-dev:$APT_ARCH" \ "libvulkan-dev:$APT_ARCH" \ "libx11-xcb-dev:$APT_ARCH" \ "libxcb1-dev:$APT_ARCH" \ @@ -176,6 +178,8 @@ jobs: libegl1-mesa-dev \ libgl1-mesa-dev \ libgles2-mesa-dev \ + libharfbuzz-dev \ + libfribidi-dev \ libvulkan-dev \ libx11-xcb-dev \ libxcb1-dev \ @@ -341,6 +345,8 @@ jobs: # pkg-config inject their shared-only -l flags into this static link. staticPkgConfigDir="$RUNNER_TEMP/kosmo-empty-pkgconfig" mkdir -p "$staticPkgConfigDir" + # Keep text shaping headers available after hiding graphics .pc files. + echo "HARFBUZZ_CFLAGS=$(pkg-config --cflags harfbuzz)" >> "$GITHUB_ENV" echo "PKG_CONFIG_LIBDIR=$staticPkgConfigDir" >> "$GITHUB_ENV" - name: Build Kosmo @@ -352,6 +358,7 @@ jobs: --cc:gcc \ --gcc.exe:"$MUSL_CC" \ --gcc.linkerexe:"$MUSL_CC" \ + --passC:"$HARFBUZZ_CFLAGS" \ --passL:-static \ --opt:size \ --debugger:native \ @@ -500,6 +507,9 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Install text rendering libraries + run: brew install meson ninja pkg-config + - name: Cache packages uses: actions/cache@v4 with: @@ -621,6 +631,9 @@ jobs: iconutil -c icns "$iconset_path" \ -o "$app_path/Contents/Resources/Kosmo.icns" + text_prefix="$RUNNER_TEMP/kosmo-text-libraries" + bash packaging/macos/build-text-libraries.sh "$text_prefix" + export PKG_CONFIG_PATH="$text_prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" nim c \ --opt:size \ --debugger:native \ @@ -628,10 +641,13 @@ jobs: --stackTrace:off \ -d:nimStackTraceOverride \ -d:moe.embedded \ + -d:harfbuzzyDynlib=@executable_path/../Frameworks/libharfbuzz.dylib \ + -d:harfbuzzyFribidiDynlib=@executable_path/../Frameworks/libfribidi.dylib \ -d:KosmoVersion="$version" \ -d:KosmoGitHashOverride="$revision" \ --out:"$app_path/Contents/MacOS/kosmo" \ src/merenda/kosmo/kosmo.nim + bash packaging/macos/bundle-text-libraries.sh "$app_path" "$text_prefix" xcrun dsymutil \ "$app_path/Contents/MacOS/kosmo" \ -o "$dsym_path" @@ -738,6 +754,15 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Install text rendering libraries + shell: pwsh + run: | + & "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" install ` + harfbuzz:x64-windows-staticcrt fribidi:x64-windows-staticcrt ` + --overlay-triplets=packaging/windows + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + "CPATH=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/include/harfbuzz" >> $env:GITHUB_ENV + - name: Install Nim uses: jiro4989/setup-nim-action@v2 with: @@ -800,6 +825,7 @@ jobs: if ($imports -match '(?im)^\s*DLL Name:\s+libgcc_s.*\.dll\s*$') { throw 'Kosmo executable must not depend on a MinGW GCC runtime DLL.' } + Copy-Item "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/bin/*.dll" dist/ $process = Start-Process ` -FilePath "$PWD/dist/kosmo.exe" ` -PassThru @@ -823,8 +849,14 @@ jobs: run: | Copy-Item data/FONT-LICENSES.md dist/FONT-LICENSES.md Copy-Item -Recurse data/font-licenses dist/font-licenses + New-Item -ItemType Directory -Force dist/text-library-licenses | Out-Null + Get-ChildItem "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/share" -Directory | ForEach-Object { + if (Test-Path "$($_.FullName)/copyright") { + Copy-Item "$($_.FullName)/copyright" "dist/text-library-licenses/$($_.Name).txt" + } + } Compress-Archive ` - -Path dist/kosmo.exe, dist/FONT-LICENSES.md, dist/font-licenses ` + -Path dist/kosmo.exe, dist/*.dll, dist/FONT-LICENSES.md, dist/font-licenses, dist/text-library-licenses ` -DestinationPath dist/kosmo-windows-amd64.zip ` -Force diff --git a/CHANGES.md b/CHANGES.md index be574bcf..7d5f793c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -71,6 +71,13 @@ Use a compact menu that opens upward, and keep its checked selection synchronized with Settings and detached editor windows. +- Enable HarfBuzz text rendering and automatic system-font fallback for missing + glyphs in the editor, terminal, and other text views, including checkbox symbols + absent from the bundled Nerd Font. + +- Render Markdown task lists with ☐ for `[ ]`, ☑ for `[x]` or `[X]`, and ☒ for + `[-]`, preserving nested lists, numbering, and inline formatting. + - Add a Windows VirtualBox test setup, matching Guest Additions, an OpenGL 3.3 console probe, and guards against concurrent bhyve operation. Document the failed GPU acceleration test with the FreeBSD 7.2.20 packages. diff --git a/README.md b/README.md index ccec21a9..b1eb3079 100644 --- a/README.md +++ b/README.md @@ -600,6 +600,10 @@ to focus an existing terminal. If none is open, Kosmo opens one in the nearest pane to the right, creating a right split when needed. **New Terminal** in the File menu still opens an additional terminal in the active pane. +Markdown task lists show ☐ for `- [ ]`, ☑ for `- [x]` (also `[X]`), and ☒ for +`- [-]`. Text rendering uses system-font fallback for characters missing from +the selected font, including symbols absent from the bundled Nerd Font. + In Moe's normal mode, `:e path` opens a Kosmo document tab or selects the file's existing tab. `:help` and `:config` open reusable tabs; `:config` keeps Moe's interactive @@ -788,6 +792,10 @@ for supported builds, custom install locations, and the static Linux build. ### Build from source +Install HarfBuzz and FriBidi development libraries for your platform first +(`brew install harfbuzz fribidi pkg-config` on macOS, or the `libharfbuzz-dev` +and `libfribidi-dev` packages on Debian/Ubuntu). + From your Merenda checkout, install the extra Kosmo dependencies, then build and launch it: @@ -803,12 +811,15 @@ On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: ```sh +brew install meson ninja pkg-config nim install_kosmo ``` This uses Atlas to resolve dependencies, replaces `~/Applications/Kosmo.app`, and updates the `~/.local/bin/kosmo` link. Restart Kosmo if it was already running. Set `KOSMO_INSTALL_DIR` or `KOSMO_BIN_DIR` to choose other locations. +The app build compiles and bundles its text libraries for macOS 13, so the +installed app does not require Homebrew at runtime. ## Explore more diff --git a/docs/releasing-kosmo.md b/docs/releasing-kosmo.md index d242d913..5e46dbb1 100644 --- a/docs/releasing-kosmo.md +++ b/docs/releasing-kosmo.md @@ -14,6 +14,12 @@ Each executable archive also includes the notices for Kosmo's bundled IBM Plex Sans and JetBrains Mono Nerd Font Mono resources. On macOS the notices live in the app's `Contents/Resources` directory. +macOS bundles minimal HarfBuzz and FriBidi libraries built for macOS 13 in +`Contents/Frameworks`, with their notices in `Contents/Resources/text-library-licenses`. +Windows archives place the text DLLs beside `kosmo.exe`; the installer copies +them together and installs their notices with the font notices. Linux and +FreeBSD use system HarfBuzz and FriBidi libraries, including the musl build. + The five bundled Moe themes are embedded in the executable and installed into Kosmo's platform asset cache for Moe's file-based theme loader. Release archives do not need a separate `data/moe/themes` directory, and theme discovery does not diff --git a/install.sh b/install.sh index 2cde2062..8f31a1dd 100755 --- a/install.sh +++ b/install.sh @@ -158,6 +158,10 @@ install_notices() { rm -rf "$KOSMO_DOC_DIR/font-licenses" cp -R "$extract_dir/font-licenses" "$KOSMO_DOC_DIR/font-licenses" fi + if [ -d "$extract_dir/text-library-licenses" ]; then + rm -rf "$KOSMO_DOC_DIR/text-library-licenses" + cp -R "$extract_dir/text-library-licenses" "$KOSMO_DOC_DIR/text-library-licenses" + fi echo "install.sh: installed bundled-font notices to $KOSMO_DOC_DIR" >&2 } @@ -289,6 +293,11 @@ case "$platform" in echo "install.sh: release archive did not contain kosmo.exe" >&2 exit 1 fi + for library in "$extract_dir/"*.dll; do + if [ -f "$library" ]; then + install_command "$library" "$(basename "$library")" + fi + done install_command "$extract_dir/kosmo.exe" kosmo.exe install_notices "$extract_dir" ;; diff --git a/merenda.nimble b/merenda.nimble index dbf90fc6..26ee9f95 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,7 +12,7 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz]" +requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" requires "sigils >= 0.31.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" diff --git a/packaging/macos/build-text-libraries.sh b/packaging/macos/build-text-libraries.sh new file mode 100644 index 00000000..a03f75d9 --- /dev/null +++ b/packaging/macos/build-text-libraries.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -euo pipefail + +prefix="${1:?usage: build-text-libraries.sh /absolute/install/prefix}" +work_dir="$(mktemp -d "${TMPDIR:-/tmp}/kosmo-text.XXXXXX")" +cleanup() { + local build_status=$? + if [[ "$build_status" -ne 0 ]]; then + for log in "$work_dir/"*-build/meson-logs/meson-log.txt; do + if [[ -f "$log" ]]; then + tail -80 "$log" >&2 + fi + done + fi + rm -rf "$work_dir" + exit "$build_status" +} +trap cleanup EXIT + +# Homebrew verifies source checksums. Build minimal libraries rather than using +# bottles whose deployment targets can exceed Kosmo's macOS 13 minimum. +export MACOSX_DEPLOYMENT_TARGET=13.0 +export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" +export CC="$(xcrun --find clang)" +export CXX="$(xcrun --find clang++)" +export HOMEBREW_NO_AUTO_UPDATE=1 +brew fetch --build-from-source harfbuzz fribidi +for formula in harfbuzz fribidi; do + mkdir -p "$work_dir/$formula" + tar -xf "$(brew --cache --build-from-source "$formula")" \ + -C "$work_dir/$formula" --strip-components 1 +done + +meson setup "$work_dir/harfbuzz-build" "$work_dir/harfbuzz" \ + --prefix="$prefix" --libdir=lib --buildtype=release --default-library=shared \ + --wrap-mode=nofallback --auto-features=disabled \ + -Dtests=disabled -Dutilities=disabled -Dsubset=disabled \ + -Draster=disabled -Dvector=disabled -Dgpu=disabled +meson compile -C "$work_dir/harfbuzz-build" +meson install -C "$work_dir/harfbuzz-build" + +meson setup "$work_dir/fribidi-build" "$work_dir/fribidi" \ + --prefix="$prefix" --libdir=lib --buildtype=release --default-library=shared \ + --wrap-mode=nofallback -Ddocs=false -Dbin=false -Dtests=false +meson compile -C "$work_dir/fribidi-build" +meson install -C "$work_dir/fribidi-build" + +for library in "$prefix/lib/libharfbuzz.dylib" "$prefix/lib/libfribidi.dylib"; do + otool -l "$library" | awk ' + $1 == "cmd" { legacy = ($2 == "LC_VERSION_MIN_MACOSX") } + ($1 == "minos" || (legacy && $1 == "version")) && $2 + 0 > 13.0 { + print "Text library exceeds the macOS 13 deployment target: " $2 > "/dev/stderr" + exit 1 + }' +done + +mkdir -p "$prefix/share/licenses" +cp "$work_dir/harfbuzz/COPYING" "$prefix/share/licenses/harfbuzz.txt" +cp "$work_dir/fribidi/COPYING" "$prefix/share/licenses/fribidi.txt" diff --git a/packaging/macos/bundle-text-libraries.sh b/packaging/macos/bundle-text-libraries.sh new file mode 100644 index 00000000..91624aaf --- /dev/null +++ b/packaging/macos/bundle-text-libraries.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +app_path="${1:?usage: bundle-text-libraries.sh /path/to/Kosmo.app /text/library/prefix}" +text_prefix="${2:?text library prefix required}" +library_dir="$app_path/Contents/Frameworks" +mkdir -p "$library_dir" + +bundle_library() { + local source="$1" name destination dependency + name="$(basename "$source")" + destination="$library_dir/$name" + if [[ -f "$destination" ]]; then + return + fi + cp -L "$source" "$destination" + chmod u+w "$destination" + install_name_tool -id "@loader_path/$name" "$destination" + while IFS= read -r dependency; do + case "$dependency" in + /usr/lib/*|/System/Library/*) continue ;; + esac + # The first entry is the source library's own install name. + if [[ "$dependency" -ef "$source" ]]; then + continue + fi + if [[ ! -f "$dependency" ]]; then + echo "Cannot bundle text-library dependency: $dependency" >&2 + exit 1 + fi + bundle_library "$dependency" + install_name_tool -change "$dependency" \ + "@loader_path/$(basename "$dependency")" "$destination" + done < <(otool -L "$source" | sed -E '1d; s/^[[:space:]]+//; s/ \(compatibility version.*$//') + if [[ -n "${KOSMO_SIGNING_IDENTITY:-}" ]]; then + codesign --force --options runtime --timestamp \ + --sign "$KOSMO_SIGNING_IDENTITY" "$destination" + else + codesign --force --sign - "$destination" + fi +} + +bundle_library "$text_prefix/lib/libharfbuzz.dylib" +bundle_library "$text_prefix/lib/libfribidi.dylib" +mkdir -p "$app_path/Contents/Resources/text-library-licenses" +cp "$text_prefix/share/licenses/"*.txt "$app_path/Contents/Resources/text-library-licenses/" diff --git a/packaging/macos/install-local.sh b/packaging/macos/install-local.sh index 99d07822..176f502f 100755 --- a/packaging/macos/install-local.sh +++ b/packaging/macos/install-local.sh @@ -10,7 +10,7 @@ fi repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$repo_root" -for required_command in atlas nim git awk sips iconutil xcrun nm plutil codesign ditto; do +for required_command in atlas nim git awk sips iconutil xcrun nm plutil codesign ditto brew pkg-config meson ninja; do if ! command -v "$required_command" >/dev/null 2>&1; then echo "install-local.sh: missing required command: $required_command" >&2 exit 1 @@ -84,6 +84,9 @@ done iconutil -c icns "$iconset_path" -o "$app_path/Contents/Resources/Kosmo.icns" echo "install-local.sh: building Kosmo.app $version ($revision)" +text_prefix="$build_root/text-libraries" +bash packaging/macos/build-text-libraries.sh "$text_prefix" +export PKG_CONFIG_PATH="$text_prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" nim c \ --opt:size \ --debugger:native \ @@ -91,10 +94,13 @@ nim c \ --stackTrace:off \ -d:nimStackTraceOverride \ -d:moe.embedded \ + -d:harfbuzzyDynlib=@executable_path/../Frameworks/libharfbuzz.dylib \ + -d:harfbuzzyFribidiDynlib=@executable_path/../Frameworks/libfribidi.dylib \ -d:KosmoVersion="$version" \ -d:KosmoGitHashOverride="$revision" \ --out:"$app_path/Contents/MacOS/kosmo" \ src/merenda/kosmo/kosmo.nim +bash packaging/macos/bundle-text-libraries.sh "$app_path" "$text_prefix" xcrun dsymutil "$app_path/Contents/MacOS/kosmo" \ -o "$app_path/Contents/MacOS/kosmo.dSYM" binary_uuid="$(xcrun dwarfdump --uuid "$app_path/Contents/MacOS/kosmo" | awk '{ print $2 }')" diff --git a/packaging/windows/x64-windows-staticcrt.cmake b/packaging/windows/x64-windows-staticcrt.cmake new file mode 100644 index 00000000..2dc8fd60 --- /dev/null +++ b/packaging/windows/x64-windows-staticcrt.cmake @@ -0,0 +1,4 @@ +# Keep the text DLLs independent of an installed Visual C++ redistributable. +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_LIBRARY_LINKAGE dynamic) diff --git a/src/merenda/nimkit/drawing/drawing.nim b/src/merenda/nimkit/drawing/drawing.nim index a4af12e2..fa045085 100644 --- a/src/merenda/nimkit/drawing/drawing.nim +++ b/src/merenda/nimkit/drawing/drawing.nim @@ -244,6 +244,8 @@ proc defaultFont( tag: variation.tag, value: variation.value ) when AutomaticFontFallbackEnabled: + # Each text-layout worker needs its own thread-local fallback resolver. + installAutomaticFontFallbackResolver() font.language = $resolvedLanguage fontRef(font) diff --git a/src/merenda/nimkit/text/markdownviews.nim b/src/merenda/nimkit/text/markdownviews.nim index 5ab73e4c..c4b5e8f6 100644 --- a/src/merenda/nimkit/text/markdownviews.nim +++ b/src/merenda/nimkit/text/markdownviews.nim @@ -747,8 +747,25 @@ proc renderInlineChildren( builder: var MarkdownBuilder, token: markdownParser.Token, attributes: TextAttributes, + skipPrefixBytes = 0, ) = + let prefixEnd = token.pos - token.doc.len + skipPrefixBytes + var skippingPrefix = skipPrefixBytes > 0 for child in token.children: + if skippingPrefix: + # Inline positions are source byte endpoints. Consume the task marker + # without changing the AST, including markers parsed as reference links. + if child.pos <= prefixEnd: + skippingPrefix = child.pos < prefixEnd + continue + skippingPrefix = false + if child of markdownParser.Text: + let skipped = max(prefixEnd - (child.pos - child.doc.len), 0) + if skipped > 0: + builder.add( + child.doc[skipped ..< child.doc.len].replace("\n", " "), attributes + ) + continue builder.renderInline(child, attributes) proc renderImage( @@ -885,6 +902,20 @@ proc renderInline( else: builder.add(token.doc, attributes) +proc renderParagraph( + builder: var MarkdownBuilder, + token: markdownParser.Token, + attributes: TextAttributes, + skipPrefixBytes = 0, +) = + let start = builder.runeLength + builder.renderInlineChildren(token, attributes, skipPrefixBytes) + if builder.runeLength > start: + builder.navigationItems.add MarkdownNavigationItem( + range: initTextRange(start, builder.runeLength - start), + sectionIdentifier: builder.sectionIdentifier, + ) + proc renderBlock( builder: var MarkdownBuilder, token: markdownParser.Token, attributes: TextAttributes ) @@ -900,6 +931,29 @@ func endsWithCodeBlock(builder: MarkdownBuilder): bool = builder.codeBlocks.len > 0 and builder.codeBlocks[^1].range.maxIndex == builder.runeLength +func taskListMarker(item: markdownParser.Li): tuple[symbol: string, prefixBytes: int] = + if item.children.head.isNil: + return + let paragraph = item.children.head.value + if not (paragraph of markdownParser.Paragraph): + return + let source = paragraph.doc + if source.len < 3 or source[0] != '[' or source[2] != ']' or + (source.len > 3 and source[3] notin Whitespace): + return + case source[1] + of ' ': + result.symbol = "☐" + of 'x', 'X': + result.symbol = "☑" + of '-': + result.symbol = "☒" + else: + return + result.prefixBytes = 3 + while result.prefixBytes < source.len and source[result.prefixBytes] in Whitespace: + inc result.prefixBytes + proc renderListItem( builder: var MarkdownBuilder, item: markdownParser.Li, @@ -908,11 +962,19 @@ proc renderListItem( attributes: TextAttributes, ) = let + task = item.taskListMarker() + itemMarker = + if task.symbol.len == 0: + marker + elif marker == "•": + task.symbol + else: + marker & " " & task.symbol indentation = " ".repeat(max(depth * builder.style.listIndent, 0)) - continuation = indentation & " ".repeat(marker.runeLen + 1) + continuation = indentation & " ".repeat(itemMarker.runeLen + 1) var markerAttributes = attributes markerAttributes.foregroundColor = builder.style.mutedColor - builder.add(indentation & marker & " ", markerAttributes) + builder.add(indentation & itemMarker & " ", markerAttributes) var first = true for child in item.children: @@ -928,7 +990,10 @@ proc renderListItem( builder.add(continuation, attributes) elif not first: builder.add("\n" & continuation, attributes) - builder.renderBlock(child, attributes) + if first and task.prefixBytes > 0: + builder.renderParagraph(child, attributes, task.prefixBytes) + else: + builder.renderBlock(child, attributes) first = false proc renderList( @@ -1538,13 +1603,7 @@ proc renderBlock( if token.isNil: return if token of markdownParser.Paragraph: - let start = builder.runeLength - builder.renderInlineChildren(token, attributes) - if builder.runeLength > start: - builder.navigationItems.add MarkdownNavigationItem( - range: initTextRange(start, builder.runeLength - start), - sectionIdentifier: builder.sectionIdentifier, - ) + builder.renderParagraph(token, attributes) elif token of markdownParser.Heading: let heading = markdownParser.Heading(token) var headingAttributes = attributes diff --git a/src/merenda/nimkit/text/textlayout.nim b/src/merenda/nimkit/text/textlayout.nim index 2a2e44d6..0c293bcc 100644 --- a/src/merenda/nimkit/text/textlayout.nim +++ b/src/merenda/nimkit/text/textlayout.nim @@ -826,7 +826,8 @@ proc textRangeForGlyphLine(layout: GlyphArrangement, line: Slice[int]): TextRang if found: initTextRange(start, stop - start) else: - initTextRange(0, 0) + # Shaping backends can represent an empty line with an insertion glyph. + layout.sourceRangeForGlyph(line.a) proc containsHardBreak[T](runes: T, range: TextRange): bool = if runes.len == 0: @@ -1265,11 +1266,12 @@ proc lineFragment[T]( lineCount: int, sourceRunes: T, ): TextLineFragment = - let - containers = manager.effectiveContainers() - glyphRange = initGlyphRange(line.a, line.b - line.a + 1) + let containers = manager.effectiveContainers() + var glyphRange = initGlyphRange(line.a, line.b - line.a + 1) # Read the retained arrangement directly; a local value copies every glyph. var textRange = manager.xLayout.textRangeForGlyphLine(line) + if textRange.isEmpty: + glyphRange.length = 0 if textRange.maxIndex < sourceRunes.len and sourceRunes[textRange.maxIndex] in [Rune('\n'), Rune('\r')]: inc textRange.length @@ -1302,7 +1304,10 @@ proc lineFragment[T]( let lineTop = - if hasUsedRect: virtualUsedRect.origin.y else: manager.xLayoutRect.origin.y + if hasUsedRect: + virtualUsedRect.origin.y + else: + manager.xLayout.glyphRect(line.a).y + manager.xLayoutRect.origin.y containerIndex = containers.containerIndexForVirtualY(lineTop) container = containers[containerIndex.toInt] virtualContainer = containers.virtualContainerRect(containerIndex.toInt) diff --git a/tests/nimkit/font_layout.nim b/tests/nimkit/font_layout.nim index e2031173..8ed8486e 100644 --- a/tests/nimkit/font_layout.nim +++ b/tests/nimkit/font_layout.nim @@ -1,4 +1,4 @@ -import std/[options, os, strutils, tempfiles, unittest] +import std/[options, os, strutils, tempfiles, unicode, unittest] import figdraw import pkg/bumpy @@ -82,6 +82,37 @@ proc selectionBounds(layout: GlyphArrangement): tuple[x, y, w, h: float32] = (minX, minY, maxX - minX, maxY - minY) suite "nimkit font layout": + test "checkbox symbols fall back from the bundled Nerd Font and rasterize": + require "font-fallback" in textBackendFeatures() + require fontFallbackResolver() != nil + let + font = TextStyle( + fontName: "JetBrainsMonoNerdFontMono-Regular.ttf.zip", fontSize: 16.0 + ).textFont(frMonospace).font + shaped = typeset( + bumpy.rect(0, 0, 120, 40), font, "☐☑☒", minContent = false, wrap = false + ) + placed = placeGlyphs( + font, + [ + (Rune(0x2610), vec2(0, 0)), + (Rune(0x2611), vec2(20, 0)), + (Rune(0x2612), vec2(40, 0)), + ], + ) + for layout in [shaped, placed]: + require layout.arrangedGlyphs.len == 3 + for glyph in layout.arrangedGlyphs: + check glyph.glyphId != FontGlyphId(0) + check getFigFont(glyph.fontId).typefaceId != font.typefaceId + for glyph in layout.glyphs(): + let image = glyph.generateGlyph(force = true, upload = false) + require not image.isNil + check image.opaqueBounds().w > 0 + check image.opaqueBounds().h > 0 + for index, glyph in placed.arrangedGlyphs: + check glyph.pos.x == float32(index * 20) + when defined(posix): test "platform default fonts load shape and rasterize without figDataDir": let previousDataDir = figDataDir() diff --git a/tests/nimkit/markdownviews.nim b/tests/nimkit/markdownviews.nim index bf1d9d17..75f2fc10 100644 --- a/tests/nimkit/markdownviews.nim +++ b/tests/nimkit/markdownviews.nim @@ -882,6 +882,89 @@ sibling body check rendered.contains("3. third\n4. fourth") check rendered.contains("• paragraph one\n paragraph two") + test "task lists render Unicode checkboxes and preserve inline styling": + let + style = initMarkdownStyle() + storage = markdownTextStorage( + "- [ ] Todo é\n- [x] **Done** and [link](https://example.com)\n" & + "- [X] Checked\n- [-] Cancelled\n- ordinary" + ) + check storage.stringValue() == + "☐ Todo é\n☑ Done and link\n☑ Checked\n☒ Cancelled\n• ordinary" + check storage.attributesFor("Done").fontName == style.strongFontName + check storage.attributesFor("link").link == "https://example.com" + check storage.attributesFor("☐").foregroundColor == style.mutedColor + check storage.attributesFor("Todo").foregroundColor == style.textColor + + test "nested and ordered tasks retain numbering and continuation indentation": + let storage = markdownTextStorage( + "- [ ] Parent\n - [x] Child\n\n More details\n\n" & + "3. [-] Cancelled\n\n Follow up\n4. [ ] Next" + ) + check storage.stringValue().contains("☐ Parent\n ☑ Child\n More details") + check storage.stringValue().contains( + "3. ☒ Cancelled\n Follow up\n4. ☐ Next" + ) + + test "background task-list layout resolves missing checkbox glyphs": + var style = initMarkdownStyle() + style.bodyFontName = "JetBrainsMonoNerdFontMono-Regular.ttf.zip" + let view = newMarkdownView( + "- [ ] Pending\n- [x] Done\n- [-] Cancelled\n\n" & + "Background content. ".repeat(256), + style = style, + frame = rect(0, 0, 360, 180), + ) + require view.waitForMarkdownParsing() + require view.waitForMarkdownLayout() + let manager = view.textView().layoutManager() + check manager.snapshotBuildThreadId() != getThreadId() + let layout = manager.glyphArrangementResource()[] + var checkboxCount = 0 + for glyph in layout.arrangedGlyphs: + if glyph.rune in [Rune(0x2610), Rune(0x2611), Rune(0x2612)]: + inc checkboxCount + check glyph.glyphId != FontGlyphId(0) + check not getTypefaceSource(getFigFont(glyph.fontId).typefaceId).name.contains( + "JetBrainsMonoNerdFontMono" + ) + check checkboxCount == 3 + + test "task markers only replace a list item's initial literal marker": + let storage = markdownTextStorage( + "[ ] paragraph\n\n- \\[ ] escaped\n- [x]word\n- [z] unknown\n" & + "- `[x]` code\n- [x](https://example.com) link\n- body [x] later\n\n" & + " [ ] later paragraph\n\n```\n- [x] fenced\n```" + ) + let rendered = storage.stringValue() + check rendered.contains("[ ] paragraph") + check rendered.contains("• [ ] escaped") + check rendered.contains("• [x]word") + check rendered.contains("• [z] unknown") + check rendered.contains("• [x] code") + check rendered.contains("• x link") + check rendered.contains("• body [x] later\n [ ] later paragraph") + check rendered.contains("- [x] fenced") + check not rendered.contains("☐") + check not rendered.contains("☑") + check not rendered.contains("☒") + + test "task rendering keeps the parsed tree reusable for style changes": + let view = newMarkdownView( + "- [x] **Done**\n- [ ] [pending](https://example.com)\n- [-] Cancelled\n\n" & + "[x]: https://example.com/reference" + ) + require view.waitForMarkdownParsing() + let expected = "☑ Done\n☐ pending\n☒ Cancelled" + check view.textStorage().stringValue() == expected + var style = view.markdownStyle + style.mutedColor = color(0.7, 0.2, 0.1) + view.markdownStyle = style + require view.waitForMarkdownRendering() + check view.textStorage().stringValue() == expected + check view.textStorage().attributesFor("☑").foregroundColor == style.mutedColor + check view.textStorage().attributesFor("pending").link == "https://example.com" + test "block quotes prefix every line and thematic breaks use the configured rule": let style = initMarkdownStyle() diff --git a/tests/nimkit/monotextviews.nim b/tests/nimkit/monotextviews.nim index 640e24ee..c23ac583 100644 --- a/tests/nimkit/monotextviews.nim +++ b/tests/nimkit/monotextviews.nim @@ -516,6 +516,27 @@ suite "nimkit mono text views": check foundText + test "monospace checkbox cells use symbol fallback and keep their grid positions": + let view = newMonoTextViewer("☐☑☒", frame = rect(0, 0, 220, 80)) + view.fontName = "JetBrainsMonoNerdFontMono-Regular.ttf.zip" + let list = buildRenders(view)[DefaultDrawLevel] + var foundText = false + for node in list.nodes: + if node.kind == nkText and node.renderedText() == "☐☑☒": + foundText = true + require node.textLayout.arrangedGlyphs.len == 3 + let glyphs = node.textLayout.arrangedGlyphs + for glyph in glyphs: + check glyph.glyphId != FontGlyphId(0) + check getTypefaceSource(getFigFont(glyph.fontId).typefaceId).name.find( + "JetBrainsMonoNerdFontMono" + ) < 0 + check glyphs[1].pos.x - glyphs[0].pos.x > 0 + check abs( + (glyphs[2].pos.x - glyphs[1].pos.x) - (glyphs[1].pos.x - glyphs[0].pos.x) + ) < 0.001 + check foundText + test "row glyph batches preserve cell positions across colors and blank backgrounds": let view = newMonoTextViewer(frame = rect(0, 0, 240, 80)) diff --git a/tests/nimkit/renderfragments.nim b/tests/nimkit/renderfragments.nim index e9675b98..554d07d9 100644 --- a/tests/nimkit/renderfragments.nim +++ b/tests/nimkit/renderfragments.nim @@ -1,4 +1,4 @@ -import std/[hashes, tables, unicode, unittest] +import std/[hashes, strutils, tables, unicode, unittest] import figdraw import pkg/pixie except draw @@ -951,8 +951,9 @@ suite "NimKit render fragments": var renderedLine: string for glyphIndex in 0 ..< node.textLayout.glyphCount(): renderedLine.add node.textLayout.displayRune(glyphIndex) - renderedLines.add renderedLine - check renderedLines == @["alpha\n", "Beta\n", "gamma"] + # Shaping backends can omit newline glyphs while retaining their source. + renderedLines.add renderedLine.strip(chars = {'\n', '\r'}) + check renderedLines == @["alpha", "Beta", "gamma"] let replica = retainedScenes.newRenderSceneReplica() var fullUpdate = retainedScenes.newRenderSceneUpdate(scene, 0, 0) diff --git a/tests/nimkit/textlayout.nim b/tests/nimkit/textlayout.nim index b61fb0cc..08f17752 100644 --- a/tests/nimkit/textlayout.nim +++ b/tests/nimkit/textlayout.nim @@ -1121,13 +1121,18 @@ suite "nimkit text layout": ) let snapshot = manager.layoutSnapshot() - check snapshot.lineFragments.len >= 4 + require snapshot.lineFragments.len == 4 check snapshot.hasLineStart(0) check snapshot.hasLineStart(2) check snapshot.hasLineStart(3) check snapshot.hasLineStart(5) check snapshot.lineFragments[^1].textRange.location == 5 check snapshot.lineFragments[^1].glyphRange.isEmpty + for index, start in [0, 2, 3, 5]: + check snapshot.lineFragments[index].textRange.location == start + if index > 0: + check snapshot.lineFragments[index].fragmentRect.minY >= + snapshot.lineFragments[index - 1].fragmentRect.maxY - TextLayoutEpsilon test "layout lifecycle and scalar queries expose current cached layout": let manager = newTextLayoutManager( From 986c49b82d7c94ad63346eed61e01b10c8d5f851 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 18 Oct 2026 22:14:25 +0300 Subject: [PATCH 02/27] Resolve packaged Windows text DLL names from installed artifacts --- .github/workflows/release-kosmo.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index 934faa61..6fb89b3c 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -791,6 +791,12 @@ jobs: run: | $version = $env:KOSMO_RELEASE_VERSION.TrimStart('v') $revision = $env:GITHUB_SHA.Substring(0, 12) + $textBin = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/bin" + $harfbuzzDll = Get-ChildItem "$textBin/*.dll" | Where-Object Name -Match '^(lib)?harfbuzz(-[0-9]+)?\.dll$' | Select-Object -First 1 + $fribidiDll = Get-ChildItem "$textBin/*.dll" | Where-Object Name -Match '^(lib)?fribidi(-[0-9]+)?\.dll$' | Select-Object -First 1 + if (-not $harfbuzzDll -or -not $fribidiDll) { + throw 'The text dependency installation is missing HarfBuzz or FriBidi DLLs.' + } New-Item -ItemType Directory -Force -Path build, dist | Out-Null Push-Location data windres kosmo.rc -O coff -o ../build/kosmo.res @@ -807,6 +813,8 @@ jobs: -d:nimStackTraceOverride ` --app:gui ` -d:moe.embedded ` + "-d:harfbuzzyDynlib=$($harfbuzzDll.Name)" ` + "-d:harfbuzzyFribidiDynlib=$($fribidiDll.Name)" ` "-d:KosmoVersion=$version" ` "-d:KosmoGitHashOverride=$revision" ` --passL:build/kosmo.res ` @@ -825,7 +833,7 @@ jobs: if ($imports -match '(?im)^\s*DLL Name:\s+libgcc_s.*\.dll\s*$') { throw 'Kosmo executable must not depend on a MinGW GCC runtime DLL.' } - Copy-Item "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/bin/*.dll" dist/ + Copy-Item "$textBin/*.dll" dist/ $process = Start-Process ` -FilePath "$PWD/dist/kosmo.exe" ` -PassThru From b5071712c3f7a07c828f4b5fde0730345858f834 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 18 Oct 2026 22:20:38 +0300 Subject: [PATCH 03/27] Document HarfBuzz backend selection and fallback activation --- CHANGES.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7d5f793c..02c960b3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,14 @@ ## Unreleased +- Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so + this checkout and downstream Merenda apps use HarfBuzz text rendering and + automatic system-font fallback. Previously HarfBuzz was installed as a dependency, + but FigDraw still selected Pixie, leaving automatic fallback disabled. Missing + glyphs in the editor, terminal, and other text views can now use system fonts, + including checkbox symbols absent from the bundled Nerd Font. Initialize fallback + on background text-layout threads as well as the main thread. + - Match split divider fills, edges, and grips to each bundled theme's palette, including the dark Nebula, Peachy, and Synthwave83 themes. @@ -71,10 +79,6 @@ Use a compact menu that opens upward, and keep its checked selection synchronized with Settings and detached editor windows. -- Enable HarfBuzz text rendering and automatic system-font fallback for missing - glyphs in the editor, terminal, and other text views, including checkbox symbols - absent from the bundled Nerd Font. - - Render Markdown task lists with ☐ for `[ ]`, ☑ for `[x]` or `[X]`, and ☒ for `[-]`, preserving nested lists, numbering, and inline formatting. From 10802ffb2fd874dd5cf41cc3f3a7cf773ec1e31a Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 18 Oct 2026 22:39:48 +0300 Subject: [PATCH 04/27] Build Windows Kosmo with MSVC and fix HarfBuzz CI checks --- .github/lsan.supp | 2 + .github/workflows/build-full.yml | 16 +++++- .github/workflows/release-kosmo.yml | 51 ++++++++++++------- CHANGES.md | 4 ++ README.md | 3 +- config.nims | 10 ++-- docs/releasing-kosmo.md | 15 +++--- install.sh | 3 ++ .../fixtures/windowsstacktrace.nim | 19 +++++++ tests/nimkit/tooltips.nim | 6 +-- 10 files changed, 98 insertions(+), 31 deletions(-) create mode 100644 tests/integrations/fixtures/windowsstacktrace.nim diff --git a/.github/lsan.supp b/.github/lsan.supp index 38d32122..94b5b717 100644 --- a/.github/lsan.supp +++ b/.github/lsan.supp @@ -5,6 +5,8 @@ leak:drawing::defaultFont leak:imgutils::retainFontRefId leak:typefaces::registerTypeface +# The HarfBuzz backend also retains parsed faces in an ARC/ORC threadvar table. +leak:harfbuzzy::initHarfbuzzTypeface leak:libfontconfig.so leak:outputs::memoryOutput leak:times::newTimezone diff --git a/.github/workflows/build-full.yml b/.github/workflows/build-full.yml index 68187eb1..d80e3283 100644 --- a/.github/workflows/build-full.yml +++ b/.github/workflows/build-full.yml @@ -316,6 +316,12 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Configure MSVC for x64 + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Install macOS text rendering libraries if: runner.os == 'macOS' run: brew install harfbuzz fribidi @@ -332,7 +338,7 @@ jobs: run: | & "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" install harfbuzz:x64-windows if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - "CPATH=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/include/harfbuzz" >> $env:GITHUB_ENV + "INCLUDE=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/include/harfbuzz;$env:INCLUDE" >> $env:GITHUB_ENV - name: Cache packages uses: actions/cache@v4 @@ -366,5 +372,13 @@ jobs: run: bash .github/scripts/clean_generated_examples.sh - name: Compile Examples + if: runner.os != 'Windows' run: | atlas-run tests --jobs:2 --compile-only --only-errors "examples/all_compile.nim" + + - name: Compile Windows Examples with MSVC + if: runner.os == 'Windows' + shell: pwsh + run: | + atlas-run tests --jobs:2 --compile-only --only-errors "examples/all_compile.nim" -- --cc:vcc --passC:/MT + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index 6fb89b3c..d8878e92 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -754,6 +754,11 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Configure MSVC for x64 + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Install text rendering libraries shell: pwsh run: | @@ -761,7 +766,7 @@ jobs: harfbuzz:x64-windows-staticcrt fribidi:x64-windows-staticcrt ` --overlay-triplets=packaging/windows if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - "CPATH=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/include/harfbuzz" >> $env:GITHUB_ENV + "INCLUDE=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-staticcrt/include/harfbuzz;$env:INCLUDE" >> $env:GITHUB_ENV - name: Install Nim uses: jiro4989/setup-nim-action@v2 @@ -799,18 +804,16 @@ jobs: } New-Item -ItemType Directory -Force -Path build, dist | Out-Null Push-Location data - windres kosmo.rc -O coff -o ../build/kosmo.res + rc.exe /nologo /fo ../build/kosmo.res kosmo.rc + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Pop-Location - # The setup action uses MinGW. Link its exception runtime into the - # executable so the release ZIP does not require libgcc_s_seh-1.dll - # beside kosmo.exe. nim c ` + --cc:vcc ` --opt:size ` --debugger:native ` - --lineTrace:off ` - --stackTrace:off ` - --passL:-static-libgcc ` - -d:nimStackTraceOverride ` + --lineTrace:on ` + --stackTrace:on ` + --passC:/MT ` --app:gui ` -d:moe.embedded ` "-d:harfbuzzyDynlib=$($harfbuzzDll.Name)" ` @@ -818,22 +821,29 @@ jobs: "-d:KosmoVersion=$version" ` "-d:KosmoGitHashOverride=$revision" ` --passL:build/kosmo.res ` + "--passL:/link /DEBUG:FULL /PDB:dist/kosmo.pdb" ` --out:dist/kosmo.exe ` src/merenda/kosmo/kosmo.nim if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - if (-not (& objdump -h dist/kosmo.exe | Select-String -Quiet '\.debug_info')) { - throw 'Kosmo executable is missing native debug information.' + if (-not (Test-Path dist/kosmo.pdb)) { + throw 'Kosmo executable is missing its PDB.' } - if (-not (& nm dist/kosmo.exe | Select-String -Quiet 'backtrace_create_state')) { - throw 'Kosmo executable is missing libbacktrace.' + if (-not (& dumpbin /headers dist/kosmo.exe | Select-String -Quiet 'Debug Directories')) { + throw 'Kosmo executable is missing its native debug directory.' } - $imports = & objdump -p dist/kosmo.exe - if ($imports -match '(?im)^\s*DLL Name:\s+libgcc_s.*\.dll\s*$') { - throw 'Kosmo executable must not depend on a MinGW GCC runtime DLL.' + $imports = & dumpbin /dependents dist/kosmo.exe + if ($imports -match '(?i)libgcc|libstdc\+\+|vcruntime[0-9]+|msvcp[0-9]+') { + throw 'Kosmo executable must not depend on a compiler runtime DLL.' } Copy-Item "$textBin/*.dll" dist/ + Get-ChildItem dist/*.dll | ForEach-Object { + $dependencies = & dumpbin /dependents $_.FullName + if ($dependencies -match '(?i)vcruntime[0-9]+|msvcp[0-9]+') { + throw "Text DLL $($_.Name) requires a Visual C++ redistributable." + } + } $process = Start-Process ` -FilePath "$PWD/dist/kosmo.exe" ` -PassThru @@ -852,6 +862,13 @@ jobs: } } + - name: Verify MSVC stack traces + shell: pwsh + run: | + nim c -r --cc:vcc --passC:/MT --out:build/windowsstacktrace.exe ` + tests/integrations/fixtures/windowsstacktrace.nim + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Package Kosmo shell: pwsh run: | @@ -864,7 +881,7 @@ jobs: } } Compress-Archive ` - -Path dist/kosmo.exe, dist/*.dll, dist/FONT-LICENSES.md, dist/font-licenses, dist/text-library-licenses ` + -Path dist/kosmo.exe, dist/kosmo.pdb, dist/*.dll, dist/FONT-LICENSES.md, dist/font-licenses, dist/text-library-licenses ` -DestinationPath dist/kosmo-windows-amd64.zip ` -Force diff --git a/CHANGES.md b/CHANGES.md index 02c960b3..c95d9be3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,10 @@ ## Unreleased +- Build Windows Kosmo releases and examples with MSVC. Use the Windows SDK + resource compiler, ship the matching PDB, and keep Nim stack traces with + function names and source lines in place of GNU libbacktrace on MSVC. + - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and automatic system-font fallback. Previously HarfBuzz was installed as a dependency, diff --git a/README.md b/README.md index b1eb3079..d59aa7d0 100644 --- a/README.md +++ b/README.md @@ -805,7 +805,8 @@ nim c -o:kosmo src/merenda/kosmo/kosmo.nim ./kosmo . ``` -On Windows, run `./kosmo.exe .` after compiling. +On Windows, build from a Visual Studio Developer PowerShell with +`nim c --cc:vcc -o:kosmo.exe src/merenda/kosmo/kosmo.nim`, then run `./kosmo.exe .`. On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: diff --git a/config.nims b/config.nims index 234a5a0b..a8071334 100644 --- a/config.nims +++ b/config.nims @@ -14,13 +14,17 @@ goto --stacktrace: off ---define: - nimStackTraceOverride --path: "src/" --nimcache: "deps/.nimcache" -switch("import", "libbacktrace") +when defined(windows): + # MSVC emits PDBs; GNU libbacktrace expects DWARF and a GNU unwinder. + switch("stackTrace", "on") + switch("lineTrace", "on") +else: + switch("define", "nimStackTraceOverride") + switch("import", "libbacktrace") when defined(freebsd): --define: diff --git a/docs/releasing-kosmo.md b/docs/releasing-kosmo.md index 5e46dbb1..c77fc47c 100644 --- a/docs/releasing-kosmo.md +++ b/docs/releasing-kosmo.md @@ -25,15 +25,18 @@ Kosmo's platform asset cache for Moe's file-based theme loader. Release archives do not need a separate `data/moe/themes` directory, and theme discovery does not depend on the working directory used to launch Kosmo. -Release executables retain native debug information and link the `libbacktrace` -stack-trace override. Nim's instrumented stack tracing remains disabled because +Release executables retain native debug information. Windows builds use MSVC, +ship `kosmo.pdb` beside `kosmo.exe`, and use Nim's stack tracing with function names +and source lines. The installer keeps the PDB beside the installed executable. +The other platforms link the `libbacktrace` stack-trace override. Nim's +instrumented stack tracing remains disabled on those platforms because `libbacktrace` uses the compiler's native debug information without its runtime overhead. `libbacktrace` is an unconditional package dependency, and the project -configuration imports it globally; release builds also define -`nimStackTraceOverride` explicitly. Each platform job verifies both the native -debug data and linked libbacktrace backend before packaging. +configuration imports it except on Windows/MSVC; the other release builds also +define `nimStackTraceOverride` explicitly. Each platform job verifies its native +debug data and stack-trace support before packaging. -Linux and Windows keep the debug information in the executable. The macOS app +Linux keeps the debug information in the executable. The macOS app bundles its matching symbols at `Contents/MacOS/kosmo.dSYM`. This is the location libbacktrace checks beside the running executable, so release stack traces retain file and line information. diff --git a/install.sh b/install.sh index 8f31a1dd..38003c89 100755 --- a/install.sh +++ b/install.sh @@ -298,6 +298,9 @@ case "$platform" in install_command "$library" "$(basename "$library")" fi done + if [ -f "$extract_dir/kosmo.pdb" ]; then + install_command "$extract_dir/kosmo.pdb" kosmo.pdb + fi install_command "$extract_dir/kosmo.exe" kosmo.exe install_notices "$extract_dir" ;; diff --git a/tests/integrations/fixtures/windowsstacktrace.nim b/tests/integrations/fixtures/windowsstacktrace.nim new file mode 100644 index 00000000..12b8ee21 --- /dev/null +++ b/tests/integrations/fixtures/windowsstacktrace.nim @@ -0,0 +1,19 @@ +## Standalone MSVC smoke test for the Windows stack-trace compiler settings. +## The release job compiles this separately so its PDB stays beside the process. + +when isMainModule: + import std/strutils + + when not (defined(windows) and defined(vcc)): + {.error: "Compile this fixture for Windows with --cc:vcc".} + + proc captureFailure() {.noinline.} = + raise newException(ValueError, "MSVC stack trace probe") + + try: + captureFailure() + except ValueError as error: + let trace = getStackTrace(error) + doAssert trace.contains("captureFailure"), trace + doAssert trace.contains("windowsstacktrace.nim"), trace + echo "MSVC stack trace includes the function and Nim source location" diff --git a/tests/nimkit/tooltips.nim b/tests/nimkit/tooltips.nim index cd3bcf36..cbed57ed 100644 --- a/tests/nimkit/tooltips.nim +++ b/tests/nimkit/tooltips.nim @@ -1,4 +1,4 @@ -import std/[times, unicode, unittest] +import std/[times, unittest] import figdraw @@ -11,8 +11,8 @@ func center(bounds: Rect): Point = ) proc renderedText(node: Fig): string = - for glyphIndex in 0 ..< node.textLayout.glyphCount(): - result.add node.textLayout.displayRune(glyphIndex) + # A shaped glyph can cover multiple characters, such as the `fi` ligature. + node.textLayout.sourceRunes.bytes() proc layerContainsText(renders: Renders, level: ZLevel, text: string): bool = if level notin renders.layers: From edf4cf7a0f8ad4b22d44a6629c94335b76249d27 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 18 Oct 2026 23:22:07 +0300 Subject: [PATCH 05/27] Pin corrected MSVC atomics and validate native worker ownership --- .github/scripts/install-msvc-nim.sh | 18 ++++++ .github/workflows/build-full.yml | 13 +++- .github/workflows/release-kosmo.yml | 15 ++++- CHANGES.md | 2 + README.md | 7 ++- docs/releasing-kosmo.md | 5 ++ merenda.nimble | 2 +- src/merenda/nimkit/themes/themecore.nim | 7 +++ .../integrations/fixtures/windowsatomics.nim | 63 +++++++++++++++++++ 9 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/install-msvc-nim.sh create mode 100644 tests/integrations/fixtures/windowsatomics.nim diff --git a/.github/scripts/install-msvc-nim.sh b/.github/scripts/install-msvc-nim.sh new file mode 100644 index 00000000..dd88e803 --- /dev/null +++ b/.github/scripts/install-msvc-nim.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Stable Nim 2.2.10 with corrected MSVC atomic exchange/CAS/arithmetic. +# Keep the environment name tied to the immutable source revision. +nimMsvcRevision=31511ba12d9d9333fdf886b4ae2fe78b5b398645 +nimMsvcEnvironment=msvc-2.2.10-31511ba1 +githubPathArgs=() +if [[ -n "${GITHUB_PATH:-}" ]]; then + githubPathArgs=(--github-path) +fi +atlas env "$nimMsvcEnvironment" \ + --git-url=https://github.com/elcritch/Nim \ + --git-ref="$nimMsvcRevision" \ + "${githubPathArgs[@]}" + +echo "Patched MSVC compiler: deps/nim-$nimMsvcEnvironment/bin/nim.exe" diff --git a/.github/workflows/build-full.yml b/.github/workflows/build-full.yml index d80e3283..56013b0c 100644 --- a/.github/workflows/build-full.yml +++ b/.github/workflows/build-full.yml @@ -349,9 +349,18 @@ jobs: - name: Install latest Atlas and set up Nim run: | mkdir -p "$HOME/.local/bin" - echo "$HOME/.local/bin" >> "$GITHUB_PATH" + if [[ "$RUNNER_OS" == "Windows" ]]; then + cygpath -w "$HOME/.local/bin" >> "$GITHUB_PATH" + else + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + fi + export PATH="$HOME/.local/bin:$PATH" bash .github/scripts/install-atlas.sh - "$HOME/.local/bin/atlas" env "${{ matrix.nimversion }}" --binary --github-path + if [[ "$RUNNER_OS" == "Windows" ]]; then + bash .github/scripts/install-msvc-nim.sh + else + "$HOME/.local/bin/atlas" env "${{ matrix.nimversion }}" --binary --github-path + fi - name: Verify Nim run: | diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index d8878e92..af2bbcd6 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -777,7 +777,8 @@ jobs: shell: bash run: | mkdir -p "$HOME/.local/bin" - echo "$HOME/.local/bin" >> "$GITHUB_PATH" + cygpath -w "$HOME/.local/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$PATH" bash .github/scripts/install-atlas.sh atlas -v @@ -791,6 +792,10 @@ jobs: shell: bash run: atlas install -tuk --features:kosmo + - name: Install Nim with corrected MSVC atomics + shell: bash + run: bash .github/scripts/install-msvc-nim.sh + - name: Build Kosmo shell: pwsh run: | @@ -869,6 +874,14 @@ jobs: tests/integrations/fixtures/windowsstacktrace.nim if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Verify MSVC atomic ownership and worker shutdown + shell: pwsh + timeout-minutes: 5 + run: | + nim c -r --cc:vcc --passC:/MT --out:build/windowsatomics.exe ` + tests/integrations/fixtures/windowsatomics.nim + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Package Kosmo shell: pwsh run: | diff --git a/CHANGES.md b/CHANGES.md index c95d9be3..b44c3f39 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,8 @@ - Build Windows Kosmo releases and examples with MSVC. Use the Windows SDK resource compiler, ship the matching PDB, and keep Nim stack traces with function names and source lines in place of GNU libbacktrace on MSVC. + Use Sigils 0.32.0 and a pinned Nim 2.2.10 build with corrected MSVC atomic + exchange, compare-and-swap, and arithmetic operations. - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and diff --git a/README.md b/README.md index d59aa7d0..ed5517f3 100644 --- a/README.md +++ b/README.md @@ -805,8 +805,11 @@ nim c -o:kosmo src/merenda/kosmo/kosmo.nim ./kosmo . ``` -On Windows, build from a Visual Studio Developer PowerShell with -`nim c --cc:vcc -o:kosmo.exe src/merenda/kosmo/kosmo.nim`, then run `./kosmo.exe .`. +On Windows, run `bash .github/scripts/install-msvc-nim.sh` first. From a Visual +Studio Developer PowerShell, use the compiler path it prints to build with +`c --cc:vcc -o:kosmo.exe src/merenda/kosmo/kosmo.nim`, then run `./kosmo.exe .`. +The pinned Nim 2.2.10 compiler includes the MSVC atomic-operation fixes needed +by the worker threads; an unpatched MSVC build is rejected at compile time. On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: diff --git a/docs/releasing-kosmo.md b/docs/releasing-kosmo.md index c77fc47c..c555c1d6 100644 --- a/docs/releasing-kosmo.md +++ b/docs/releasing-kosmo.md @@ -28,6 +28,11 @@ depend on the working directory used to launch Kosmo. Release executables retain native debug information. Windows builds use MSVC, ship `kosmo.pdb` beside `kosmo.exe`, and use Nim's stack tracing with function names and source lines. The installer keeps the PDB beside the installed executable. +Windows CI runs `.github/scripts/install-msvc-nim.sh` to build an immutable +revision of the Nim 2.2.10 fork with corrected MSVC atomics. Local MSVC builds +must use that script's compiler too. The standard-library regression tests run +natively with MSVC in the fork, and the release job additionally checks shared +ownership, concurrent increments, and worker shutdown before packaging. The other platforms link the `libbacktrace` stack-trace override. Nim's instrumented stack tracing remains disabled on those platforms because `libbacktrace` uses the compiler's native debug information without its runtime diff --git a/merenda.nimble b/merenda.nimble index 26ee9f95..db1dadcd 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -13,7 +13,7 @@ requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "sigils >= 0.31.0 [sigNameAsString, closures, siwin, chronos]" +requires "gh:elcritch/sigils#00ce21564a390bd3883da601b54f59344d1b6c7b [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" requires "cborious" diff --git a/src/merenda/nimkit/themes/themecore.nim b/src/merenda/nimkit/themes/themecore.nim index cc77152a..4d6b8ba9 100644 --- a/src/merenda/nimkit/themes/themecore.nim +++ b/src/merenda/nimkit/themes/themecore.nim @@ -1,5 +1,12 @@ import std/[atomics, sets, strutils, tables] +when defined(windows) and defined(vcc): + when not declared(nimHasCorrectMsvcAtomics): + {. + error: + "MSVC needs the corrected Nim atomics library. Run bash .github/scripts/install-msvc-nim.sh and use the printed compiler path." + .} + import figdraw except CornerRadii from sigils/selectors import DynamicAgent diff --git a/tests/integrations/fixtures/windowsatomics.nim b/tests/integrations/fixtures/windowsatomics.nim new file mode 100644 index 00000000..b73fdfd2 --- /dev/null +++ b/tests/integrations/fixtures/windowsatomics.nim @@ -0,0 +1,63 @@ +## Standalone native MSVC coverage for shared ownership and contended atomics. + +when isMainModule: + import std/[atomics, monotimes, os, times] + import threading/smartptrs + import sigils/[threadChronos, threadPool] + + when not (defined(windows) and defined(vcc)): + {.error: "Compile this fixture for Windows with --cc:vcc".} + when not declared(nimHasCorrectMsvcAtomics): + {.error: "Use the pinned MSVC compiler with corrected atomics".} + + var destructions: Atomic[int] + + type Tracked = object + value: int + + proc `=destroy`(value: var Tracked) = + if value.value != 0: + discard destructions.fetchAdd(1) + + var before: int + block: + let owner = newSharedPtr(Tracked(value: 42)) + before = destructions.load() + block: + let copy = owner + doAssert copy[].value == 42 + doAssert owner[].value == 42 + doAssert destructions.load() == before + doAssert destructions.load() == before + 1 + + const + workerCount = 4 + iterations = 20_000 + var completed: Atomic[int] + + proc increment(counter: ptr Atomic[int]) {.thread.} = + for index in 0 ..< iterations: + let previous = counter[].fetchAdd(1, moRelaxed) + doAssert previous >= 0 and previous < workerCount * iterations + discard completed.fetchAdd(1, moRelease) + + var + counter: Atomic[int] + workers: array[workerCount, Thread[ptr Atomic[int]]] + for worker in workers.mitems: + createThread(worker, increment, addr counter) + let deadline = getMonoTime() + initDuration(seconds = 60) + while completed.load(moAcquire) != workerCount and getMonoTime() < deadline: + sleep(1) + doAssert completed.load(moAcquire) == workerCount + joinThreads(workers) + doAssert counter.load() == workerCount * iterations + let pool = newSigilThreadPool(workers = 2) + pool.start() + pool.stop() + pool.join() + let dispatcher = newSigilChronosThread() + dispatcher.start() + dispatcher.stop() + dispatcher.join() + echo "MSVC shared ownership and contended atomic increments passed" From ed9ae2ec41821f9afbcc4043cd417e76a504c51c Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 18 Oct 2026 23:53:12 +0300 Subject: [PATCH 06/27] Install pinned MSVC stdlib fix with official Nim binary --- .github/scripts/install-msvc-nim.sh | 72 +++++++++++++++++++++++++---- CHANGES.md | 5 +- README.md | 8 ++-- docs/releasing-kosmo.md | 9 ++-- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/.github/scripts/install-msvc-nim.sh b/.github/scripts/install-msvc-nim.sh index dd88e803..0816f1e1 100644 --- a/.github/scripts/install-msvc-nim.sh +++ b/.github/scripts/install-msvc-nim.sh @@ -2,17 +2,69 @@ set -euo pipefail -# Stable Nim 2.2.10 with corrected MSVC atomic exchange/CAS/arithmetic. -# Keep the environment name tied to the immutable source revision. +# The compiler is unchanged: install the official binary and overlay the one +# corrected standard-library module in a private copy of the distribution. nimMsvcRevision=31511ba12d9d9333fdf886b4ae2fe78b5b398645 -nimMsvcEnvironment=msvc-2.2.10-31511ba1 -githubPathArgs=() +nimMsvcChecksum=2d9c6b381ad09b09b64d29f2af5a6da693c7835d574075e238a8d8c4370ef05a +projectRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$projectRoot" +nimMsvcDir="$projectRoot/deps/nim-msvc-2.2.10-31511ba1-bin" +atomicsPath=lib/pure/concurrency/atomics.nim +stageDir= +probeDir= +nimMsvcCandidate="$nimMsvcDir" + +cleanup() { + if [[ -n "$stageDir" ]]; then rm -rf "$stageDir"; fi + if [[ -n "$probeDir" ]]; then rm -rf "$probeDir"; fi +} +trap cleanup EXIT + +verifyAtomics() { + [[ -f "$1" ]] && [[ "$(sha256sum "$1" | cut -d ' ' -f 1)" == "$nimMsvcChecksum" ]] +} + +if [[ ! -e "$nimMsvcDir" ]]; then + atlas env 2.2.10 --binary + stageDir="$(mktemp -d "$projectRoot/deps/.nim-msvc-XXXXXX")" + cp -R "$projectRoot/deps/nim-2.2.10/." "$stageDir/" + curl --fail --location --retry 3 \ + "https://raw.githubusercontent.com/elcritch/Nim/$nimMsvcRevision/$atomicsPath" \ + --output "$stageDir/$atomicsPath" + if ! verifyAtomics "$stageDir/$atomicsPath"; then + echo 'The MSVC atomics module checksum does not match.' >&2 + exit 1 + fi + # Atlas activation scripts contain the original installation's absolute path. + # Use the private compiler path printed below instead. + rm -f "$stageDir/activate.bat" "$stageDir/activate.ps1" "$stageDir/activate.sh" + nimMsvcCandidate="$stageDir" +fi + +if [[ ! -f "$nimMsvcCandidate/bin/nim.exe" ]] || ! verifyAtomics "$nimMsvcCandidate/$atomicsPath"; then + echo "Incomplete MSVC Nim environment: remove $nimMsvcDir and retry." >&2 + exit 1 +fi + +# Use the compiler's default library lookup, including on cached installations. +probeDir="$(mktemp -d "$projectRoot/deps/.msvc-atomics-probe-XXXXXX")" +probeFile="$probeDir/msvc_atomics_probe.nim" +cat > "$probeFile" <<'NIM' +import std/atomics +static: + doAssert declared(nimHasCorrectMsvcAtomics) +NIM +"$nimMsvcCandidate/bin/nim.exe" check --cc:vcc --os:windows \ + --skipUserCfg:on --skipParentCfg:on --skipProjCfg:on "$probeFile" + +if [[ -n "$stageDir" ]]; then + mv "$stageDir" "$nimMsvcDir" + stageDir= +fi + if [[ -n "${GITHUB_PATH:-}" ]]; then - githubPathArgs=(--github-path) + cygpath -w "$nimMsvcDir/bin" >> "$GITHUB_PATH" fi -atlas env "$nimMsvcEnvironment" \ - --git-url=https://github.com/elcritch/Nim \ - --git-ref="$nimMsvcRevision" \ - "${githubPathArgs[@]}" -echo "Patched MSVC compiler: deps/nim-$nimMsvcEnvironment/bin/nim.exe" +nimMsvcCompilerWindows="$(cygpath -w "$nimMsvcDir/bin/nim.exe")" +echo "Patched MSVC compiler: $nimMsvcCompilerWindows" diff --git a/CHANGES.md b/CHANGES.md index b44c3f39..13200b93 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,8 +21,9 @@ - Build Windows Kosmo releases and examples with MSVC. Use the Windows SDK resource compiler, ship the matching PDB, and keep Nim stack traces with function names and source lines in place of GNU libbacktrace on MSVC. - Use Sigils 0.32.0 and a pinned Nim 2.2.10 build with corrected MSVC atomic - exchange, compare-and-swap, and arithmetic operations. + Use Sigils 0.32.0 and the official Nim 2.2.10 compiler with a pinned, + checksum-verified standard-library fix for MSVC atomic exchange, + compare-and-swap, and arithmetic operations. - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and diff --git a/README.md b/README.md index ed5517f3..0885501f 100644 --- a/README.md +++ b/README.md @@ -807,9 +807,11 @@ nim c -o:kosmo src/merenda/kosmo/kosmo.nim On Windows, run `bash .github/scripts/install-msvc-nim.sh` first. From a Visual Studio Developer PowerShell, use the compiler path it prints to build with -`c --cc:vcc -o:kosmo.exe src/merenda/kosmo/kosmo.nim`, then run `./kosmo.exe .`. -The pinned Nim 2.2.10 compiler includes the MSVC atomic-operation fixes needed -by the worker threads; an unpatched MSVC build is rejected at compile time. +`& "" c --cc:vcc -o:kosmo.exe src/merenda/kosmo/kosmo.nim`, +then run `./kosmo.exe .`. +The script makes a private copy of the official Nim 2.2.10 binary distribution +and installs a checksum-verified standard-library fix for MSVC atomic operations. +Worker threads need that fix; an unpatched MSVC build is rejected at compile time. On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: diff --git a/docs/releasing-kosmo.md b/docs/releasing-kosmo.md index c555c1d6..61db3527 100644 --- a/docs/releasing-kosmo.md +++ b/docs/releasing-kosmo.md @@ -28,9 +28,12 @@ depend on the working directory used to launch Kosmo. Release executables retain native debug information. Windows builds use MSVC, ship `kosmo.pdb` beside `kosmo.exe`, and use Nim's stack tracing with function names and source lines. The installer keeps the PDB beside the installed executable. -Windows CI runs `.github/scripts/install-msvc-nim.sh` to build an immutable -revision of the Nim 2.2.10 fork with corrected MSVC atomics. Local MSVC builds -must use that script's compiler too. The standard-library regression tests run +Windows CI runs `.github/scripts/install-msvc-nim.sh` to copy the official Nim +2.2.10 binary distribution into a private environment and install a corrected +MSVC atomics module from an immutable fork revision. The script verifies the +module's SHA-256 and probes the compiler's default library lookup before adding +it to CI's path. The Atlas-managed official installation stays untouched. Local +MSVC builds must use that script's compiler too. The regression tests run natively with MSVC in the fork, and the release job additionally checks shared ownership, concurrent increments, and worker shutdown before packaging. The other platforms link the `libbacktrace` stack-trace override. Nim's From 7640f992e33030028fc7a1b46a1b41ebc4d632a8 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 00:17:39 +0300 Subject: [PATCH 07/27] Pin MSVC-compatible shader embedding and verify Windows startup --- .github/workflows/release-kosmo.yml | 7 +++++++ CHANGES.md | 1 + merenda.nimble | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index af2bbcd6..becbbf07 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -759,6 +759,13 @@ jobs: with: arch: x64 + - name: Set up software OpenGL for the startup check + uses: ssciwr/setup-mesa-dist-win@v3.0.0 + with: + version: '26.0.3' + build-type: 'release-msvc' + deployment-choice: '1' + - name: Install text rendering libraries shell: pwsh run: | diff --git a/CHANGES.md b/CHANGES.md index 13200b93..312f14a1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ Use Sigils 0.32.0 and the official Nim 2.2.10 compiler with a pinned, checksum-verified standard-library fix for MSVC atomic exchange, compare-and-swap, and arithmetic operations. + Pin FigDraw's numeric shader embedding to avoid MSVC's string literal limit. - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and diff --git a/merenda.nimble b/merenda.nimble index db1dadcd..aa383fb7 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,7 +12,7 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/figdraw#f25a3ec873ba38906534d1cca3df1520bf79b3dd [siwin, harfbuzz, textBackendHarfbuzzy]" requires "gh:elcritch/sigils#00ce21564a390bd3883da601b54f59344d1b6c7b [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" From 10669d23c89fe952563c772a925f3c637a51f4cf Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 00:28:23 +0300 Subject: [PATCH 08/27] Build pinned Nim once for MSVC atomics and large embedded strings --- .github/scripts/install-msvc-nim.sh | 61 +++++++++++++++++++++-------- CHANGES.md | 6 +-- README.md | 12 +++--- docs/releasing-kosmo.md | 13 +++--- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/.github/scripts/install-msvc-nim.sh b/.github/scripts/install-msvc-nim.sh index 0816f1e1..10ba4912 100644 --- a/.github/scripts/install-msvc-nim.sh +++ b/.github/scripts/install-msvc-nim.sh @@ -2,14 +2,16 @@ set -euo pipefail -# The compiler is unchanged: install the official binary and overlay the one -# corrected standard-library module in a private copy of the distribution. -nimMsvcRevision=31511ba12d9d9333fdf886b4ae2fe78b5b398645 -nimMsvcChecksum=2d9c6b381ad09b09b64d29f2af5a6da693c7835d574075e238a8d8c4370ef05a +# Start with the complete official distribution, verify both pinned fixes, then +# compile the corrected compiler once. No Git clone or csources build is needed. +nimMsvcRevision=705392dc03a3f8233f5f0f048acaa9b5267576f6 +nimMsvcAtomicsChecksum=2d9c6b381ad09b09b64d29f2af5a6da693c7835d574075e238a8d8c4370ef05a +nimMsvcCompilerChecksum=22e3011e65906288373fc991d2abc9a06e6ebd348c7682f162a14f21b047451b projectRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$projectRoot" -nimMsvcDir="$projectRoot/deps/nim-msvc-2.2.10-31511ba1-bin" +nimMsvcDir="$projectRoot/deps/nim-msvc-2.2.10-705392dc" atomicsPath=lib/pure/concurrency/atomics.nim +compilerPath=compiler/ccgliterals.nim stageDir= probeDir= nimMsvcCandidate="$nimMsvcDir" @@ -20,28 +22,41 @@ cleanup() { } trap cleanup EXIT -verifyAtomics() { - [[ -f "$1" ]] && [[ "$(sha256sum "$1" | cut -d ' ' -f 1)" == "$nimMsvcChecksum" ]] +verifyFile() { + [[ -f "$1" ]] && [[ "$(sha256sum "$1" | cut -d ' ' -f 1)" == "$2" ]] +} + +fetchPatch() { + local relativePath="$1" expectedChecksum="$2" + curl --fail --location --retry 3 \ + "https://raw.githubusercontent.com/elcritch/Nim/$nimMsvcRevision/$relativePath" \ + --output "$stageDir/$relativePath" + if ! verifyFile "$stageDir/$relativePath" "$expectedChecksum"; then + echo "The pinned $relativePath checksum does not match." >&2 + exit 1 + fi } if [[ ! -e "$nimMsvcDir" ]]; then atlas env 2.2.10 --binary stageDir="$(mktemp -d "$projectRoot/deps/.nim-msvc-XXXXXX")" cp -R "$projectRoot/deps/nim-2.2.10/." "$stageDir/" - curl --fail --location --retry 3 \ - "https://raw.githubusercontent.com/elcritch/Nim/$nimMsvcRevision/$atomicsPath" \ - --output "$stageDir/$atomicsPath" - if ! verifyAtomics "$stageDir/$atomicsPath"; then - echo 'The MSVC atomics module checksum does not match.' >&2 - exit 1 - fi + fetchPatch "$atomicsPath" "$nimMsvcAtomicsChecksum" + fetchPatch "$compilerPath" "$nimMsvcCompilerChecksum" + stageWindows="$(cygpath -m "$stageDir")" + # Keep compiler/nim.cfg (booting/nimcore), but skip Merenda's parent config. + "$projectRoot/deps/nim-2.2.10/bin/nim.exe" c --cc:vcc -d:release --mm:orc \ + --skipUserCfg:on --skipParentCfg:on --lib:"$stageWindows/lib" \ + --out:"$stageWindows/bin/nim.exe" "$stageDir/compiler/nim.nim" # Atlas activation scripts contain the original installation's absolute path. # Use the private compiler path printed below instead. rm -f "$stageDir/activate.bat" "$stageDir/activate.ps1" "$stageDir/activate.sh" nimMsvcCandidate="$stageDir" fi -if [[ ! -f "$nimMsvcCandidate/bin/nim.exe" ]] || ! verifyAtomics "$nimMsvcCandidate/$atomicsPath"; then +if [[ ! -f "$nimMsvcCandidate/bin/nim.exe" ]] || + ! verifyFile "$nimMsvcCandidate/$atomicsPath" "$nimMsvcAtomicsChecksum" || + ! verifyFile "$nimMsvcCandidate/$compilerPath" "$nimMsvcCompilerChecksum"; then echo "Incomplete MSVC Nim environment: remove $nimMsvcDir and retry." >&2 exit 1 fi @@ -53,9 +68,21 @@ cat > "$probeFile" <<'NIM' import std/atomics static: doAssert declared(nimHasCorrectMsvcAtomics) +const binary = block: + var data = newString(20_000) + for i in 0 ..< data.len: + data[i] = char(i mod 256) + data +var value = binary +let original = value +for i, c in value: + doAssert c == char(i mod 256) +value[0] = 'x' +doAssert original[0] == '\0' NIM -"$nimMsvcCandidate/bin/nim.exe" check --cc:vcc --os:windows \ - --skipUserCfg:on --skipParentCfg:on --skipProjCfg:on "$probeFile" +"$nimMsvcCandidate/bin/nim.exe" c -r --cc:vcc --mm:arc \ + --skipUserCfg:on --skipParentCfg:on --skipProjCfg:on \ + --out:"$(cygpath -m "$probeDir")/msvc_probe.exe" "$probeFile" if [[ -n "$stageDir" ]]; then mv "$stageDir" "$nimMsvcDir" diff --git a/CHANGES.md b/CHANGES.md index 312f14a1..878b4d73 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,9 +21,9 @@ - Build Windows Kosmo releases and examples with MSVC. Use the Windows SDK resource compiler, ship the matching PDB, and keep Nim stack traces with function names and source lines in place of GNU libbacktrace on MSVC. - Use Sigils 0.32.0 and the official Nim 2.2.10 compiler with a pinned, - checksum-verified standard-library fix for MSVC atomic exchange, - compare-and-swap, and arithmetic operations. + Use Sigils 0.32.0 and a private Nim 2.2.10 compiler built with pinned, + checksum-verified fixes for MSVC atomic exchange, compare-and-swap, + arithmetic operations, and large ARC/ORC string literals in embedded assets. Pin FigDraw's numeric shader embedding to avoid MSVC's string literal limit. - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so diff --git a/README.md b/README.md index 0885501f..f988b05b 100644 --- a/README.md +++ b/README.md @@ -805,13 +805,15 @@ nim c -o:kosmo src/merenda/kosmo/kosmo.nim ./kosmo . ``` -On Windows, run `bash .github/scripts/install-msvc-nim.sh` first. From a Visual -Studio Developer PowerShell, use the compiler path it prints to build with +On Windows, open a Visual Studio x64 Developer PowerShell and run +`bash .github/scripts/install-msvc-nim.sh` using Git for Windows Bash. +Use the compiler path it prints to build with `& "" c --cc:vcc -o:kosmo.exe src/merenda/kosmo/kosmo.nim`, then run `./kosmo.exe .`. -The script makes a private copy of the official Nim 2.2.10 binary distribution -and installs a checksum-verified standard-library fix for MSVC atomic operations. -Worker threads need that fix; an unpatched MSVC build is rejected at compile time. +The script makes a private copy of the official Nim 2.2.10 distribution, +installs checksum-verified atomic and large-string fixes, and builds the corrected +compiler once. Worker threads need the atomic fix; an unpatched MSVC build is +rejected at compile time. The string fix lets ARC/ORC embed the bundled assets. On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: diff --git a/docs/releasing-kosmo.md b/docs/releasing-kosmo.md index 61db3527..d1eb96cf 100644 --- a/docs/releasing-kosmo.md +++ b/docs/releasing-kosmo.md @@ -29,11 +29,14 @@ Release executables retain native debug information. Windows builds use MSVC, ship `kosmo.pdb` beside `kosmo.exe`, and use Nim's stack tracing with function names and source lines. The installer keeps the PDB beside the installed executable. Windows CI runs `.github/scripts/install-msvc-nim.sh` to copy the official Nim -2.2.10 binary distribution into a private environment and install a corrected -MSVC atomics module from an immutable fork revision. The script verifies the -module's SHA-256 and probes the compiler's default library lookup before adding -it to CI's path. The Atlas-managed official installation stays untouched. Local -MSVC builds must use that script's compiler too. The regression tests run +2.2.10 distribution into a private environment and install atomic and ARC/ORC +large-string fixes from an immutable fork revision. The script verifies both +files' SHA-256 and builds the compiler once with the official binary. It retains +the compiler's own configuration while skipping Merenda's parent configuration. +A compiled probe verifies default library lookup, binary string contents, and +copy-on-write behavior before adding the compiler to CI's path. The Atlas-managed +official installation stays untouched. Local MSVC builds must use that script's +compiler too. The regression tests run natively with MSVC in the fork, and the release job additionally checks shared ownership, concurrent increments, and worker shutdown before packaging. The other platforms link the `libbacktrace` stack-trace override. Nim's From a57d2311f641e4cf02f1dd0ac657460b60809c08 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 00:42:14 +0300 Subject: [PATCH 09/27] Compile the Nim icon resource for the private MSVC build --- .github/scripts/install-msvc-nim.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/scripts/install-msvc-nim.sh b/.github/scripts/install-msvc-nim.sh index 10ba4912..91188a58 100644 --- a/.github/scripts/install-msvc-nim.sh +++ b/.github/scripts/install-msvc-nim.sh @@ -43,6 +43,11 @@ if [[ ! -e "$nimMsvcDir" ]]; then cp -R "$projectRoot/deps/nim-2.2.10/." "$stageDir/" fetchPatch "$atomicsPath" "$nimMsvcAtomicsChecksum" fetchPatch "$compilerPath" "$nimMsvcCompilerChecksum" + # Binary distributions omit the prebuilt MSVC resource used by compiler/nim.nim. + ( + cd "$stageDir/icons" + MSYS2_ARG_CONV_EXCL='*' rc.exe /nologo /fo nim-amd64-windows-vcc.res nim.rc + ) stageWindows="$(cygpath -m "$stageDir")" # Keep compiler/nim.cfg (booting/nimcore), but skip Merenda's parent config. "$projectRoot/deps/nim-2.2.10/bin/nim.exe" c --cc:vcc -d:release --mm:orc \ From 1fbac8306ecb03c3d04c761d96899c02a3b3f4c7 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 00:46:04 +0300 Subject: [PATCH 10/27] Retain existing FigDraw dependency with the general MSVC compiler fix --- CHANGES.md | 1 - merenda.nimble | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 878b4d73..7f0a4cee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,7 +24,6 @@ Use Sigils 0.32.0 and a private Nim 2.2.10 compiler built with pinned, checksum-verified fixes for MSVC atomic exchange, compare-and-swap, arithmetic operations, and large ARC/ORC string literals in embedded assets. - Pin FigDraw's numeric shader embedding to avoid MSVC's string literal limit. - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and diff --git a/merenda.nimble b/merenda.nimble index aa383fb7..db1dadcd 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,7 +12,7 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw#f25a3ec873ba38906534d1cca3df1520bf79b3dd [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" requires "gh:elcritch/sigils#00ce21564a390bd3883da601b54f59344d1b6c7b [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" From 24936f8228933a7e29eb440be55b206a39d1a02e Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 00:53:33 +0300 Subject: [PATCH 11/27] Pin Sigils 0.32 with the standalone MSVC safety guard --- merenda.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merenda.nimble b/merenda.nimble index db1dadcd..34dc0a5d 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -13,7 +13,7 @@ requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "gh:elcritch/sigils#00ce21564a390bd3883da601b54f59344d1b6c7b [sigNameAsString, closures, siwin, chronos]" +requires "gh:elcritch/sigils#4d0d75658f6033996f44fcb8af7d99aeeeda9766 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" requires "cborious" From ebd338cb02c289aa901df5373e2398bf2e9d7698 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 01:28:54 +0300 Subject: [PATCH 12/27] Invoke configured MSVC directly to preserve linker response files --- .github/scripts/install-msvc-nim.sh | 2 ++ .github/workflows/release-kosmo.yml | 9 +++++++++ CHANGES.md | 2 ++ README.md | 2 ++ config.nims | 4 ++++ .../fixtures/windowsresponsefiles.nim | 16 ++++++++++++++++ 6 files changed, 35 insertions(+) create mode 100644 tests/integrations/fixtures/windowsresponsefiles.nim diff --git a/.github/scripts/install-msvc-nim.sh b/.github/scripts/install-msvc-nim.sh index 91188a58..a1edb490 100644 --- a/.github/scripts/install-msvc-nim.sh +++ b/.github/scripts/install-msvc-nim.sh @@ -51,6 +51,7 @@ if [[ ! -e "$nimMsvcDir" ]]; then stageWindows="$(cygpath -m "$stageDir")" # Keep compiler/nim.cfg (booting/nimcore), but skip Merenda's parent config. "$projectRoot/deps/nim-2.2.10/bin/nim.exe" c --cc:vcc -d:release --mm:orc \ + --vcc.exe:cl.exe --vcc.linkerexe:cl.exe \ --skipUserCfg:on --skipParentCfg:on --lib:"$stageWindows/lib" \ --out:"$stageWindows/bin/nim.exe" "$stageDir/compiler/nim.nim" # Atlas activation scripts contain the original installation's absolute path. @@ -86,6 +87,7 @@ value[0] = 'x' doAssert original[0] == '\0' NIM "$nimMsvcCandidate/bin/nim.exe" c -r --cc:vcc --mm:arc \ + --vcc.exe:cl.exe --vcc.linkerexe:cl.exe \ --skipUserCfg:on --skipParentCfg:on --skipProjCfg:on \ --out:"$(cygpath -m "$probeDir")/msvc_probe.exe" "$probeFile" diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index becbbf07..37815cd4 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -803,6 +803,15 @@ jobs: shell: bash run: bash .github/scripts/install-msvc-nim.sh + - name: Verify MSVC linker response files + shell: pwsh + timeout-minutes: 5 + run: | + New-Item -ItemType Directory -Force -Path build | Out-Null + nim c -r --cc:vcc --passC:/MT --out:build/windowsresponsefiles.exe ` + tests/integrations/fixtures/windowsresponsefiles.nim + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Build Kosmo shell: pwsh run: | diff --git a/CHANGES.md b/CHANGES.md index 7f0a4cee..00b97137 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,8 @@ Use Sigils 0.32.0 and a private Nim 2.2.10 compiler built with pinned, checksum-verified fixes for MSVC atomic exchange, compare-and-swap, arithmetic operations, and large ARC/ORC string literals in embedded assets. + Invoke MSVC directly from its Developer shell so large linker response files + remain within Windows' command-line limit. - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and diff --git a/README.md b/README.md index f988b05b..fe3ef201 100644 --- a/README.md +++ b/README.md @@ -814,6 +814,8 @@ The script makes a private copy of the official Nim 2.2.10 distribution, installs checksum-verified atomic and large-string fixes, and builds the corrected compiler once. Worker threads need the atomic fix; an unpatched MSVC build is rejected at compile time. The string fix lets ARC/ORC embed the bundled assets. +Keep the Developer shell open when building: the project invokes `cl.exe` +directly so large applications can link using response files. On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: diff --git a/config.nims b/config.nims index a8071334..0976db0d 100644 --- a/config.nims +++ b/config.nims @@ -19,6 +19,10 @@ --nimcache: "deps/.nimcache" when defined(windows): + # Use the configured Developer shell directly; vccexe expands response files + # into command lines that exceed Windows' limit for large applications. + switch("vcc.exe", "cl.exe") + switch("vcc.linkerexe", "cl.exe") # MSVC emits PDBs; GNU libbacktrace expects DWARF and a GNU unwinder. switch("stackTrace", "on") switch("lineTrace", "on") diff --git a/tests/integrations/fixtures/windowsresponsefiles.nim b/tests/integrations/fixtures/windowsresponsefiles.nim new file mode 100644 index 00000000..b257bcee --- /dev/null +++ b/tests/integrations/fixtures/windowsresponsefiles.nim @@ -0,0 +1,16 @@ +## Standalone native MSVC regression for linker commands over Windows' limit. + +when isMainModule: + when not (defined(windows) and defined(vcc)): + {.error: "Compile this fixture for Windows with --cc:vcc".} + + const linkerOptions = block: + var options = "/link" + # Repeating a harmless warning option forces a response file. vccexe used + # to expand it again and exceed CreateProcess's 32,767-character limit. + for i in 0 ..< 3000: + options.add " /IGNORE:4099" + options + + {.passL: linkerOptions.} + echo "MSVC linked successfully with a response file over 32K" From d40fcc8ae9cfc7649bda312d1bb54b93cfb5b1d1 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 02:15:03 +0300 Subject: [PATCH 13/27] Require the released Sigils 0.32.0 package --- merenda.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merenda.nimble b/merenda.nimble index 34dc0a5d..dca59dcb 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -13,7 +13,7 @@ requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "gh:elcritch/sigils#4d0d75658f6033996f44fcb8af7d99aeeeda9766 [sigNameAsString, closures, siwin, chronos]" +requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" requires "cborious" From 6416bb092eebb6ead3133c646af1a67cdd883f71 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 03:32:22 +0300 Subject: [PATCH 14/27] Prepare 0.28.0 and fix locally signed Kosmo startup --- .github/workflows/release-kosmo.yml | 3 +++ CHANGES.md | 5 ++++- merenda.nimble | 3 ++- packaging/macos/debug.entitlements | 2 ++ packaging/macos/install-local.sh | 3 +++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index 37815cd4..44ab9e20 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -703,6 +703,9 @@ jobs: exit 1 fi + - name: Verify signed Kosmo startup + run: build/Kosmo.app/Contents/MacOS/kosmo --help + - name: Package Kosmo app run: | ditto \ diff --git a/CHANGES.md b/CHANGES.md index 00b97137..7ab0c29b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,10 @@ # Changes -## Unreleased +## 0.28.0 + +- Let locally signed macOS apps load their bundled text libraries. Check startup + after signing and through the installed command to catch loader failures. - Build Windows Kosmo releases and examples with MSVC. Use the Windows SDK resource compiler, ship the matching PDB, and keep Nim stack traces with diff --git a/merenda.nimble b/merenda.nimble index dca59dcb..71655eee 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -1,4 +1,4 @@ -version = "0.27.6" +version = "0.28.0" author = "Jaremy Creechley" description = "Nim-native UI toolkit" license = "BSD-3-Clause" @@ -13,6 +13,7 @@ requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/harfbuzzy#15a69436b3acd3bb6f4cc0f41a805ec1e2504f3c" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" diff --git a/packaging/macos/debug.entitlements b/packaging/macos/debug.entitlements index 5a8979bd..9f9dd10f 100644 --- a/packaging/macos/debug.entitlements +++ b/packaging/macos/debug.entitlements @@ -4,5 +4,7 @@ com.apple.security.get-task-allow + com.apple.security.cs.disable-library-validation + diff --git a/packaging/macos/install-local.sh b/packaging/macos/install-local.sh index 176f502f..c6c3c006 100755 --- a/packaging/macos/install-local.sh +++ b/packaging/macos/install-local.sh @@ -116,12 +116,14 @@ plutil -lint "$app_path/Contents/Info.plist" codesign --force --options runtime --sign - \ --entitlements packaging/macos/debug.entitlements "$app_path" codesign --verify --deep --strict "$app_path" +"$app_path/Contents/MacOS/kosmo" --help >/dev/null mkdir -p "$install_dir" "$bin_dir" staged_app="$install_dir/.Kosmo.app.install.$$" staged_command="$bin_dir/.kosmo.install.$$" ditto "$app_path" "$staged_app" codesign --verify --deep --strict "$staged_app" +"$staged_app/Contents/MacOS/kosmo" --help >/dev/null if [[ -e "$installed_app" || -L "$installed_app" ]]; then backup_app="$install_dir/.Kosmo.app.backup.$$" @@ -131,6 +133,7 @@ restore_install=1 mv "$staged_app" "$installed_app" staged_app="" ln -s "$installed_app/Contents/MacOS/kosmo" "$staged_command" +"$staged_command" --help >/dev/null mv -f "$staged_command" "$installed_command" staged_command="" restore_install=0 From 8bd038c1a91cd643363339426de799b6a45bc14c Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 04:14:23 +0300 Subject: [PATCH 15/27] Statically link both text libraries in local Kosmo installs --- CHANGES.md | 1 + README.md | 6 +++--- merenda.nimble | 4 ++-- packaging/macos/install-local.sh | 15 ++++++++------- packaging/macos/staticTextLicenses.nims | 10 ++++++++++ 5 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 packaging/macos/staticTextLicenses.nims diff --git a/CHANGES.md b/CHANGES.md index 7ab0c29b..b3299ed6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,7 @@ - Let locally signed macOS apps load their bundled text libraries. Check startup after signing and through the installed command to catch loader failures. + Statically link HarfBuzz and FriBidi when installing with `nim install_kosmo`. - Build Windows Kosmo releases and examples with MSVC. Use the Windows SDK resource compiler, ship the matching PDB, and keep Nim stack traces with diff --git a/README.md b/README.md index fe3ef201..b18cdc76 100644 --- a/README.md +++ b/README.md @@ -821,15 +821,15 @@ On macOS, install the current checkout as a complete `Kosmo.app` with its icon, bundled notices, debug symbols, and local code signature: ```sh -brew install meson ninja pkg-config +brew install cmake meson ninja nim install_kosmo ``` This uses Atlas to resolve dependencies, replaces `~/Applications/Kosmo.app`, and updates the `~/.local/bin/kosmo` link. Restart Kosmo if it was already running. Set `KOSMO_INSTALL_DIR` or `KOSMO_BIN_DIR` to choose other locations. -The app build compiles and bundles its text libraries for macOS 13, so the -installed app does not require Homebrew at runtime. +This build links HarfBuzz and FriBidi statically with `-d:harfbuzzyStatic`, +so the installed app does not require Homebrew at runtime. ## Explore more diff --git a/merenda.nimble b/merenda.nimble index 71655eee..0365eb84 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,8 +12,8 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw >= 0.44.0 [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "gh:elcritch/harfbuzzy#15a69436b3acd3bb6f4cc0f41a805ec1e2504f3c" +requires "gh:elcritch/figdraw#813e154b784be539fbc563c57b4454689a875e27 [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/harfbuzzy#84da3116293113eb70e20be56d727cabdb61fbdb" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" diff --git a/packaging/macos/install-local.sh b/packaging/macos/install-local.sh index c6c3c006..bf76e456 100755 --- a/packaging/macos/install-local.sh +++ b/packaging/macos/install-local.sh @@ -10,7 +10,7 @@ fi repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$repo_root" -for required_command in atlas nim git awk sips iconutil xcrun nm plutil codesign ditto brew pkg-config meson ninja; do +for required_command in atlas nim git awk sips iconutil xcrun nm plutil codesign ditto meson ninja cmake; do if ! command -v "$required_command" >/dev/null 2>&1; then echo "install-local.sh: missing required command: $required_command" >&2 exit 1 @@ -84,9 +84,12 @@ done iconutil -c icns "$iconset_path" -o "$app_path/Contents/Resources/Kosmo.icns" echo "install-local.sh: building Kosmo.app $version ($revision)" -text_prefix="$build_root/text-libraries" -bash packaging/macos/build-text-libraries.sh "$text_prefix" -export PKG_CONFIG_PATH="$text_prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +export MACOSX_DEPLOYMENT_TARGET=13.0 +export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" +export CC="$(xcrun --find clang)" +export CXX="$(xcrun --find clang++)" +KOSMO_TEXT_LICENSE_DIR="$app_path/Contents/Resources/text-library-licenses" \ + nim e packaging/macos/staticTextLicenses.nims nim c \ --opt:size \ --debugger:native \ @@ -94,13 +97,11 @@ nim c \ --stackTrace:off \ -d:nimStackTraceOverride \ -d:moe.embedded \ - -d:harfbuzzyDynlib=@executable_path/../Frameworks/libharfbuzz.dylib \ - -d:harfbuzzyFribidiDynlib=@executable_path/../Frameworks/libfribidi.dylib \ + -d:harfbuzzyStatic \ -d:KosmoVersion="$version" \ -d:KosmoGitHashOverride="$revision" \ --out:"$app_path/Contents/MacOS/kosmo" \ src/merenda/kosmo/kosmo.nim -bash packaging/macos/bundle-text-libraries.sh "$app_path" "$text_prefix" xcrun dsymutil "$app_path/Contents/MacOS/kosmo" \ -o "$app_path/Contents/MacOS/kosmo.dSYM" binary_uuid="$(xcrun dwarfdump --uuid "$app_path/Contents/MacOS/kosmo" | awk '{ print $2 }')" diff --git a/packaging/macos/staticTextLicenses.nims b/packaging/macos/staticTextLicenses.nims new file mode 100644 index 00000000..111218c2 --- /dev/null +++ b/packaging/macos/staticTextLicenses.nims @@ -0,0 +1,10 @@ +## Copy notices from the pinned native sources used by the static build. +import std/os +import harfbuzzy/private/staticHarfbuzz + +let destination = getEnv("KOSMO_TEXT_LICENSE_DIR") +if destination.len == 0: + raise newException(ValueError, "KOSMO_TEXT_LICENSE_DIR is required") +mkDir(destination) +cpFile(harfbuzzSourceDir.parentDir / "COPYING", destination / "harfbuzz.txt") +cpFile(fribidiSourceDir / "COPYING", destination / "fribidi.txt") From eaa34928f175027a007ae9a140e87ccc602fe32c Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 04:16:14 +0300 Subject: [PATCH 16/27] Keep cached local builds on the macOS deployment target --- packaging/macos/install-local.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/macos/install-local.sh b/packaging/macos/install-local.sh index bf76e456..8f801786 100755 --- a/packaging/macos/install-local.sh +++ b/packaging/macos/install-local.sh @@ -91,6 +91,8 @@ export CXX="$(xcrun --find clang++)" KOSMO_TEXT_LICENSE_DIR="$app_path/Contents/Resources/text-library-licenses" \ nim e packaging/macos/staticTextLicenses.nims nim c \ + --passC:-mmacosx-version-min=13.0 \ + --passL:-mmacosx-version-min=13.0 \ --opt:size \ --debugger:native \ --lineTrace:off \ From 2925305659cc6182f57f4d8744213e7228751cb7 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 04:18:55 +0300 Subject: [PATCH 17/27] Rebuild cached native objects during local installation --- packaging/macos/install-local.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/macos/install-local.sh b/packaging/macos/install-local.sh index 8f801786..44eca2e5 100755 --- a/packaging/macos/install-local.sh +++ b/packaging/macos/install-local.sh @@ -90,7 +90,9 @@ export CC="$(xcrun --find clang)" export CXX="$(xcrun --find clang++)" KOSMO_TEXT_LICENSE_DIR="$app_path/Contents/Resources/text-library-licenses" \ nim e packaging/macos/staticTextLicenses.nims +# Cached external C objects can retain an older deployment target. nim c \ + --forceBuild:on \ --passC:-mmacosx-version-min=13.0 \ --passL:-mmacosx-version-min=13.0 \ --opt:size \ From 79c496cea8e2cbab8e826cd7e1d6bdcf7bae0367 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 04:26:13 +0300 Subject: [PATCH 18/27] Update Harfbuzzy pin for MSVC static archive naming --- merenda.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merenda.nimble b/merenda.nimble index 0365eb84..f8a55ec5 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -13,7 +13,7 @@ requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" requires "gh:elcritch/figdraw#813e154b784be539fbc563c57b4454689a875e27 [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "gh:elcritch/harfbuzzy#84da3116293113eb70e20be56d727cabdb61fbdb" +requires "gh:elcritch/harfbuzzy#7b09d58ddd2c8ef5eb9f0e58aeba2a190c1cc0f3" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" From 8ddc74a87198d66db4ba3c689691e26bcdd787b8 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 12:59:24 +0300 Subject: [PATCH 19/27] Allow optimized MSVC release builds to finish --- .github/workflows/release-kosmo.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-kosmo.yml b/.github/workflows/release-kosmo.yml index 44ab9e20..26167fc7 100644 --- a/.github/workflows/release-kosmo.yml +++ b/.github/workflows/release-kosmo.yml @@ -753,7 +753,8 @@ jobs: build-windows: runs-on: windows-2022 - timeout-minutes: 45 + # Optimizing Kosmo's large C translation units can exceed 45 minutes. + timeout-minutes: 75 steps: - uses: actions/checkout@v5 From f9e9d5b9ad82d4f09dc218d56aa96a1fb32de74d Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 13:41:51 +0300 Subject: [PATCH 20/27] Publish hook fixture readiness atomically --- .github/workflows/build-full.yml | 3 ++- tests/integrations/fixtures/moefrontendexec.nim | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-full.yml b/.github/workflows/build-full.yml index 56013b0c..259df8a9 100644 --- a/.github/workflows/build-full.yml +++ b/.github/workflows/build-full.yml @@ -10,7 +10,8 @@ jobs: tests: name: tests (${{ matrix.os }}, ${{ matrix.nimversion }}, ${{ matrix.display }}) runs-on: ${{ matrix.os }} - timeout-minutes: 40 + # Full GUI and integration coverage can take nearly 40 minutes on macOS. + timeout-minutes: 60 env: ATLAS_TEST_NIM_ARGS: ${{ matrix.nimargs }} defaults: diff --git a/tests/integrations/fixtures/moefrontendexec.nim b/tests/integrations/fixtures/moefrontendexec.nim index 5941ff3b..b7ad5398 100644 --- a/tests/integrations/fixtures/moefrontendexec.nim +++ b/tests/integrations/fixtures/moefrontendexec.nim @@ -2,7 +2,10 @@ import std/[monotimes, os, times] if paramCount() >= 3 and paramStr(1) == "--moe-hook-fixture": - writeFile(paramStr(3), $getCurrentProcessId()) + let ready = paramStr(3) + # File existence must mean the parent can read a complete PID. + writeFile(ready & ".tmp", $getCurrentProcessId()) + moveFile(ready & ".tmp", ready) echo "hook output: " & paramStr(2) if paramCount() == 4 and paramStr(4) == "wait": let deadline = getMonoTime() + initDuration(seconds = 60) From fd4116c410dc61471732770668a4c693c3477dce Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 13:53:55 +0300 Subject: [PATCH 21/27] Pin Harfbuzzy 0.3.0 and the Windows-compatible raster dependency --- merenda.nimble | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merenda.nimble b/merenda.nimble index f8a55ec5..24dd95fc 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,8 +12,8 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw#813e154b784be539fbc563c57b4454689a875e27 [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "gh:elcritch/harfbuzzy#7b09d58ddd2c8ef5eb9f0e58aeba2a190c1cc0f3" +requires "gh:elcritch/figdraw#2371c405d6fc0f6f0e35acf58f0968bae7d2b17c [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/harfbuzzy#48611dc89ad508644fd1eba475760390526606ea" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" From b93cab7519475c20ac386471918da3384d20b474 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 14:41:22 +0300 Subject: [PATCH 22/27] Use released Harfbuzzy 0.3.0 --- merenda.nimble | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merenda.nimble b/merenda.nimble index 24dd95fc..435e9ae8 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,8 +12,8 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw#2371c405d6fc0f6f0e35acf58f0968bae7d2b17c [siwin, harfbuzz, textBackendHarfbuzzy]" -requires "gh:elcritch/harfbuzzy#48611dc89ad508644fd1eba475760390526606ea" +requires "gh:elcritch/figdraw#2eed8d4b3d2d7b5a25a6df30a66b4a9ba744808a [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/harfbuzzy >= 0.3.0" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" requires "gh:elcritch/kiwiberry#4d273da93f67d8e5d6317898851772b32fa635ec" From 6319b5a7dcd0b0ab87122ad0f190fc11f9579620 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 15:02:50 +0300 Subject: [PATCH 23/27] Pin FigDraw 0.45.0 --- merenda.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merenda.nimble b/merenda.nimble index 435e9ae8..f4ac3108 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,7 +12,7 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw#2eed8d4b3d2d7b5a25a6df30a66b4a9ba744808a [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/figdraw#7eef757ac210f1ecb9a2ee54f0ce55861b12a7ce [siwin, harfbuzz, textBackendHarfbuzzy]" requires "gh:elcritch/harfbuzzy >= 0.3.0" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" From 3e1e312d18a635318dec28f2b6f6bb055fe6d1cb Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 15:20:36 +0300 Subject: [PATCH 24/27] Retry Windows compiler promotion after transient file locks --- .github/scripts/install-msvc-nim.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/scripts/install-msvc-nim.sh b/.github/scripts/install-msvc-nim.sh index a1edb490..b531e4af 100644 --- a/.github/scripts/install-msvc-nim.sh +++ b/.github/scripts/install-msvc-nim.sh @@ -92,8 +92,19 @@ NIM --out:"$(cygpath -m "$probeDir")/msvc_probe.exe" "$probeFile" if [[ -n "$stageDir" ]]; then - mv "$stageDir" "$nimMsvcDir" - stageDir= + # Windows may briefly retain a file lock after the compiler/probe exits. + for moveAttempt in {1..20}; do + # Do not nest the staged compiler if the destination unexpectedly exists. + if mv -T "$stageDir" "$nimMsvcDir"; then + stageDir= + break + fi + if ((moveAttempt == 20)); then + echo "Cannot install the MSVC Nim environment at $nimMsvcDir." >&2 + exit 1 + fi + sleep 0.5 + done fi if [[ -n "${GITHUB_PATH:-}" ]]; then From 5acad9bd3a61d462d9e1f9cb3bd0782fde782d09 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Mon, 19 Oct 2026 17:03:41 +0300 Subject: [PATCH 25/27] Use released FigDraw 0.45.0 dependency --- CHANGES.md | 13 +++++++------ merenda.nimble | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b3299ed6..c91c2397 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,5 @@ +# Changes + ## Unreleased - Route development control through Sigils JSON-RPC selectors and dispatcher over @@ -14,8 +16,6 @@ - Add `MERENDA_BACKGROUND=1` nonactivating development presentation on macOS and Windows through a pinned Siwin update. -# Changes - ## 0.28.0 - Let locally signed macOS apps load their bundled text libraries. Check startup @@ -33,12 +33,16 @@ - Enable FigDraw's `textBackendHarfbuzzy` Atlas feature in `merenda.nimble` so this checkout and downstream Merenda apps use HarfBuzz text rendering and - automatic system-font fallback. Previously HarfBuzz was installed as a dependency, + automatic system-font fallback. Require released FigDraw 0.45.0 and Harfbuzzy + 0.3.0 for dynamic and static builds. Previously HarfBuzz was installed as a dependency, but FigDraw still selected Pixie, leaving automatic fallback disabled. Missing glyphs in the editor, terminal, and other text views can now use system fonts, including checkbox symbols absent from the bundled Nerd Font. Initialize fallback on background text-layout threads as well as the main thread. +- Render Markdown task lists with ☐ for `[ ]`, ☑ for `[x]` or `[X]`, and ☒ for + `[-]`, preserving nested lists, numbering, and inline formatting. + - Match split divider fills, edges, and grips to each bundled theme's palette, including the dark Nebula, Peachy, and Synthwave83 themes. @@ -92,9 +96,6 @@ Use a compact menu that opens upward, and keep its checked selection synchronized with Settings and detached editor windows. -- Render Markdown task lists with ☐ for `[ ]`, ☑ for `[x]` or `[X]`, and ☒ for - `[-]`, preserving nested lists, numbering, and inline formatting. - - Add a Windows VirtualBox test setup, matching Guest Additions, an OpenGL 3.3 console probe, and guards against concurrent bhyve operation. Document the failed GPU acceleration test with the FreeBSD 7.2.20 packages. diff --git a/merenda.nimble b/merenda.nimble index f4ac3108..e4204c56 100644 --- a/merenda.nimble +++ b/merenda.nimble @@ -12,7 +12,7 @@ requires "chroniclers >= 0.6" requires "crunchy >= 0.1.11" # Includes merged ABI/X11 fixes and opt-in nonactivating window presentation. requires "gh:elcritch/siwin#50a96efcd5a3feb5fcfdf07a46baa7da50360c0b" -requires "gh:elcritch/figdraw#7eef757ac210f1ecb9a2ee54f0ce55861b12a7ce [siwin, harfbuzz, textBackendHarfbuzzy]" +requires "gh:elcritch/figdraw >= 0.45.0 [siwin, harfbuzz, textBackendHarfbuzzy]" requires "gh:elcritch/harfbuzzy >= 0.3.0" requires "gh:elcritch/sigils >= 0.32.0 [sigNameAsString, closures, siwin, chronos]" requires "gh:elcritch/variant#fix/ic-type-ids" From 9b86a3a39572be0582e94ba4235b9f4bc2325716 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Tue, 20 Oct 2026 00:40:03 +0300 Subject: [PATCH 26/27] Document development control and live screenshots for agents --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index cce84d93..646688af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,7 @@ - Prefer `nim ic` for local development builds and testing to reuse incremental compilation results. Use `-r` to run the compiled program, and use `--backend:ic` with `atlas-run build` or `atlas-run tests`. Leave CI's compiler backend unchanged. - Run multiple or all tests with `atlas-run tests --backend:ic [test-selections]`. Omit selections to run the full suite, or pass one or more test selectors to run focused tests. Selectors match as `foo` -> `tests/tfoo*.nim`, `foo.nim` -> `tests/tfoo.nim`, and `examples/foo*.nim` -> `examples/foo*.nim`. Do not adjust the `--jobs` count or the `--nimcache`. - Compile the example bundle with `atlas-run tests --backend:ic --compile-only examples/all_compile.nim`; do not run `examples/all_compile.nim` as a test. +- Development control (`-d:merenda.devControl`) provides live renderer screenshots, window inspection, input automation, and CSS reloads. Read [docs/dev-control.md](docs/dev-control.md) before capturing live screenshots from a running app. - Execute a single test locally using `nim ic`: - `nim ic -r tests/ttransfer.nim` - `nim ic -r -d:debug tests/ttransfer.nim` From 5334031a1186348cdecf095ad16fb8b01b618f12 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Tue, 20 Oct 2026 00:40:29 +0300 Subject: [PATCH 27/27] Note background app presentation without taking focus --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 646688af..7ce75eb9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ - Prefer `nim ic` for local development builds and testing to reuse incremental compilation results. Use `-r` to run the compiled program, and use `--backend:ic` with `atlas-run build` or `atlas-run tests`. Leave CI's compiler backend unchanged. - Run multiple or all tests with `atlas-run tests --backend:ic [test-selections]`. Omit selections to run the full suite, or pass one or more test selectors to run focused tests. Selectors match as `foo` -> `tests/tfoo*.nim`, `foo.nim` -> `tests/tfoo.nim`, and `examples/foo*.nim` -> `examples/foo*.nim`. Do not adjust the `--jobs` count or the `--nimcache`. - Compile the example bundle with `atlas-run tests --backend:ic --compile-only examples/all_compile.nim`; do not run `examples/all_compile.nim` as a test. -- Development control (`-d:merenda.devControl`) provides live renderer screenshots, window inspection, input automation, and CSS reloads. Read [docs/dev-control.md](docs/dev-control.md) before capturing live screenshots from a running app. +- Development control (`-d:merenda.devControl`) provides live renderer screenshots, window inspection, input automation, and CSS reloads. Use `MERENDA_BACKGROUND=1` to start apps in the background without taking focus on macOS and Windows. Read [docs/dev-control.md](docs/dev-control.md) before capturing live screenshots from a running app. - Execute a single test locally using `nim ic`: - `nim ic -r tests/ttransfer.nim` - `nim ic -r -d:debug tests/ttransfer.nim`