wox.core/ui/README.md
wox.core/ui is Wox's cross-platform Go UI. Core and UI run in one executable and process. Lifecycle, queries, actions, terminal subscriptions, and every core-to-UI update use typed Go Services/View contracts.
wox.core always starts the embedded Go UI; there is no standalone Wox UI module or runner.
The portable Go layer owns widget layout, focus routing, text editing state, scrolling, Wox protocol DTOs, query behavior, previews, actions, and settings pages. Platform files are deliberately thin and own only the native window/event loop, renderer submission, font measurement, clipboard, file dialogs, external browser dispatch, and IME integration:
Display-list and widget changes must compile unchanged on all three platforms. A platform-specific feature should first expose a small capability on Window; business widgets must not import Win32, AppKit, GTK, or renderer APIs.
System WebView previews keep their public facade on runtime.Window, while runtime/internal/webview owns validation, visibility, navigation, pointer forwarding, and reset/close lifecycle. Platform host adapters only translate that driver contract to WebView2, WKWebView, or WebKitGTK and must not leak native engine types into launcher or widget packages.
Launcher presentation is split into three layers. launcher/component/wox_*.go owns theme-aware Wox controls and their shared interaction/accessibility behavior, with one component per file. launcher/view/ owns pure page composition through immutable props and callbacks; cohesive domains such as preview rendering live in a dedicated subpackage (launcher/view/preview). The launcher package root retains controller state, core requests, native-window lifecycle, and adapters that prepare typed view props; adapters do not construct primitive widget trees. Widget slots are reserved for composing an already separate view or shared form control. View and component packages must not depend on launcher.App.
The widget Host supports incremental retained Stateful elements alongside legacy stateless descriptions. Stateful identity is scoped by widget type and stable key; the Host owns mount, update, build, post-frame work, and disposal ordering, while StateContext schedules frames without moving native work into rendering. FocusNode attachments remain window-specific, TextEditingController retains portable editor state across immutable rebuilds, and keyed ScrollView widgets retain offsets through ScrollController and consume declarative KeepVisible ranges when the target or viewport changes. WoxTextField always mounts retained State and exposes only its committed business value by default; caret, selection, and IME composition stay inside the field unless an advanced stateful owner explicitly supplies a controller. Shared choice overlays own query editing, keyboard highlight, and list scrolling while adapters receive only the committed option. Settings pages, navigation and search, plugin/theme catalogs, runtime row following, title-bar hover, shared forms, form-table pickers/editors, and model-manager lists keep transient interaction state below launcher.App. Modal focus trapping and restoration stay Host-owned, while WoxDialog State can request a specific descendant after its first layout. New stateful controls should use this lifecycle instead of storing interaction state in launcher.App; legacy controls can migrate independently without replacing the complete widget tree at once.
Fine-grained layout invalidation, Boundary decomposition, and repaint-region tuning target the Launcher window. Launcher interactions are continuous and latency-sensitive, so query editing, list and grid result fields, Glance, previews, and action surfaces should invalidate the smallest stable visual owner that actually changed.
Settings and other secondary pages are low-frequency surfaces and do not require per-field incremental rendering. Full-page or full-window refreshes are acceptable there; do not add fine-grained boundaries solely for those pages unless profiling demonstrates a user-visible performance problem. Shared widgets must remain correct in every window, but Launcher is the optimization target.
Settings, account, theme, resource catalogs, and other UI domains use typed services directly. The only production loopback HTTP surface is appcontrol; automation has a separate authenticated endpoint that exists only in wox_automation builds. Production builds register no UI business routes.
The Go process currently supports the Wox query protocol, list and grid results, refinements, completion hints, toolbar messages, actions and action forms, live Glance items and actions, GPU raster/SVG images, text/image/file/list/structured previews, system WebView previews, streaming terminal output with cursor-based history loading and local find, media controls, and a native-GPU chat surface with multiline IME input, streamed snapshots, stop, history, model and skill catalogs, message copy/edit/retry actions, a copyable debug trace, tool-call cards, and ask_user answers. Query rows render multiple plugin and development tails, including the Go UI receive-time marker backed by core's shared query timestamp. Response-local action icon references are restored before shared widgets render them. List and grid result viewports preserve the complete result set, keep keyboard selection visible, expose a portable scrollbar, and leave pointer scrolling independent from selection. Launcher rows, action surfaces, query rounding, and toolbar gutters consume platform-resolved theme geometry. The action panel reuses the shared IME editor for translated filtering and has its own bounded scroll surface. Windows applies native Mica/Acrylic policy and snaps image destinations to physical pixels before high-quality Direct2D sampling. WebView previews share one portable URL/HTML/CSS/User-Agent/cache contract and use WebView2, WKWebView, or runtime-detected WebKitGTK without exposing those engines to query widgets. Query-owned requirement settings and trigger-keyword conflict editing reuse the same portable form engine. Theme color editing is one shared live-preview component mounted by both query previews and the settings route. The core-backed settings window includes general, appearance, hotkeys, network, live runtime-host diagnostics, theme, updates, privacy with a copyable telemetry sample, development diagnostics, plugin management, AI provider/MCP/skill management, local data and backup management, Cloud Sync, a real local usage dashboard, and an About page backed by the running core version; text settings reuse the common IME editor, executable paths use the cross-platform native file picker, and searchable choice overlays handle system font and Glance catalogs. Settings, the shared macOS/Windows/Linux X11 screenshot editor with scrolling capture, and named wox.instance.<session> launchers use the process-local window manager, which tracks create/show/hide/close transitions, gives each launcher an independent session/client/widget host, and broadcasts shared setting changes. Its scrollable rail includes keyboard-first fuzzy search across built-in settings, sections, installed plugins, and plugin-defined settings. Runtime diagnostics expose version, executable, loaded-plugin, install/upgrade, and host-restart state through the existing core protocol. Cloud Sync covers login, registration and legal consent, email verification, password reset/change, encrypted bootstrap/restore, live progress, enable/disable/manual sync, subscription links, device management, and plugin exclusions without platform-specific page code. The configured application font is applied by DirectWrite, CoreText, or Pango through one window-level API. Hotkey recording continues to use core's native cross-platform recorder, while the Go layer provides shared query/tray editors, emoji or structured image values, a core-backed ignored-application picker, and shared dictation/OCR model managers. Plugin and theme pages share installed/store catalogs with install, upgrade, apply, enable, disable, and uninstall actions as appropriate, while the theme page reuses the live GPU preview editor. AI settings reuse the JSON table editor, load provider choices from core, validate transport-specific fields, and support both local skill directories and remote repository cloning. The Data page uses core-owned cross-platform routes for storage migration, backup/restore, shell opening, and logs, while the Go layer owns responsive progress and confirmation state. The plugin editor consumes the translated, platform-filtered SettingDefinitions DTO from core. Shared form components include JSON table add/edit/delete flows with cross-platform directory picking; unsupported specialized table columns are preserved without mutation.
The settings domain state has been split into 13 per-domain settings controllers (about, privacy, usage, update, runtime, data, network, appearance, ai, hotkey, theme, plugin, cloud) plus a settingsSearchController, all with zero back-dependency on App. Each controller owns its slice of settings state and exposes a Snapshot() for the view layer; settingsSnapshot is now a nested struct of per-domain snapshots rather than a flat 90-field mirror. App retains only window-level settings state (tab, row, note, saving) and delegates the settingsData payload to generalSettingsController's sharedEditState. Cross-domain reads (e.g. query domain reading UsePinYin) still go through App getters, and the settings search controller aggregates matches from each controller via a Searchable interface.
Windows builds embed the checked-in resource/others/webview/WebView2Loader.dll, extract it under the Wox data directory, and set WOX_WEBVIEW2_LOADER_PATH before the first WebView opens. Linux builds do not require WebKitGTK headers, but WebView previews need a WebKitGTK 4.1 or 4.0 runtime installed.
The July 2026 M3 Max investigation established a release-build reference for the complete Wox process, not an isolated renderer demo. The original approximately 300 MB report motivated replacing the CAMetalLayer renderer with CoreGraphics drawing into a bounded IOSurface pool. After that renderer replacement, a controlled follow-up used the same real Wox data and process lifecycle for both sides:
| Release checkpoint | Before the follow-up | After the follow-up |
|---|---|---|
| Hidden physical footprint | 102.9 MB | 80-84 MB |
| Settled visible query | 123.1-130 MB | 90.9 MB |
| Visible peak during the final run | Not recorded | 94 MB |
| Visible IOSurface memory | Approximately 21 MB | 14 MB |
These numbers are comparison points rather than a CI budget because macOS, display scale, installed applications, and user data affect the absolute footprint.
The memory reduction depends on four decisions:
IOSurfaceIsInUse is false; stale-size surfaces are removed first, and retries are bounded.system_profiler discovery contributes application paths to the shared application cache instead of reparsing every returned bundle. Localized names are read from Info.plist, InfoPlist.loctable, root InfoPlist.strings, and .lproj strings without constructing one NSBundle per application. Cache freshness includes those files, the resolved icon source, and the preferred macOS languages. On the reference machine this reduced the post-index native heap from approximately 256 NSBundle and 258 CFBundle instances to 8 and 12.GOGC=50 when the environment does not provide GOGC; this heap-growth policy is shared across platforms, while the reference measurements in this section are macOS-only. The override remains available for diagnosis. In the final interleaved macOS release comparison, the default settled at 90.9 MB versus 96.8 MB with GOGC=100; query p95 was 6 ms versus 7 ms, and GC pause p95 remained approximately 0.5 ms.Do not replace the idle trim with a hard two-surface allocation cap. Both existing surfaces can still be owned by Core Animation while the next frame is encoded; refusing a third surface can drop the final frame. Trimming to one visible surface also saves only about 7 MB at the cost of allocating another full-size surface on the next refresh, which turns a stable footprint into allocation churn and input-visible stalls.
For later comparisons, stop every other Wox instance and sample the same release-process PID throughout the run. Use real Wox data, warm up application and image caches, replay deterministic query blocks, wait for the same visible or hidden lifecycle checkpoint, and record at least three samples. PhysicalFootprintMB or footprint --pid <PID> --noCategories is the primary macOS process metric; pair it with vmmap <PID> -summary, heap -s -H <PID>, and Go heap profiles when attribution is needed. The retained workload and sampler should be reused. Do not compare an Activity Monitor spike, a debug build, an isolated layer demo, or a different PID directly with this release baseline.