zserge/lorca
Build cross-platform modern desktop apps in Go + HTML5
Stale — last commit 2y ago
weakest axislast commit was 2y ago; no tests detected
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓27+ active contributors
- ✓Distributed ownership (top contributor 39% of recent commits)
- ✓MIT licensed
Show all 6 evidence items →Show less
- ✓CI configured
- ⚠Stale — last commit 2y ago
- ⚠No test directory detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: 1 commit in the last 365 days
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/zserge/lorca)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/zserge/lorca on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: zserge/lorca
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/zserge/lorca shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Stale — last commit 2y ago
- 27+ active contributors
- Distributed ownership (top contributor 39% of recent commits)
- MIT licensed
- CI configured
- ⚠ Stale — last commit 2y ago
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live zserge/lorca
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/zserge/lorca.
What it runs against: a local clone of zserge/lorca — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in zserge/lorca | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 656 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of zserge/lorca. If you don't
# have one yet, run these first:
#
# git clone https://github.com/zserge/lorca.git
# cd lorca
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of zserge/lorca and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "zserge/lorca(\\.git)?\\b" \\
&& ok "origin remote is zserge/lorca" \\
|| miss "origin remote is not zserge/lorca (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "ui.go" \\
&& ok "ui.go" \\
|| miss "missing critical file: ui.go"
test -f "chrome.go" \\
&& ok "chrome.go" \\
|| miss "missing critical file: chrome.go"
test -f "locate.go" \\
&& ok "locate.go" \\
|| miss "missing critical file: locate.go"
test -f "value.go" \\
&& ok "value.go" \\
|| miss "missing critical file: value.go"
test -f "export.go" \\
&& ok "export.go" \\
|| miss "missing critical file: export.go"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 656 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~626d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/zserge/lorca"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Lorca is a pure-Go library that builds cross-platform desktop applications using Chrome as the UI renderer, without bundling Chrome into the app. It establishes a DevTools Protocol connection to an existing Chrome/Chromium browser (≥70), enabling bidirectional Go↔JavaScript communication for function binding and arbitrary code execution, resulting in lightweight 5-10MB executables instead of Electron-sized bundles. Single-package design: core functionality split into chrome.go (DevTools Protocol client), ui.go (main UI API binding/eval), value.go (JS↔Go type conversion), locate.go (Chrome executable discovery), and messagebox.go (platform-specific dialogs). The examples/ directory contains three runnable demos (counter, hello, stopwatch) with per-OS build scripts and assets.
👥Who it's for
Go developers building desktop applications who want to leverage HTML/CSS for UI design without the overhead of Electron, and who have Chrome/Chromium already available on target systems. They value small binary sizes and want seamless interop between Go goroutines and JavaScript promises.
🌱Maturity & risk
Actively maintained but limited scope. The repo shows a single Go maintainer (zserge) with CI/CD setup (.github/workflows/ci.yml) and test files (chrome_test.go, ui_test.go, value_test.go), but the last commit age is not visible from provided data. The project is feature-complete for its specific niche (Chrome automation via Go) and used in production scenarios, making it production-ready for apps targeting systems with Chrome already installed.
Minimal dependency risk: only depends on golang.org/x/net (0 cgo required). However, the library is tightly coupled to Chrome's DevTools Protocol version and Chrome/Chromium availability—if Chrome is uninstalled or versioning changes dramatically, apps break silently. Single maintainer with no visible GitHub organization backing increases bus-factor risk. No explicit versioning strategy visible (go.mod shows no release tags in data).
Active areas of work
No specific recent activity visible from the provided data. The file structure and examples appear stable; the repo seems in maintenance mode rather than active development.
🚀Get running
git clone https://github.com/zserge/lorca.git
cd lorca/examples/counter
go get
go run ./
Daily commands:
Per-OS: examples/counter/build-linux.sh, examples/counter/build-macos.sh, examples/counter/build-windows.bat. Or directly: go run examples/counter/main.go (requires Chrome installed and discoverable via locate.go).
🗺️Map of the codebase
ui.go— Core API exposing the UI struct and methods for bidirectional Go-JavaScript communication—the primary entry point for all developer interactionschrome.go— Establishes and manages the Chrome/Chromium process and DevTools Protocol connection—fundamental to the entire app lifecyclelocate.go— Detects and locates the Chrome/Chromium binary across platforms—critical for cross-platform reliability and startupvalue.go— Implements JavaScript value marshaling and type conversion between Go and JavaScript—essential for correct data exchangeexport.go— Handles Go function/method exposure to the JavaScript side—key mechanism for calling Go from the UIexamples/counter/main.go— Reference implementation demonstrating the full lifecycle: window creation, event binding, and data binding patterns
🧩Components & responsibilities
- locate (locate.go) (os.Exec, os.LookupEnv, Windows Registry API) — Discover Chrome binary on Windows, macOS, Linux via registry, environment, standard paths
- Failure mode: Returns error if Chrome not found; app cannot start
- chrome (chrome.go) (os/exec, net.Dial, websocket) — Spawn Chrome process with debugging flags, establish and manage DevTools WebSocket connection
- Failure mode: Dial timeout or handshake failure; WebSocket connection lost → app hangs or crashes
- ui (ui.go) (Context, channels, reflect) — Public API: create windows, bind Go functions, load HTML, evaluate JavaScript, close gracefully
- Failure mode: Eval/Call timeout or JSON marshaling error → runtime panic or incorrect data
- export (export.go) (reflect package) — Inspect Go function signatures, inject into browser window scope, handle calls from JS with arg/return marshaling
- Failure mode: Unsupported argument type or nil panic → JS call hangs or crashes Go
- value (value.go) (json.Marshal/Unmarshal, reflect) — Bidirectional type conversion between Go (int, string, map, slice) and JavaScript (number, string, object, array)
- Failure mode: Type assertion error or circular reference → panic or data corruption
- messagebox (messagebox.go, messagebox_windows.go) — Display native modal
🛠️How to make changes
Expose a new Go function to JavaScript
- Define a Go function or method in your application code (e.g., main.go) (
examples/counter/main.go) - Call ui.Bind("functionName", functionPtr) before ui.Load() to register it with export.go (
export.go) - Call the function from JavaScript as window.functionName(...) in your HTML/JS (
examples/counter/www/index.html)
Evaluate JavaScript and get results back in Go
- Use ui.Eval(jsCode) to execute arbitrary JavaScript code; returns a Value (
ui.go) - Call Value.String(), Value.Int(), Value.Float64(), or Value.Bool() to extract the result (
value.go) - For complex objects, use Value.Call() or iterate with Value.Get(key) to unwrap nested data (
value.go)
Add cross-platform native dialogs
- Import messagebox and call messagebox.Alert(), Confirm(), or Prompt() (
messagebox.go) - On Windows, platform-specific logic is in messagebox_windows.go; other platforms use fallback (
messagebox_windows.go) - Call the dialog function from your Go code after ui.Load() returns (
examples/counter/main.go)
Build and bundle a cross-platform desktop app
- Create a main.go with ui.New() and ui.Load(url or html) (
examples/counter/main.go) - Place HTML assets in a www/ directory (or embed them with go:embed in Go 1.16+) (
examples/counter/www/index.html) - Use platform-specific build scripts (build-linux.sh, build-macos.sh, build-windows.bat) to compile (
examples/counter/build-linux.sh)
🔧Why these technologies
- Chrome/Chromium as UI layer — Leverages installed system browser instead of bundling (5–10MB vs 100MB+), enables modern HTML5/CSS without custom UI framework
- DevTools Protocol (WebSocket) — Standard, language-agnostic interface for programmatic browser control; allows Go to execute JS and receive results without cgo
- Go reflection (export.go) — Enables automatic binding of arbitrary Go functions/methods to JavaScript without boilerplate or code generation
- Pure Go (no cgo) — Simplifies compilation, cross-compilation, and distribution; avoids platform-specific C dependencies
⚖️Trade-offs already made
-
Rely on system Chrome instead of bundling
- Why: Dramatically reduces app size and avoids bundling complexity
- Consequence: End-user must have Chrome/Chromium installed; app will not run on systems without it
-
DevTools Protocol over direct C bindings
- Why: Avoids cgo; enables pure Go and simpler cross-compilation
- Consequence: Slight performance overhead vs. direct API calls; requires spawning full Chrome process even for small UIs
-
Reflect-based function export with minimal type introspection
- Why: Maximizes simplicity and developer experience for common cases
- Consequence: Limited support for complex types; functions must have simple signatures (basic types, error return)
🚫Non-goals (don't propose these)
- Not a general-purpose web framework; focuses on desktop apps only
- Does not provide native component library; UI must be authored in HTML/CSS
- Does not handle native system integration beyond basic dialogs and Chrome location detection
- Not designed for server-side or headless use; requires a display and running Chrome instance
🪤Traps & gotchas
Chrome must be installed and discoverable—locate.go has hardcoded paths per OS; if user has Chrome in an unusual location or only Chromium available, New() will fail with no helpful error. DevTools Protocol versioning is implicit (tied to Chrome version); breaking CDP changes in Chrome ≥90 may silently break binding/eval. No graceful fallback if Chrome is headless-only or sandboxed. The Eval() result parsing is synchronous but the JS execution is async—timing bugs possible if you assume result immediacy.
🏗️Architecture
💡Concepts to learn
- Chrome DevTools Protocol (CDP) — Lorca's entire communication layer uses CDP over WebSocket to control Chrome and inject JavaScript; understanding message framing and method calls is essential for debugging and extending the library
- Go goroutines and channels — Lorca's async model relies on goroutines to handle WebSocket reads/writes concurrently while Eval() and Bind() block waiting for responses; deadlock bugs are common if you misunderstand channel ordering
- JavaScript Promise / async-await — Bound Go functions are wrapped as Promises in JavaScript; Eval() calls that return promises require .then() or await in JS and proper .Float()/.Int() chaining in Go
- Type marshaling / reflection — value.go uses Go reflection to convert function return types to JSON and parse JS responses back to Go types; silent type conversion bugs are possible if you return unexpected types from Bind() functions
- Data URLs (RFC 2397) — Lorca can load HTML UI directly as data:// URLs instead of localhost web servers, enabling truly standalone binaries without external files; important for packaging cross-platform
- Process discovery and PATH resolution — locate.go must find the Chrome executable across Windows, macOS, and Linux with different installation conventions; understanding OS-specific lookup patterns is key if you customize for unusual Chrome locations
🔗Related repos
zserge/webview— Same author's alternative library using native WebView (WKWebKit on macOS, WebKit2GTK on Linux, MSHTML on Windows) instead of Chrome—lower resource footprint, more window control, smaller API surfacewailsapp/wails— Go desktop framework with similar bidirectional Go↔JavaScript binding but using native WebViews; active development, better window control, larger communityasticode/go-astilectron— Go wrapper around Electron for desktop apps; heavier (bundles Chromium) but provides full browser API and native menus, trade-off vs. Lorca's minimal approachgolang/go— The language runtime and standard library that Lorca depends on; important for understanding goroutine and cgo-free design choiceschromium/chromium— The DevTools Protocol client in Lorca communicates with Chromium's debugger; understanding CDP message format is key to extending Lorca functionality
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add cross-platform Chrome locator tests for locate.go
locate.go and locate_test.go handle critical functionality for finding Chrome/Chromium across Windows, macOS, and Linux. The current test file likely has incomplete coverage for edge cases (missing installations, custom paths, different browser variants). This PR would add comprehensive tests for all OS-specific paths and fallback scenarios, improving reliability across platforms.
- [ ] Review locate_test.go for existing test coverage gaps
- [ ] Add tests for missing Chrome/Chromium variants (Google Chrome, Chromium, Brave, Edge, etc.) on each OS
- [ ] Add tests for edge cases: missing installations, permission errors, symlinks
- [ ] Add tests for PATH environment variable fallbacks
- [ ] Ensure tests cover all code paths in locate.go for Windows, macOS, and Linux detection logic
Expand ui_test.go with integration tests for UI value binding
value.go handles JS-to-Go value marshaling and value_test.go exists, but ui_test.go appears minimal. This PR should add integration tests verifying the bidirectional communication between Go and JavaScript (calling Go functions from UI, updating UI from Go). This is core to lorca's value proposition.
- [ ] Review current ui_test.go and value_test.go coverage
- [ ] Add tests for Eval() executing JavaScript and returning values
- [ ] Add tests for Bind() exposing Go functions to JavaScript with various argument types
- [ ] Add tests for updating DOM from Go and verifying JavaScript state changes
- [ ] Add tests for error handling when JavaScript execution fails
- [ ] Consider adding timeout/cancellation context tests
Add GitHub Actions workflow for multi-OS binary builds in .github/workflows/
The ci.yml workflow exists but the repo lacks a separate build workflow that compiles and tests the examples (counter, hello, stopwatch) across Windows, macOS, and Linux. This would catch platform-specific compilation issues early and verify the example build scripts actually work.
- [ ] Create .github/workflows/build-examples.yml
- [ ] Add matrix job for Ubuntu (Linux), macOS, and Windows runners
- [ ] On Linux/macOS: run examples/counter/build-*.sh scripts and verify binary creation
- [ ] On Windows: run examples/counter/build-windows.bat
- [ ] Add steps to verify hello and stopwatch examples compile without errors
- [ ] Cache Go modules to speed up builds
- [ ] Add artifact upload for successful binaries (optional, for PR validation)
🌿Good first issues
- Add error context to locate.go: currently returns nil if Chrome not found; wrap with os.ErrNotExist and include search paths in error message so users know what went wrong. Files to touch: locate.go, locate_test.go.
- Expand messagebox.go to support macOS (currently only Windows-specific messagebox_windows.go exists); implement NSAlert bridging via CGO or system calls. Test with examples/counter.
- Add timeout parameter to ui.New(): currently no timeout for Chrome startup—add a context.Context or time.Duration param, propagate to chrome.go's WebSocket dial, and document in README. Files: ui.go, chrome.go, ui_test.go.
⭐Top contributors
Click to expand
Top contributors
- [@Sergii Zaitsev](https://github.com/Sergii Zaitsev) — 39 commits
- @zserge — 29 commits
- @velut — 4 commits
- @szzhiyang — 3 commits
- @yaodingyd — 2 commits
📝Recent commits
Click to expand
Recent commits
36a77ca— Fixes Updated chrome - now ui.New fails to start #184 (#185) (Justin Vieira)a990beb— Locate MS Edge on 32-bit Windows (#154) (LANDMVRK)204197d— Add Microsoft Edge support on macOS (#147) (jsfaint)1006348— Use Go 1.16 embed (#140) (Srinivasa314)4d6397d— feat: added RawValue interface (#127) (nrwiersma)4161d59— README to give correct instructions to run counter (#130) (seanburlington)fd002b0— Support for Edge Chromium (#137) (chenilim)a3e4339— Added Intellij IDEA configuration to .gitignore (#93) (slavakrampetz)50f70a5— Update README.md (zserge)115f378— Use native window titlebar on Windows 10 (#90) (szzhiyang)
🔒Security observations
The Lorca library presents a moderate security risk primarily due to an outdated dependency (golang.org/x/net from Feb 2020) with multiple known CVEs. While the core library design is relatively clean, there are concerns around input validation between Go and JavaScript, Chrome process spawning configuration, and lack of documented security practices for applications built with this framework. The project would benefit from: (1) immediate dependency updates, (2) explicit input validation mechanisms, (3) security hardening documentation, and (4) secure-by-default Chrome configuration options.
- High · Outdated Dependency with Known Vulnerabilities —
go.mod. The golang.org/x/net dependency is pinned to v0.0.0-20200222125558-5a598a2470a0, which is from February 2020. This version is significantly outdated and likely contains multiple known vulnerabilities including issues in TLS handling, HTTP/2, and DNS resolution that have been patched in subsequent releases. Fix: Update golang.org/x/net to the latest stable version (v0.19.0 or higher). Run 'go get -u golang.org/x/net' and test thoroughly with the application. - Medium · Insufficient Input Validation in Browser Communication —
chrome.go, ui.go. The codebase establishes communication between Go and a Chrome browser instance. Without explicit evidence of input validation and sanitization in the message handling (chrome.go, ui.go), there is potential for code injection attacks if user input or external data is passed between the Go backend and JavaScript frontend without proper validation. Fix: Implement strict input validation and sanitization for all data passed between Go and the JavaScript frontend. Use parameterized approaches for dynamic content and avoid direct string concatenation when building UI commands. - Medium · Unrestricted Chrome Process Spawning —
locate.go, chrome.go. The locate.go file likely contains logic to spawn and connect to Chrome processes. Without visible restrictions on Chrome flags or profile isolation, this could potentially allow execution with overly permissive settings or enable access to sensitive user data through Chrome profiles. Fix: Implement strict Chrome launch flags including --no-first-run, --no-default-browser-check, and use isolated user data directories. Consider using --disable-extensions and other security-hardening flags. - Low · Missing Security Headers Documentation —
examples/counter/www/index.html, examples/hello/main.go. No evidence of Content-Security-Policy (CSP), X-Frame-Options, or other security headers being set in the HTML UI layer. Applications built with Lorca may be vulnerable to XSS attacks if developers don't implement these protections. Fix: Document security best practices for Lorca applications, including mandatory CSP headers, X-Content-Type-Options, and X-Frame-Options. Provide example implementations in the examples directory. - Low · No Visible HTTPS/TLS Configuration —
chrome.go, ui.go. The codebase does not show explicit TLS/HTTPS configuration for the local communication channel between Go and Chrome. If communication is unencrypted over network sockets, local privilege escalation could allow eavesdropping. Fix: Ensure all Go-Chrome communication uses secure channels. If using WebSocket or HTTP, enforce HTTPS/WSS and verify certificate pinning where applicable. Use localhost binding only.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.