Help us shape the future of Go! Take the 2026 Go Developer Survey

nativeui

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 32 Imported by: 0

README

Native desktop UI (MyGo)

pando desktop runs the API server in process and shows this native UI on the main goroutine (the only desktop front end). Plan: .kb/pando/plans/native_desktop_mygo.md.

Toolkit: MyGo v0.7.1, pinned exactly (pure Go, GPU rendered, immediate mode with keyed retained state). It needs Go

= 1.27.3: GOTOOLCHAIN=go1.27.3 ~/.g/go/bin/go .... MyGo docs: $(go env GOMODCACHE)/github.com/egoist/mygo@v0.7.1/docs/ui/*.md.

Packages

Package Role Imports MyGo
client/ Pando HTTP API client: token, local CA TLS, REST, SSE parser, chat stream with reattach, event subscriptions, DTOs no
i18n/ Web UI catalogs (copied from web-ui/src/i18n/locales, a test keeps them in sync) and T(key, "name", value...) no
kit/ Theme (web design tokens -> ui.Theme), lucide icon subset, base components yes
shell/ mygo.App, main window, application menu (Edit roles), later tray/notifications/dock yes
app/ Context, area registry, router, window layout (sidebar + header + content), placeholders yes
app/<area>/ One package per area: chat (P0 MVP), then settings, files, editor, terminal, ... yes
natives/ libghostty-vt embedded per platform (go:embed, make natives-<goos>-<goarch>; signed on darwin), extracted to <user cache>/pando/natives/ and exported as $MYGO_GHOSTTY_VT by Run before the terminal loads no
nativeui.go Run(Options): client + context + root view + shell; blank-imports the area packages yes

Adding an area (parallel follow-up work)

  1. Create internal/nativeui/app/<area>/ with a type implementing app.View (Build(c *ui.Context, r *ui.Route)).

  2. Register it from init() with the same ID as its placeholder in app/placeholders.go (a real registration always replaces a placeholder):

    func init() {
        app.Register(app.Area{
            ID: "settings", Path: "/settings", Patterns: []string{"/settings", "/settings/{section}"},
            TitleKey: "nav.settings", Icon: kit.IconSettings, Section: app.SectionBottom,
            New: func(a *app.Context) app.View { return newSettings(a) },
        })
    }
    
  3. Blank-import the package in nativeui.go.

  4. Optional interfaces on the view: app.Titled (header title from state), app.HeaderActions (buttons in the header bar), app.Closer (stop goroutines at shutdown).

Area IDs and paths: chat /chat (+/chat/s/{id}), simple-chat /chat/simple, projects, editor /editor/{path...}, terminal, orchestrator, design /design/{id...}, logs, agentvcs /snapshots/{id...}, evaluator, instances, extensions, settings /settings/{section...}, setup (hidden).

Routing

The app's routers show pages at once (ui.TransitionNone, as the Web UI). An area view is one stateful object: MyGo's sliding transition builds the page going away too, which built the view twice per frame ("a ListState shown by two Lists"); Root.leavingSameArea keeps that safe if a router ever slides again, and a fade left pages transparent while the window got no frames (locked macOS screen). Areas keep their state in sync with the route: the chat pushes /chat/s/{id} on session clicks and follows Back/deep links (app/chat/shellwire.go); agentvcs follows /snapshots/{id...} in place.

Rules

  • UI thread only. View state is touched only in Build, or from goroutines through a.Update(func(){...}) (app.Context.Update -> mygo.Window.Update). Never mutate state from a goroutine directly.
  • Streams feed the UI in batches. Use app.NewBatch(a.Update, apply): many Push calls from a goroutine become one UI update per frame (the chat does this for token deltas).
  • Background work goes through a.Go(func(ctx){...}): it recovers panics (toast + log) and its ctx ends at shutdown. Use a.Client for every server call; add typed wrappers to client/ (no MyGo there) and use Client.Do/Get/Post/... meanwhile.
  • Look and feel comes from kit: kit.Current() returns the tokens (Bg, BgShell, BgCard, Fg, FgMuted, Accent, Border, radii, text sizes) of the frame; MyGo widgets already follow the mapped ui.Theme. Prefer kit components (Button, IconButton, Card, Badge, Input, Switch, Select, Dialog, SettingsSection/Row, NavItem, CodeBlock, EmptyState, Spinner, ToastViewport) and add new shared ones to kit.
  • Icons are lucide SVGs embedded in kit/icons; add a file there and a constant in kit/icons.go. Source used: lucide 1830-icon set at ~/.cargo/registry/src/index.crates.io-*/gpui-kit-assets-0.7.2/assets/icons.
  • Text goes through a.T("web.ui.key") so the 7 Web UI languages work; count selects _one/_other plurals.
  • Toasts from goroutines: a.Toasts.Error(title, detail) (queued, flushed in the root view).
  • Keys. Elements built in loops that keep state need c.Key(id); lists use ui.ListState.Key. Do not set ListState.Selected when rows paint their own selection (it paints the accent colour).
  • Tests. Headless with ui.NewTester(view, w, h) (Click, Type, Key, Compose, Scroll, HasText); drive goroutine updates with app.QueueDispatcher (a.SetDispatcher(q), then q.Drain(); tt.Frame()). Server behaviour with httptest (see client/client_test.go, app/chat/chat_test.go). Recorded SSE/JSON fixtures live in client/testdata (from the GPUI client, Pando v1.3.0).

Protocol lessons (ported from pando-assistant/crates/pando-client)

  • Token: GET /api/v1/token (loopback servers hand it out), sent as X-Pando-Token, with X-Pando-Client: desktop; on 401 the client fetches a new token once (single-flight) and retries.
  • https servers: the local CA ~/.config/pando/tls/ca.crt is trusted in addition to the system roots.
  • POST /api/v1/chat/stream body closing without done while the run is still active (/sessions/{id}/pending) => emit reattach and follow GET /sessions/{id}/stream, which replays the run from its start: discard the partial turn on reattach.
  • A stream ending without done and no content (slash commands, runs already over) => reload history.
  • Steer 409 not_busy => send the text as a normal prompt.
  • PUT /api/v1/models/active must carry sessionId from a chat, otherwise it rewrites the persisted coder model.
  • Never reload history while this client streams its own run; tag streams so events of superseded streams are dropped.
  • Runs started elsewhere: /sessions/events only reports a run once it ends, so the open session is also polled (/pending, 4 s) and attached when running, or reloaded when run_seq grew (run_seq protocol).

Development aids

  • PANDO_NATIVE_CAPTURE=<dir> + PANDO_NATIVE_CAPTURE_AT=6,12 write <dir>/capture-<n>s.png with MyGo's renderer (works where compositor screenshots are refused).
  • PANDO_NATIVE_PATH=/chat/s/<id> opens a route at start.
  • PANDO_NATIVE_SIZE=2400x900 sets the initial window size (wide-window checks).
  • PANDO_NATIVE_CAPTURE_PATHS=/chat,/logs,/settings/appearance (with PANDO_NATIVE_CAPTURE) visits each route in turn and writes capture-<route slug>.png (PANDO_NATIVE_CAPTURE_STEP seconds apart, default 3): one launch screenshots every area.
  • PANDO_NATIVE_SPIKE_MESSAGES=1000 opens a synthetic transcript.
  • MYGO_FRAME_STATS=all logs frame timings (they land in Pando's log store: GET /api/v1/logs).
  • PANDO_NATIVE_IT_URL=https://localhost:8765 go test -run TestRealServerOwnRun ./internal/nativeui/app/chat -v drives the chat headlessly against a real server (PANDO_NATIVE_IT_CANCEL=1 stops mid-run).

Window shell (P1-shell)

  • shell.Controller (given to shell.Options.OnReady; app.Host is the interface the app sees): tray icon (Show, Settings, open project workspaces, Quit), hide-to-tray on close/minimise when desktop_tray_on_minimize_close is on and a tray icon is live (no libayatana on Linux -> normal behaviour), explicit quit always quits, macOS Dock click restores, OS notifications, window focus state.
  • Single instance: nativeui.AcquireSingleInstance(cwd) (called first in cmd/desktop.go) is keyed by working directory so a project window (pando desktop --cwd X) is a separate instance. PANDO_DESKTOP_MULTI=1 disables the lock (use it when several agents/tests run pando desktop from the same directory). A second launch with --simple shows the simple chat.
  • app.Context.StartServices(host) (after the window exists): UI prefs (/api/v1/ui/preferences: theme family/mode/accent, language, sidebar, chatMode), the tray setting followed live via /config/events, notification relay (/notifications/stream, only when unfocused). Persist with a.SetTheme, a.SetLanguage, a.SetSidebarCollapsed, a.ToggleSimpleChat.
  • Extension slots (all in app/shellstate.go): RegisterHeaderSlot (persona selector: ID persona, order 10), RegisterOverlay, RegisterBanner, RegisterCommand (quick menu), Shell().NextModel / Shell().ModelChanged (model switcher <-> chat), Shell().FocusProject. Overlays live in app/overlays (quick menu, model switcher, config-init banner, update notice). RegisterStatusItem adds to the status strip under the main tab (the extensions "status-bar" slot uses it; the strip only shows when an item is visible). Quick menu commands may set TitleKey.
  • Chat wiring (app/chat/shellwire.go): persona selector in the header slot persona (composer chip only in project workspaces), Shell().NextModel sent with a new chat's first prompt, Shell().ModelChanged chained, Shift+Tab toggles auto-approve, the design draft (TakeChatDraft) fills the composer.
  • Theme at start: desktop UI preferences, else the backend theme field (app.ParseThemeID, legacy families mapped); Settings > Appearance saves both and persists the accent in the preferences.
  • Global keys (app/chrome.go): Cmd/Ctrl+K or +P quick menu, +O model switcher, +B sidebar, +Shift+L theme, Ctrl+Alt+1..9 / Left / Right / W project tabs. Errors from background calls: a.ReportError(title, err) (401 gets a clear, de-duplicated toast).

Documentation

Overview

Package nativeui is the entry point of the native desktop UI (MyGo): Run connects the API client, builds the app context and the root view with every registered area, and runs the window on the main goroutine.

Layout of the tree (see README.md):

client/  Pando HTTP API client (no MyGo)
i18n/    Web UI catalogs + T()
kit/     theme, icons, base components
shell/   MyGo app, window, menus
app/     Context, area registry, router, layout
app/<area>/  one package per area (chat, settings, ...)

Index

Constants

This section is empty.

Variables

View Source
var ErrNoDisplay = shell.ErrNoDisplay

ErrNoDisplay is returned by Run when no display server is reachable.

Functions

func AcquireSingleInstance

func AcquireSingleInstance(key string) bool

AcquireSingleInstance makes `pando desktop` focus the window of a running native desktop instead of starting a second one. key is the working directory: different directories (a project's own window, spawned with `pando desktop --cwd`) are different instances. It returns false after handing the command line to the running instance; call it before any server or database starts. PANDO_DESKTOP_MULTI=1 disables the lock.

func Run

func Run(o Options) error

Run shows the native desktop and blocks until it quits. It must be called from the main goroutine.

Types

type Options

type Options struct {
	// Ctx ends the app when canceled (signals, server shutdown).
	Ctx context.Context
	// BaseURL of the Pando API server.
	BaseURL string
	// Token is the API token when known (in-process server); otherwise it
	// is fetched from /api/v1/token.
	Token string
	// CWD is the working directory the server runs in (window title).
	CWD string
	// Language selects the UI language ("" uses $LANG, falling back to en).
	Language string
	// Theme selects the theme (zero value: Pando following the system).
	Theme kit.Settings
	// Path is the route opened first (default /chat).
	Path string
	// Simple opens the simple chat (`pando desktop --simple`).
	Simple bool
	// Logger receives UI logs (default slog.Default()).
	Logger *slog.Logger
}

Options configures Run.

Directories

Path Synopsis
app
Package app is the core of the native desktop UI: the app-wide Context handed to every view, the registry of areas (chat, settings, files, ...), the router and the window layout (sidebar, header, content).
Package app is the core of the native desktop UI: the app-wide Context handed to every view, the registry of areas (chat, settings, files, ...), the router and the window layout (sidebar, header, content).
agentvcs
Package agentvcs is the Agent VCS area (route /snapshots, as in the Web UI): the sessions that have agent commits, their commit log, the files each commit changed with a side-by-side diff viewer (MyGo ui/editor DiffView), revert of a commit or of single files, and the named project snapshots.
Package agentvcs is the Agent VCS area (route /snapshots, as in the Web UI): the sessions that have agent commits, their commit log, the files each commit changed with a side-by-side diff viewer (MyGo ui/editor DiffView), revert of a commit or of single files, and the named project snapshots.
chat
Package chat is the chat area of the native desktop: the sessions list, session history and streaming runs (P0 MVP; P1 brings parity with the Web UI: tool cards, diffs, questions, plan/goal, model picker, slash and @).
Package chat is the chat area of the native desktop: the sessions list, session history and streaming runs (P0 MVP; P1 brings parity with the Web UI: tool cards, diffs, questions, plan/goal, model picker, slash and @).
design
Package design is the Design Studio area of the native desktop: the artifact and template galleries at /design and one artifact's Studio at /design/{id} (web-ui components/design + designStore).
Package design is the Design Studio area of the native desktop: the artifact and template galleries at /design and one artifact's Studio at /design/{id} (web-ui components/design + designStore).
editor
Package editor is the code editor area of the native desktop: a file explorer, open-file tabs, a code editor (MyGo ui/editor, with find bar, folding, multi-cursor and syntax highlighting), image/PDF viewers and a status bar.
Package editor is the code editor area of the native desktop: a file explorer, open-file tabs, a code editor (MyGo ui/editor, with find bar, folding, multi-cursor and syntax highlighting), image/PDF viewers and a status bar.
evaluator
Package evaluator is the self-improvement area: metrics cards, the daily evaluations chart, the UCB ranking of prompt variants, learned skills with approve/reject, the evaluated sessions with their score explanation and the doctor banner.
Package evaluator is the self-improvement area: metrics cards, the daily evaluations chart, the UCB ranking of prompt variants, learned skills with approve/reject, the evaluated sessions with their score explanation and the doctor banner.
extensions
Package extensions is the extension UI of the native desktop: the sidebar page at /ext/{panelId} and the slots (sidebar, settings, chat-side, status-bar) other areas embed.
Package extensions is the extension UI of the native desktop: the sidebar page at /ext/{panelId} and the slots (sidebar, settings, chat-side, status-bar) other areas embed.
instances
Package instances is the instances area: the Pando instances running on this machine grouped by project path, and for the selected one its sessions with the conversation history, the live event stream (/instances/{id}/sessions/{sid}/stream), a message box and cancel.
Package instances is the instances area: the Pando instances running on this machine grouped by project path, and for the selected one its sessions with the conversation history, the live event stream (/instances/{id}/sessions/{sid}/stream), a message box and cancel.
logs
Package logs is the native Logs area: the server log buffer as a table with level and text filters, auto-scroll and a detail panel.
Package logs is the native Logs area: the server log buffer as a table with level and text filters, auto-scroll and a detail panel.
orchestrator
Package orchestrator is the native Orchestrator area: Mesnada tasks (list, detail, create, cancel, delete), delegation metrics and the CronJobs panel.
Package orchestrator is the native Orchestrator area: Mesnada tasks (list, detail, create, cancel, delete), delegation metrics and the CronJobs panel.
overlays
Package overlays holds the window-level layers of the native desktop that the Web UI draws over its layout: the quick menu (command palette), the model switcher, the config-init banner and the update notice.
Package overlays holds the window-level layers of the native desktop that the Web UI draws over its layout: the quick menu (command palette), the model switcher, the config-init banner and the update notice.
projects
Package projects is the native Projects area and the project workspaces: the registry of projects (add, rename, stop, delete, init) and the tabs that open a project's child instance inside the shell.
Package projects is the native Projects area and the project workspaces: the registry of projects (add, rename, stop, delete, init) and the tabs that open a project's child instance inside the shell.
settings
Package settings is the settings area of the native desktop: the 23 categories of the Web UI settings (web-ui/src/components/settings), their unsaved-changes guard and the "?section=" deep link.
Package settings is the settings area of the native desktop: the 23 categories of the Web UI settings (web-ui/src/components/settings), their unsaved-changes guard and the "?section=" deep link.
setup
Package setup is the first-run setup assistant of the native desktop (the Web UI's SetupWizard): scope, provider, models, Remembrances and a summary.
Package setup is the first-run setup assistant of the native desktop (the Web UI's SetupWizard): scope, provider, models, Remembrances and a summary.
terminal
Package terminal is the Terminal area of the native desktop: tabs of interactive shells running in real PTYs on the server, shown by MyGo's terminal plugin (libghostty-vt, loaded at run time without cgo) and fed by the WebSocket PTY endpoint /api/v1/terminal/pty, the same one the Web UI's xterm.js pane uses (binary frames carry I/O, JSON text frames carry ready/exit/resize/close).
Package terminal is the Terminal area of the native desktop: tabs of interactive shells running in real PTYs on the server, shown by MyGo's terminal plugin (libghostty-vt, loaded at run time without cgo) and fed by the WebSocket PTY endpoint /api/v1/terminal/pty, the same one the Web UI's xterm.js pane uses (binary frames carry I/O, JSON text frames carry ready/exit/resize/close).
Package client is the Go client of the Pando HTTP API used by the native desktop UI.
Package client is the Go client of the Pando HTTP API used by the native desktop UI.
Package i18n translates the native desktop UI with the Web UI's own catalogs (web-ui/src/i18n/locales, copied into ./locales; a test keeps the copies in sync).
Package i18n translates the native desktop UI with the Web UI's own catalogs (web-ui/src/i18n/locales, copied into ./locales; a test keeps the copies in sync).
Package kit is the Pando component library of the native desktop: the Web UI design tokens (web-ui/src/styles/tokens.css) ported onto MyGo's ui.Theme, an embedded lucide icon subset, and small components built on MyGo's widget bases.
Package kit is the Pando component library of the native desktop: the Web UI design tokens (web-ui/src/styles/tokens.css) ported onto MyGo's ui.Theme, an embedded lucide icon subset, and small components built on MyGo's widget bases.
Package natives ships the native libraries of the desktop UI inside the pando binary, so every distribution (CLI zip, Pando.app, .pkg) is one self-contained executable.
Package natives ships the native libraries of the desktop UI inside the pando binary, so every distribution (CLI zip, Pando.app, .pkg) is one self-contained executable.
Package shell bootstraps the native desktop on MyGo: the application, the main window with native decorations, and the application menu.
Package shell bootstraps the native desktop on MyGo: the application, the main window with native decorations, and the application menu.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL