Skip to content

Latest commit

 

History

History
550 lines (472 loc) · 29.5 KB

runtime-notes.md

File metadata and controls

550 lines (472 loc) · 29.5 KB

Runtime Notes

These notes preserve runtime details that are too specific for the README but matter when changing launch, config, editor, shell, or popup behavior.

Managed Appearance

Root appearance.mode is the managed dark/light authority. Ratconfig uses it for its own palette. When rio/config.toml is writable, Nova owns only its top-level force-theme field and projects the root mode there. Rio starts without a theme override and its native config watcher applies later Ratconfig saves. A custom complete Rio config must supply its own adaptive pair to participate; every field other than force-theme remains native and user-owned.

Yazelix passes only the root dark/light mode to each new managed Zellij session. Zellij resolves the matching theme_dark or theme_light member. A managed session ignores ambient terminal appearance reports so the explicit root mode remains authoritative. A root appearance save from inside that session calls set-dark-theme or set-light-theme against that exact session. Zellij's host-theme event switches the top bar between its child-owned dark and light palettes. A bar loaded by a later tab receives the session's current mode even when no new host-theme transition occurs. The coordinated save rolls back if a live component projection fails.

When Rio's config is read-only or cannot be projected, Nova starts Rio with the explicit mode and captures that mode for the session. Ratconfig, Zellij, the bar, and new Yazi opens stay on that side even if the root setting changes. The saved mode applies everywhere together in the next session.

Each managed Yazi launch reads the active session mode before materializing its config. Yazelix selects the matching native flavor.dark or flavor.light, using Yazi Bistro's Bluloco Light default when the light side is absent. An absent dark side uses Yazi's native preset; Ratconfig exposes that state as the first dark choice, default, without writing it as a flavor name. When a flavor is selected, both runtime flavor keys receive it so ambient detection cannot choose the opposite side. The source theme.toml is never rewritten, and an existing Yazi process keeps its current theme until it is reopened.

Sessions

yzx enter and yzx launch pass the managed config and --new-session-with-layout to packaged Zellij before forwarding caller arguments. Zellij owns session creation, attachment, switching, process lifetime, and native failure messages. Plain launch creates an independent session. --session NAME requests a fresh named session, while attach NAME requires a live target. Attach and switching preserve the target state without reapplying the managed layout.

Rio closes with its child, so under yzx launch the welcome helper runs Zellij as a child instead of replacing itself. When Zellij exits unsuccessfully, for example after rejecting a session name or finding an existing session, its message stays on screen with the exit status until Enter is pressed. Normal quit, detach, and cancelling the only startup picker exit successfully and close the window immediately. yzx enter keeps the direct handoff, because the host terminal already keeps Zellij's output.

The managed Zellij config sets default_layout "layout" and points layout_dir at the directory containing the active layout.kdl and layout.swap.kdl. Runtime appearance, shell, or widget projection rewrites that directory to the materialized state path. Zellij lists the configured default first, so native type-to-create selects the Yazelix layout before it creates the named session.

Use full names for supported attach behavior. Native unique-prefix matching, rename, and structural resurrection remain Zellij surfaces outside the Nova v1 continuity contract. Yazelix adds no registry or second session owner.

Zellij Sidecars

zellij/config.kdl is a guarded sidecar for scalar preferences such as paired dark/light themes, pane frames, mouse mode, scrollback size, copy behavior, styled underlines, startup tips, and ui.pane_frames.rounded_corners. An explicit top-level scroll_mode_sync true restores Zellij's automatic Scroll mode entry in new sessions. Explicit pane_frame_style and stacked_pane_list values also replace the corresponding packaged defaults; omitting them inherits Nova's values. Ratconfig uses the same 41 identities from the pinned Zellij assets for both theme fields, with a flake check keeping the inventory aligned. The inherited pair is theme_dark "ansi" and theme_light "gruvbox-light"; resetting a field removes only its sparse override, and removing the final assignment removes the sidecar. Custom names found on either member join the shared pool shown by both pickers.

The former static theme field is not part of the managed model. An existing assignment remains untouched in the user sidecar and is reported as ignored, while runtime materialization omits it so the complete pair has one clear authority.

When yzx config runs inside a managed session (ZELLIJ_SESSION_NAME or YAZELIX_ZELLIJ_SESSION_NAME, plus YAZELIX_STATE_DIR), saving a Zellij tab field also patches $YAZELIX_STATE_DIR/zellij/config.kdl so the running Zellij watcher can pick up scalars without rewriting integration patches. Editing the active theme-pair member applies live; editing the other member takes effect on the next appearance change or managed session launch. Ratconfig labels pane frames, rounded corners, copy-on-select, and clipboard target as now when that active runtime file is addressable. Mouse mode, scrollback size, styled underlines, and startup tips say next session; outside an active managed session, every Zellij field is next-session. Quoted custom theme names without KDL escapes remain accepted; richer string syntax stays preserved but native-file-only.

Ratconfig scopes sidecar diagnostics instead of marking every Zellij field invalid. Safe untyped top-level leaves stay unchanged across unrelated saves and resets without a synthetic field or informational diagnostic for each leaf. The exact native-file action opens the sidecar for those settings. Invalid known fields affect only their own row and remain repairable. Advanced diagnostics report ignored legacy settings, malformed or structured native nodes, structural KDL comments or continuations, extra metadata on managed blocks, and integration-owned nodes. Unsafe source state blocks writes, and each guarded diagnostic names its Yazelix owner.

The runtime rejects uncommented top-level ownership nodes in that sidecar:

keybinds
default_shell
default_layout
layout_dir
layout
plugins
load_plugins
support_kitty_keyboard_protocol
env
session_name
attach_to_session

Use zellij/plugins.kdl for extra plugin declarations. It accepts only plugins and load_plugins blocks:

plugins {
    my_plugin location="file:/home/me/.config/zellij/plugins/my_plugin.wasm"
}

load_plugins {
    my_plugin
}

Plugin ids owned by Yazelix (nova_hints, yzpp, yazelix_pane_orchestrator, radar, and radar_controller) cannot be redeclared. Plugin keybindings are not managed by this sidecar.

Sidebar Provider

sidebar.command = "radar" selects the packaged Radar plugin. Any other valid value creates a terminal pane with that executable and sidebar.args. The config helper validates the pair and renders one KDL pane body; the launcher substitutes it into the base and swap layouts. Both providers therefore use the same Zellij pane named sidebar, including its open and collapsed sizes.

The packaged Radar alias is a lightweight per-tab view. One suppressed radar_controller instance owns session state, event subscriptions, rendering, and command keybindings. Views send bounded dimensions and mouse input over plugin messages; the controller replies directly to each view's plugin id. Both roles request the same permission union because Zellij stores one grant per Wasm path. A custom sidebar omits the controller as well as Radar's bindings and grant.

Nova captures the provider when it creates a session. It passes custom arguments as argv without a shell. The pane orchestrator remains provider-neutral and addresses the pane by name. With a custom provider, Nova omits Radar command bindings, controller load and permission seeding, sets YZX_RADAR_ENABLED=false for child launchers, and skips Radar's Codex doctor path.

Popup Lifecycle

Opening or revealing a managed popup shows Zellij's tab-wide floating layer. Toggling the focused popup off or explicitly closing it hides that layer after closing or suppressing the managed pane. This returns directly to the tiled workspace instead of exposing a session manager, terminal, or other floating pane underneath. Suppressed keep-alive popups and unrelated floating panes continue running; their explicit keybindings can show them again.

Popup Geometry

The popup plugin observes the tiled pane named sidebar. While that pane is wider than its collapsed divider, managed popups use a shared 33-cell left margin so the framed 32-column sidebar rail remains visible. A pane update reflows any visible managed popup without restarting it. When the rail is collapsed, the configured side margin applies symmetrically again. Native usable viewport changes also reflow visible managed popups. Hiding the bottom hints extends the popup by one row; restoring hints keeps that row clear. Configured margins, the running process and keyboard focus survive the resize.

For a sidebar toggle, the pane orchestrator asks Nova Zellij to select the exact named tiled swap layout underneath the visible floating layer. Zellij applies it in one render, so the popup is never hidden, restarted, or refocused.

Alt [ and Alt ] ask the pane orchestrator to apply the next content arrangement directly. The command palette's layout entry asks for the same next variant, then applies it through Zellij's CLI. Nova has one stacked startup layout and four content/sidebar families: stacked or split content, each with the sidebar open or collapsed. Every family has matching visible-hint and hidden-hint swap variants. The split places one existing work pane beside a stack of the others. It requires at least two visible tiled work panes, starts no new shell, and leaves the current tab folder unchanged.

The bottom hint row has one session-wide visibility owner in the pane orchestrator. Alt Shift B and the command palette's Toggle bottom hints entry send the same toggle_bottom_hints command. Native pane suppression reclaims the row; paired named layouts restore its bottom position. Sidebar and content-layout changes preserve the choice. Hint toggles preserve work-pane order, including manual rearrangements and background tabs. The native tiled matcher retains relative pane order when explicit UI slots change; new panes follow existing panes. New tabs inherit the hint choice. Fresh sessions start visible unless bottom_hints.start_hidden = true; changing that setting affects future sessions. Attaching preserves the current choice. One connected client handles broadcast toggles; each client restores its active tab’s layout and focus. Closing a tab removes its hint pane while retaining native preservation of unrelated background panes. Popup reconciliation uses native pane geometry to retain work columns and settle the hint layout without a repeated redraw loop. keybindings.bottom_hints accepts a replacement chord or false.

BOTTOM-HINTS-START-001 is the Nova/provider-to-orchestrator startup contract. Nova names only initial and new-tab hint panes bottom_hints_start_hidden when the preference is enabled. The oldest hint pane seeds visibility once in the orchestrator's native background controller pane. Its title records yazelix_pane_orchestrator:hints:v1:hidden or :visible; it survives tab closure and is shared by attached clients. Marked hint panes become bottom_hints after reconciliation. Later tabs inherit the stored choice even when the original tab closes immediately. Visible swap layouts keep the ordinary name. Native pane state remains the sole runtime visibility owner. Retiring hints lose their hint role before being shown and closed; one native client elected across all tabs handles retirement and toggles. The nova_hints alias passes each managed shortcut chord to nova-zjhints as pipe_hint_<id> metadata because native mode events omit plugin-message identities. The managed keybinding patch owns both binding and hint remapping or omission. The plugin shows only matching active pure-pipe keys, using native key parsing and existing whole-hint fitting. Compact modifier headers retain separate backgrounds; core workspace and Nova actions receive fitting priority. In Normal mode, C-A hjkl move combines the four orchestrator pane moves: j/k within a column and h/l across split columns. Existing pipe annotations and explicit-label merging provide the shared hint; its fixed key display is scoped to the merged Normal hint, keeping the native Move-mode entry and other input modes distinct. Tab reordering uses Ctrl Alt Shift H/L, which relies on the Kitty keyboard protocol to keep Shift distinct from the pane moves.

Agent Popup

Radar owns agent activity. The top bar does not track execution state; its tabs use Zellij's native names, selection, bells, and layout indicators. The orchestrator's v2 session response retains an empty extensions.ai_pane_activity list for existing consumers, but no activity tracker or broadcast feeds it. Codex activity status and prompt labels reach Radar through enabled, trusted Radar hooks. Nova does not query Codex for activity or provide an equivalent hookless fallback. Without those hooks, ordinary pane and command information remains available, but Codex activity reporting is unavailable.

Radar's hook and sidebar broadcasts use the bundled yzx-zellij through the managed session PATH's private zellij alias. This prevents another installed Zellij version from selecting a different session namespace. The alias reports the upstream Zellij version for Radar's compatibility check; yzx-zellij retains the distribution identity.

Managed Codex launches with Radar selected use --no-daemon when the installed Codex advertises it. Codex's shared server snapshots its own environment for hooks, which can omit the invoking pane's Zellij identifiers or refer to another pane. Running in the pane keeps reporting associated with that pane. Existing hook trust and enablement still apply; older Codex versions keep their launch arguments. When starting Codex directly in a shell, use codex --no-daemon if that version otherwise uses a shared server.

Agent popup identity uses the stable /bin/yzx-agent command marker. A runtime update can change the launcher's store path without losing the running popup, including when the agent supplies its own terminal title.

The packaged agent launcher gives the pane its initial agent popup terminal title, then replaces itself with [agent].command. The default auto chooses a provider once per state directory. On first launch it checks PATH in this order:

codex resume
grok
opencode
pi
claude --resume

It stores the selected provider at:

${YAZELIX_STATE_DIR}/agent/provider

Later launches use that stored provider. If the stored provider is unknown or missing from PATH, the popup prints a diagnostic and tells the user to remove the provider file so Yazelix can choose again.

With Radar selected, the agent launcher runs zj-radar setup codex --check before each managed Codex launch. It consumes Radar's pinned report rather than parsing Codex files: warning lines make partial or disabled hooks unhealthy even when Radar exits zero. A healthy report records enabled in agent/radar-codex-setup-offered. An unhealthy report with no known disposition produces the Enable Codex activity in Radar? [Y/n] prompt only when stdin and stderr are terminals. Declining records declined; accepting records enabled only if setup succeeds and a second read-only check proves health. The old 1 marker encoded both answers and is treated as unknown. Closing the prompt or entering an unrecognized answer leaves that state unknown.

If a previously enabled integration becomes missing, partial, or disabled, Nova sends one transient zjstatus::notify bar notice per interactive launch with zj-radar setup codex as the Codex-only repair path. It never repairs automatically or reopens the consent prompt. A declined state remains quiet until an explicit successful zj-radar setup codex is recognized by a later healthy check. Non-interactive launches neither prompt nor toast. Child-check, setup, and toast failures never block Codex. yzx doctor replays Radar's read-only Codex diagnosis and keeps missing integration warning-only. It omits the trust reminder until hooks exist and reports startup failures once through the shared diagnostic. Its default report groups useful health checks and colors statuses on a TTY. yzx doctor --verbose prints Radar's raw report and individual Classic residue entries. yzx status remains the owner of paths and settings. Trust remains owned by Codex's /hooks UI. Both diagnostic commands validate configuration without writing files, initializing Rio or granting plugin permissions, including their verbose/JSON forms. Missing runtime files remain absent until an existing launch/run command prepares them. Text status marks absent generated paths as not initialized; doctor gives the launch action. Invalid settings or sidecars fail without changing user state. yzx radar-setup and its Set up Radar for Codex, Claude Code, OpenCode entry in Alt Shift M invoke the packaged zj-radar setup codex claude opencode with inherited terminal input/output and without --yes. Radar owns sequential detection, existing-state handling, consent and installation. Its prompts default to no ([y/N]); noninteractive setup skips writes. Codex and OpenCode detection also considers existing config, including CODEX_HOME and XDG_CONFIG_HOME. Setup does not consult Nova's offer marker, so a remembered decline never prevents explicit setup. Child output and exit status are preserved, and unsupported Nova arguments fail before setup. Start a fresh agent session afterward and review Codex trust in /hooks. The automatic Codex offer never runs setup for other agents. Claude Code and OpenCode setup stays explicit; Grok and Pi have no Radar adapter.

Any other agent.command value is executed directly by the same launcher for new sessions, so custom commands receive the same initial title. Put argv-style arguments in agent.args, not in agent.command.

Yazi Picker and Popup

Every new managed tab starts with the configured sidebar provider and one focused tiled yazi_picker. A successful ordinary open or startup-search Ctrl O owns the tab's initial retarget, creates the managed editor, and only then closes that exact picker by id. Alt Enter is unavailable in this startup picker; there is no separate workspace-only step for an editor handoff to overwrite. There is no hidden starter shell or prestarted editor whose cwd can become stale.

Exiting before a successful handoff closes the picker's stable tab. A surviving editor tab remains focused; cancelling the only picker ends the session.

Alt Shift Y asks the pane orchestrator to toggle the packaged yazi popup with the active tab's canonical workspace root as its explicit request cwd. The popup uses toggle_close_behavior "hide" and preserve_on_cwd_change true, so ordinary toggles only hide or show the same live Yazi process. Yazi remains the sole owner of its navigation state even if its process cwd or the canonical tab root changes. yzx reveal sends one configured replace request to the popup owner with the absolute target as a launch argument. Yazi reveals that target during normal startup, so reveal does not wait for or address a partially started process. This deliberately resets the popup's navigation state while leaving the canonical workspace unchanged. The managed PATH resolves yzx to the exact package that launched the session before consulting the inherited host path, so mixed installed channels cannot change the behavior of Helix's reveal command. Managed Yazi owns Alt r locally and passes no hovered path. The popup role sends one explicit hide to the popup owner, preserving the live Yazi process and navigation state and returning to the underlying pane. It does not open the hovered item or change editor cwd or the canonical workspace; Enter remains the explicit file-open action. Helix owns its reveal binding locally as well. Zellij does not intercept or reroute the chord, so hiding the popup cannot replay it as a Helix reveal.

The popup runs the yzx-yazi launcher and layered config with the private workspace-popup role. Popup navigation and ordinary opens retain their local and canonical-workspace semantics.

Shell History, Nushell, And Starship

When shell.program = "nu", Yazelix does not read normal Nushell config. It generates runtime Nu files that source packaged Yazelix config first and then optional user files:

~/.config/yazelix/nu/env.nu
~/.config/yazelix/nu/config.nu

If host mise is available on the inherited PATH, managed Nu inserts mise activate nu output after packaged config.nu and before user nu/config.nu. Missing or failing mise is skipped.

Managed Nu enables the pinned native clipboard commands. Pipe any Nu value into clip copy (for example, pwd | clip copy) and read it with clip paste. The managed clc and clp aliases provide shorter copy and paste forms. Nushell owns platform clipboard access and errors; Nova does not translate remote or headless sessions to a client clipboard.

shell.atuin = true sources Nova's build-generated, locked Atuin init files after the selected shell's user startup layer. yzx-nu appends the Nushell form after user nu/config.nu; yzx-shell composes the others through Bash's explicit rc file, a temporary Zsh ZDOTDIR layer, or Fish's post-config command. An existing shell-specific Atuin search function skips the managed source, leaving user initialization as the sole hook owner. A managed init failure reaches stderr without preventing shell startup. Bindings, imports, and the opt-out are described in Configuration.

Managed Nu always sets STARSHIP_CONFIG to a runtime-effective file. That file starts with Nova's sparse [character].format = ":: " marker and recursively merges optional native overrides from ~/.config/yazelix/starship.toml. Top-level format stays unset by default, so Starship retains its native $all layout, including directory, Git, environment, and tool modules. Normal ~/.config/starship.toml does not affect the managed Nu prompt.

Helix

yzx-hx builds the effective Helix config on each launch from the packaged default plus optional managed user files under:

~/.config/yazelix/helix/

helix/config.toml is deep-merged over the packaged TOML default. The generated effective file is ${YAZELIX_STATE_DIR}/helix/config.toml. Yazelix reserves Alt r for reveal, so the generated config enforces:

[keys.normal]
A-r = ':sh yzx reveal "%{buffer_name}"'

forest.enabled = false omits Nova's generated :forest-open binding and skips Forest configuration and startup. Managed Helix and user Steel initialization still load. When Forest is enabled, keybindings.sidebar_focus adds, remaps, or disables its generated binding and matching focus toggle. A user-owned binding at the previous key remains when the managed binding is remapped or disabled. Managed Helix prepends the exact packaged Forest, notify, and glyph modules to STEEL_SEARCH_PATHS, opens Forest on the forest.side choice (right by default), and loads user Steel initialization last. When managed Helix starts on one directory, Forest stays visible but unfocused so Helix's native directory picker owns input.

helix/languages.toml is loaded by the managed Helix config dir when present. When helix/init.scm exists, a private HELIX_STEEL_CONFIG overlay loads it after Nova's managed startup. The overlay uses the packaged helix.scm module unless the user also provides helix/helix.scm; that explicit module replaces the packaged one. The packaged module provides :yzx-new-shell, which opens a new Yazelix terminal pane at the current file directory or workspace.

Packaged Steel also starts the fork's bounded authenticated loopback transport. Yazelix owns token generation, atomic private registry publication and lookup under validated session path components, same-session/tab instance selection that prefers the caller's own Helix pane, and the TCP client. The fork passes authenticated actions to Steel on the editor thread without owning Zellij, Yazi, or registry policy. Closed bridges, stale-token authentication failures, and invalid or mismatched response frames leave later registry candidates eligible.

Tab Workspace

The pane orchestrator owns one canonical workspace root per tab. The first managed open resolves the containing Git worktree, or the opened directory or file parent outside Git, and publishes it as explicit state. Later ordinary Yazi opens preserve that root while passing the requested absolute target to Helix. This includes ignored files, nested repositories, and non-Git descendants. A managed open also resets a drifted Helix cwd to the canonical root. After success, only the originating managed Yazi follows the primary target's directory; the canonical workspace, shell panes, and hidden agent stay unchanged.

The search and tab-folder keys are listed under Editor and file opens. Search Enter emits native cd and leaves the canonical root unchanged. Startup search Ctrl O sends the selected directory through the existing retarget/editor transaction and closes the picker only after success. Popup Alt Enter sets the exact canonical root without moving Yazi or opening an editor. The orchestrator updates the tab label; existing shells, Yazi, and editor processes keep their own cwd. A later ordinary startup open provides the same first-editor handoff. Explicit popup workspace choices inside Git keep the chosen subdirectory; ordinary implicit file-open bootstrap may still use the containing worktree. A failed editor handoff restores the prior root and its bootstrap or explicit provenance. Git and agent popup requests carry the canonical root explicitly. A hidden agent is reused across pane focus and local navigation changes, and is replaced only after the canonical root changes.

Alt m and pane mode's n, d and r ask the orchestrator for a new shell pane at the canonical root, with native stacked, split or directional placement and focus. d and r reuse Zellij's own down and right actions with the packaged shell, which Nova passes to the orchestrator as default_shell. Pane mode's s stays native and stacks in the focused pane's directory, because Zellij's plugin API drops a stacked pane's command. Until a workspace is chosen, the root is the tab's bootstrap directory: the launch directory or the new tab's picker directory. Zellij reads plugin paths under /host, /data, /cache and /tmp as plugin mounts, so Alt m and n open a root inside those directories at the session's launch directory instead.

Yazi

Configuration lists the managed Yazi files and their merge semantics. Only plugin.prepend_fetchers uses replace-plus-managed-Git semantics: user fetchers are kept while the two managed Git fetchers are restored exactly once. The presence of yazi/starship.toml alone activates materialization; the materializer requires readable TOML and links it into the effective config as yazelix_starship.toml, the same path the workspace popup uses. Plugin and flavor directories also activate materialization independently of init.lua. The materializer copies theme.toml only when runtime appearance projection requires it.

Automation can materialize that config without starting Yazi, Zellij, Rio, an editor, or the full Yazelix runtime:

effective_config="$(
  yzx yazi-config materialize \
    --user-config-dir "$PWD/yazi" \
    --state-dir "$PWD/state"
)"
YAZI_CONFIG_HOME="$effective_config" yazi --debug

--user-config-dir names the exact directory that contains yazi.toml, init.lua, plugins/, and the other native Yazi entries. --state-dir names the isolated state root. Callers must pass both flags; the command does not use YAZELIX_CONFIG_HOME or YAZELIX_STATE_DIR as defaults. The selected Yazelix package supplies the packaged config, including for no-yazi variants and Home Manager installations. The active session appearance and the packaged Yazi Bistro light default are supplied internally; the public command takes no appearance flags. The Home Manager module adds no materializer option.

Successful calls print one absolute effective config directory and a newline to stdout, with no stderr output. In dark mode, a user directory with no managed entries returns the package's read-only config directory and leaves the state path absent. Light mode materializes runtime theme.toml even without user entries so it can select the packaged light default. Other inputs replace only <state-dir>/yazi through a staging directory. Bad command usage exits 64. Materialization failures exit 1; validation failures leave an existing effective directory intact.

The materializer validates paths, TOML syntax and merge structure, required integration fields, plugin and flavor shapes and collisions, appearance projection, source/state overlap, and output construction. Yazi remains responsible for semantic config checks, Lua and plugin behavior, and external commands. Run yazi --debug with the printed YAZI_CONFIG_HOME when automation needs those checks.