Skip to content

Cross-platform support: target macOS and Linux #32

Open

Description

@AThraen

Goal

Ship CodeShellManager on Windows, macOS, and Linux from a single codebase while preserving the current feature set (multi-PTY tabbed/grid layout, FTS5 search, alert detection, git integration, session persistence, sleep/wake, SSH remote sessions).

Three Windows-only pillars to replace

  1. WPF → Avalonia 11 — the entire UI layer
  2. ConPTY P/Invoke → Pty.Net — Terminal/PseudoTerminal.cs
  3. WebView2 → cross-platform terminal host — biggest unknown, needs a spike

The MVVM layer (MainViewModel, SessionViewModel) and most services (SessionManager, StateService, SearchService, ColorService, GitService, AlertDetector, OutputIndexer, AlertDetector) should port largely unchanged — they don't depend on WPF or Win32 types.

Strategy by layer

UI: WPF → Avalonia

  • Avalonia 11 uses similar XAML + MVVM, though its dialect differs (no WPF Triggers, Styles work differently, Border vs Border are mostly congruent).
  • Files to port: MainWindow.xaml/.cs, Views/NewSessionDialog.xaml/.cs, Views/SettingsWindow.xaml/.cs.
  • Heavy lift: MainWindow.xaml.cs has substantial imperative code-behind (sidebar building, layout switching, drag-reorder, search panel, dormant entries). Most of it is platform-agnostic logic that just calls into WPF visuals — port is largely mechanical.
  • Catppuccin hex literals carry over as-is (just SolidColorBrush declarations).
  • TrayIcon is built into Avalonia — replaces the current Windows tray helper.
  • INotifyPropertyChanged + ObservableCollection<T> work identically in Avalonia.

PTY: ConPTY → Pty.Net

  • Microsoft's Pty.Net wraps ConPTY on Windows and forkpty on Mac/Linux behind a single API.
  • Replace the body of Terminal/PseudoTerminal.cs; keep the public surface stable so TerminalBridge and OutputIndexer don't change.
  • SSH support already works cross-platform — system ssh is the PTY command, nothing else to change there.
  • Default shell selection needs platform branching: cmd/pwsh on Windows, $SHELL (fallback bash/zsh) on Unix.

Terminal host: WebView2 → ?

This is the riskiest part. Three candidate paths:

Option Pros Cons
Avalonia.WebView (per-platform native web views: WebView2 / WKWebView / WebKitGTK) Reuses existing Assets/terminal.html + xterm.js with minimal changes Per-platform behavior differs; transparency/font rendering may not match across all 3
Photino.NET Lighter, well-maintained, embedded native web runtime Less Avalonia integration; would need a custom host
Native terminal control (port/integrate a managed xterm equivalent) No web stack Major undertaking; loses existing xterm.js investment

Recommended: spike Avalonia.WebView first, fall back to Photino if blocking issues surface.

Smaller cross-platform concerns

  • Default shells: detect from $SHELL; sensible per-OS fallbacks.
  • State path: %AppData% / ~/Library/Application Support / ~/.config via Environment.SpecialFolder.ApplicationData — already works.
  • SQLite: Microsoft.Data.Sqlite is already cross-platform ✓.
  • Windows Terminal profile import: gate behind OperatingSystem.IsWindows() — feature is Windows-specific by nature.
  • Keyboard shortcuts: Avalonia's KeyGesture handles Ctrl ↔ Cmd mapping; revisit Ctrl+T/Ctrl+W/Ctrl+F to use Meta on macOS.
  • Font fallback chain: Cascadia Code → Menlo (macOS) → DejaVu Sans Mono (Linux) → generic monospace.
  • Crash log path: already uses SpecialFolder — should work unchanged.
  • Tray icon parity: Linux tray support varies by desktop environment (GNOME requires extensions). Treat as best-effort.

Packaging & distribution

  • Windows: keep existing signed MSI + winget pipeline (.github/workflows/winget.yml).
  • macOS: .app bundle → .dmg. Requires Apple Developer cert + notarization. Distribute via Homebrew Cask.
  • Linux: AppImage (universal) as default; consider Flathub for desktop integration; .deb / .rpm optional.

CI/CD changes

  • .github/workflows/build.yml: matrix over windows-latest / macos-latest / ubuntu-latest.
  • macOS notarization step (needs APPLE_ID, APPLE_APP_PASSWORD, APPLE_TEAM_ID secrets).
  • AppImage build step on Linux.
  • New Homebrew tap workflow on release: released event.
  • winget.yml unchanged (Windows-only path).

Suggested phases

  1. Spike (~1–2 weeks) — prove the three risky pieces in isolation: Avalonia + Pty.Net + Avalonia.WebView running xterm.js with a trivial PTY echo on all 3 OSes. No feature parity, just "does the stack work."
  2. Avalonia port on Windows (~3–4 weeks) — full feature parity, still Windows-only build target. Validates the rewrite doesn't lose anything before adding OS surface area.
  3. macOS (~1–2 weeks) — cross-build, manual smoke test, signing + notarization, tray icon, fonts, shortcuts.
  4. Linux (~1–2 weeks) — same, plus a distro test matrix (Ubuntu, Fedora, Arch).
  5. CI/CD + packaging (~1 week) — multi-OS matrix, installers, release automation.

Risks

  • WebView2 replacement — biggest unknown. If Avalonia.WebView falls short on perf/transparency/fonts, terminal UX may regress.
  • ConPTY ↔ forkpty behavior gaps — some edge cases (resize signals, mouse modes, signals like SIGWINCH) may behave subtly differently.
  • macOS notarization — first-time setup is tedious; can stall a release.
  • Linux tray — GNOME parity is poor without user-installed extensions.
  • MainWindow.xaml.cs volume — port is mostly mechanical but there's a lot of code; risk of subtle regressions in sidebar/layout/drag-reorder logic.

Out of scope (for this issue)

  • Mobile (iOS/Android).
  • Web/cloud deployment.
  • Rewriting search, alert, or persistence subsystems — they should port unchanged.

Activity

  1. AThraen commented on Jun 12, 2026

    @AThraen
    ContributorAuthor

    Phase 1 spike verdict: conditional GO

    Spike branch: feat/cross-platform-spike (spikes/CrossPlatformSpike/ — full findings log in its README).

    Stack validated: Avalonia 12 + Avalonia.Controls.WebView (first-party, MIT — supersedes the community wrapper this issue suggested) + Porta.Pty (Pty.Net is not on nuget.org). Everything MIT end to end.

    • Windows: full pass. Real interactive pwsh through xterm.js — typing, ANSI colors, exit codes, no perceptible latency. Gotcha: an app.manifest with the supportedOS GUID is mandatory or NativeWebView crashes at attach.
    • Linux (WSLg): every layer we own works — PTY spawn, both message directions, xterm buffer (proven headlessly via InvokeScript probes). But the GTK web-view adapter fails at presentation: surface stuck at 200×200 and content never reaches the screen. Ubuntu 24.04 ships no WPE packages, so the GTK adapter (libwebkit2gtk-4.1-0 + PreferWebKitGtkInstead) is the only first-party path there. Follow-ups: retest on a native Linux desktop, test WPE on a distro that has it, file upstream, else fall back (community WebView.Avalonia / CefGlue).
    • macOS: CI build green; runtime untested (no hardware yet).
    • CI: 3-OS build matrix green (run).

    Recommendation: start Phase 2 (full Avalonia port, Windows-only target) now — it doesn't depend on the Linux web-view fix. Resolve Linux presentation in parallel before Phase 4.

    🤖 Generated with Claude Code

  2. AThraen commented on Jul 27, 2026

    @AThraen
    ContributorAuthor

    Pushed feat/2a-shared-core — Phase 2a (shared-core extraction) per docs/superpowers/plans/2026-06-15-shared-core-extraction.md.

    Progress: Tasks 1–6 done (Core project created; Models, WPF-free Services/Terminal, ColorService, ITerminalBridge + SessionViewModel, and MainViewModel host seams all moved to CodeShellManager.Core).

    Remaining: Task 7 — verify Core has zero WPF/WinForms/WebView2 references, confirm it builds standalone on a non-Windows TFM, full solution build + test run, manual smoke test, then merge back per the plan's merge commit.

    Branch: https://github.com/umage-ai/CodeShellManager/tree/feat/2a-shared-core

  3. AThraen commented on Aug 7, 2026

    @AThraen
    ContributorAuthor

    ⏸️ On hold as of 2026-08-07

    Parking the cross-platform effort deliberately to focus on stability and bugfixes for the shipped Windows build. This is a pause, not an abandonment — the work below is intact and this comment is the resumption checklist.

    Where the work actually lives

    Nothing from this effort is on main. Both phases exist only on branches. Do not delete these:

    Branch Contents State
    feat/cross-platform-spike Phase 1 spike — spikes/CrossPlatformSpike/ (Avalonia 12 + Porta.Pty + NativeWebView + xterm.js) Complete, verdict recorded above
    feat/2a-shared-core Phase 2a — src/CodeShellManager.Core/, plus a merge of the spike branch (35f8538) Tasks 1–6 of 7 done
    cross-platform Older/parallel line, also contains the spike merge Superseded by the two above

    feat/2a-shared-core is the one that matters: 22 commits, 69 files, +2734/-52, and at time of parking only 2 commits behind main.

    Phase 2a: what's done

    Tasks 1–6 of docs/superpowers/plans/2026-06-15-shared-core-extraction.md (that plan and its design spec at docs/superpowers/specs/2026-06-12-shared-core-extraction-design.md are themselves only on the branch). src/CodeShellManager.Core/ now holds:

    • Models (7) — ShellSession, AppState, SessionGroup, AlertEvent, RecentlyClosedEntry, RunCommandItem, WindowsTerminalProfile
    • Services (21) — SessionManager, StateService, SearchService, ColorService, GitService, AlertDetector, ClaudeSessionService, CommandPresetsService, UpdateService, ImportExportService, SessionRunner, RunInstance, RunCommandTemplatesService, WindowsTerminalProfileService, BuiltInTerminalSchemes, SchemeMapper, CursorShapeMapper, PaddingParser, CommandLineSplitter, plus the new IDispatcher / IToastNotifier host seams
    • Terminal — PseudoTerminal, IPseudoTerminal, OutputIndexer, and the new ITerminalBridge seam
    • ViewModels — both MainViewModel and SessionViewModel, decoupled from the WPF host

    New abstractions introduced to break the WPF dependency: IDispatcher (note: Post is a blocking UI-thread marshal — see b26c493), IToastNotifier, ITerminalBridge. New test coverage: MainViewModelSeamTests.cs.

    Phase 2a: the single remaining task

    Task 7 — verify & merge. Never run. To resume:

    1. Verify CodeShellManager.Core has zero WPF / WinForms / WebView2 references
    2. Confirm it builds standalone against a non-Windows TFM
    3. Full solution build + dotnet test on both test projects
    4. Manual smoke test of the WPF app against Core
    5. Merge back per the plan's merge-commit step

    Resumption risk (the reason this note is detailed)

    Core relocated Models, Services, Terminal, and both ViewModels wholesale. Every bugfix landing on main against those files while this is parked will conflict on rebase. Two consequences to plan for:

    Also parked by extension

    • Phase 2b (full Avalonia port, Windows target) — not started
    • Phases 3–5 (macOS, Linux, CI/CD + packaging) — not started
    • The unresolved Linux GTK web-view presentation bug from the Phase 1 verdict (surface stuck at 200×200). Still needs: retest on a native Linux desktop, test WPE on a distro that ships it, file upstream. No upstream issue was filed — that's an open loose end worth doing independently of this park, since it's the long-lead item.
  4. added
    on holdParked deliberately; not abandoned. See latest comment for resumption state.
    on Aug 7, 2026
  5. AThraen commented on Sep 6, 2026

    @AThraen
    ContributorAuthor

    Status: feat/2a-shared-core exists and is going stale

    Recording this here because it isn't visible from the issue and the branch is easy to lose.

    Phase 2a — extracting the WPF-free core this port depends on — was started and is unmerged:

    origin/feat/2a-shared-core
      22 commits ahead of main, 36 behind
      last commit 2026-06-15 (b26c493)
    

    What's on it:

    Commit
    89da7c1 move WPF-free Services and Terminal types to Core
    a9e4b3f drop dead ColorService WPF members and move to Core
    6ea76ea add ITerminalBridge seam, move SessionViewModel to Core
    1a62b6b decouple MainViewModel from the WPF host, move to Core
    b26c493 docs: IDispatcher.Post is a blocking UI-thread marshal

    That's the "MVVM layer and most services should port largely unchanged" claim from this issue actually cashed out — the seams (ITerminalBridge, IDispatcher) are the part worth not redoing.

    The risk is drift. Since that branch was last touched, main has taken 36 commits, several of them landing directly in the files it moved — SessionViewModel (ApplyShellIntegration, #119), MainViewModel (SaveStateDebounced, #119), PseudoTerminal (HasExited, #115), plus PwshLocator and the new SessionConfigEditor / ShellIntegrationPayload services. Every one of those is a rebase conflict waiting in a file that was moved wholesale.

    Worth deciding soon which way this goes: rebase and land the extraction as its own PR while the conflicts are still tractable, or abandon the branch and redo it later against whatever main looks like then. Leaving it where it is costs more every release.

    The three-pillar strategy in the issue body (Avalonia 11 / Pty.Net / a cross-platform terminal host) is unaffected — no spike has been done on the WebView2 replacement, which remains the open risk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    on holdParked deliberately; not abandoned. See latest comment for resumption state.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions