| 2026-07-11 |

Navi Code: force context compression via typed /compact control message
...
Forced /compact previously sent a chat message, which ran a full agent turn
that only produced summary text instead of running the real context compressor.
Worse, even when wired correctly, forced compact always reported "Nothing to
compact yet — the context is still small" regardless of context size: the typical
navi_code shape is a single long autonomous turn (1 user message + many tool
iterations = one turn), and partition_messages finds nothing to summarize when
turns <= keep_recent. The midturn auto-compress path already bypassed this via
keep_recent_messages (intra-turn split), but compact_stream passed
keep_recent_messages=None, so the fallback was disabled.
Changes:
- WS protocol: {"type":"compact"} control message (distinct from {"type":
"message"}); rejected while an agent turn is active to avoid racing the agent.
- Agent.compact_stream: forced compression that bypasses the token threshold
but still runs the real compressor; passes keep_recent_messages=max(12,
context_keep_recent*2) so a single long turn compresses via intra-turn split
(mirrors midturn auto-compress). Raises NothingToCompactError when context is
genuinely too small.
- Orchestrator.run_compact + clear_run: broadcast agent events to subscribers,
end with done marker, surface NothingToCompactError as an error event.
- Terminal client: ws_client.send accepts str|dict; CompactCommand enqueues
{"type":"compact"}; TUI distinguishes forced compact (no stream_start) from
in-turn auto-compress via the _streaming flag.
- Tests: compact_stream (incl. single-long-turn regression), WS handler
dispatch/rejection, run_compact event/error broadcasting, ws_client send,
compact command.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
23 hours ago
|
tui: remove the useless NaviCodeTui Header bar
...
The default Textual Header just shows the class name — no information value,
and it costs the top screen line. Drop it from compose (and the now-unused
import) so the chat panel starts at the very top.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
tui: per-message chat widgets — render only the streaming bubble
...
ChatPanel used to hold a single Static rebuilt via update(Group(...)) on every
event, so a per-token stream_delta re-rendered all visible items (up to 200).
Now each message is its own _ChatItemView(Static); _sync() mounts new widgets at
the end, removes dropped ones, and signature-gated maybe_update re-renders only
the changed widget. On a stream only the streaming bubble repaints — the rest
stay mounted untouched. Public API stays synchronous (mount/remove are sync in
this Textual version), so tui_app is unchanged. Truncation hint is now a
dedicated top widget toggled via styles.display.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
tui: fix 'Connection: online online' duplication
...
The status panel renders 'Connection: online {detail}'; the supervisor was
passing detail='online' on a successful connect, so the label read
'online online'. The label already conveys the state — send an empty
detail on success (matching the pre-reconnect behaviour). Reconnecting/offline
details are unaffected since they carry a reason distinct from the label.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
tui: auto-reconnect WebSocket with exponential backoff
...
A dropped server socket no longer leaves the TUI stranded. WsBridge now
runs a supervisor that connects, forwards events until the socket closes,
then backs off and reconnects indefinitely — the only thing that ends it is
stop(). The status panel flips to 'reconnecting…' and the next message the
user sends is held in the input queue until a fresh socket is live, so a
brief blip doesn't eat their input (send failures requeue + force a
reconnect; nothing is lost).
Backoff: 1s→2s→4s… cap 30s, reset on success; stop() cancels it promptly.
Interface preserved (start/stop/.client/.connected); an optional client=
param lets tests inject a fake client.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

tui: show the changed step's text in the todo update call card
...
The todo update call card (→ todo · #3 → done) now shows the text of the
step being changed, plus the validation when present. The LLM's update args
carry only the index, not the task text, so the text is read from the
current plan row before the tool runs and attached to ToolStarted.metadata
(client-rendering only, backward-compatible).
Backend:
- events.ToolStarted gains a metadata dict (mirrors ToolEvent) → to_wire.
- navi/tools/todo: step_text_for_update(index, ctx) resolves the step text
via _sid/_uid (so sub-agent isolation holds), started_metadata_for_call
wraps it for both emit sites.
- agent.py (parent) and subagent_runner.py (sub-agent) enrich ToolStarted
via the shared helper before emitting.
Renderer:
- TodoStartedRenderer update card reads msg.metadata.step_text → shows the
step text + validation; falls back to 'no validation' on history replay.
- Removed the step-text line from the result card (it now lives in the call
card) — result card is back to the compact 'plan · X/N done' summary.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
tui: sort todo panel — in_progress on top, done at bottom
...
Group todo steps by status for display: in_progress → pending →
failed/skipped → done. Original payload order is preserved within each
group (stable sort), and self._tasks is not mutated so step index still
matches the plan — only the on-screen order changes. Unknown statuses
fall in with pending so new work stays visible.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

Isolate sub-agent todo + render live todo in TUI side panel
...
Phase 1 — sub-agent todo isolation (backend):
- Add current_todo_session_id ContextVar; subagent_runner scopes it to the
sub-agent's ephemeral run id so its auto-populated plan and todo updates
land in an isolated KV row instead of clobbering the parent session's todo
(which the parent's goal-anchoring reads every iteration).
- todo._sid() and planning.set_tasks prefer current_todo_session_id; the
parent run leaves it unset, so all existing todo consumers (anti-stall,
goal anchor, get_progress_message) behave exactly as before.
Phase 2 — live todo in the TUI right column:
- TodoUpdated event + emit it from the agent loop after planning auto-populate
and after each tool-execution turn.
- GET /sessions/{id}/todos reads the parent session's todo KV row (explicit
user_id/session_id, optional injected kv).
- api.get_todos + TodoList/TodoPanel widgets: status-coloured markers
(pending dim, in_progress accent+bold, done success, failed error, skipped
dim), progress header, scrollable panel below the auto-height info block.
- Hybrid delivery: REST seeds the panel on attach/switch, todo_updated WS
events update it live.
Sub-agent todos as nested sub-lists is deferred to a later phase.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: drop broken ctrl+x n / ctrl+x l session keybinds
...
Remove the non-working session-switching keybinds ctrl+x n (New) and
ctrl+x l (Sessions) and their action methods; /new and /sessions are still
reachable by typing them (with the inline hints) or via the palette. The
keybind field is cleared on both commands so hints/palette stop showing
them. ctrl+x q/c/t (quit/compact/thinking) are left intact.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: unify session commands on shared workers; /switch hybrid
...
Route /new, /sessions, and /switch through shared app-side logic instead of
each command reimplementing the switch path:
- _open_sessions_picker(): pushes SessionsPickerScreen and wires the select
callback to _switch_session_worker / _create_new_session_worker. Both
/sessions and /switch (no/ambiguous arg) use it.
- /new -> _create_new_session_worker (was: inline create + attach + clear).
- /switch <prefix>: resolve to a single navi_code session (exact id, then
prefix match) and switch directly via _switch_session_worker; no/ambiguous/
unknown arg opens the picker pre-filtered to what was typed. This removes
the latent clear()-after-attach bug from the old SwitchCommand (clear erased
the history that attach_session had just replayed).
- SessionsPickerScreen gains initial_query to open pre-filtered.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: sessions picker for /sessions (navi_code, full switch)
...
Replace the chat-printed session list with a modal picker:
- SessionsPickerScreen lists only navi_code-profile sessions (filtered by
settings.default_profile_id), with a filter input, a top "✚ New session"
row, and the current session marked. Up/Down + Enter to pick, Esc cancels.
- /sessions pushes the screen. Selecting an existing session runs
_switch_session_worker (persist via StateManager + attach_session, which
replays history and restarts the WS bridge — no chat_panel.clear() after,
since load_history already replaces the chat and clearing would erase the
replayed history). The "New session" row runs _create_new_session_worker.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: interactive slash-command hints (Up/Down + Enter)
...
Make the inline command hints navigable:
- Up/Down move a highlight through the matching commands (wraps around).
- Enter while a hint is open runs the highlighted command (routed through
UserSubmitted as /<name>, so the app's _run_command handles it — not sent
to the agent). Enter with hints closed still submits the raw text.
- Tab completes the input to the highlighted command's canonical name and
stays in the field for typing args (was: always the first match).
CommandHints stays a non-focusable Static (renders the highlighted line with
reverse video); _PromptInput owns all key handling via a sibling reference.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: inline slash-command hints + Tab completion
...
Show a non-interactive list of matching commands above the input box while
the user types a '/'-command (no whitespace yet). Tab completes the prefix to
the canonical name of the top match. Enter already routed '/...' through
_run_command, so submitting a typed command executes it instead of sending
it to the agent — unchanged.
- CommandRegistry.match(prefix): case-insensitive prefix match on name and
aliases, exact match sorted first; empty prefix returns all.
- CommandHints(Static): purely visual, never takes focus, hidden by default.
- InputBox composes hints above the prompt and refreshes them on
TextArea.Changed.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: remove sessions table from right column
...
Drop the SessionsPanel widget and all dead code wired to it (handlers,
broadcast helper, session-list events, _refresh_sessions worker). The
/new, /sessions, /switch commands are preserved — they already operate
via app.attach_session / chat output rather than the panel.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

Navi Code TUI: show request duration (live timer + chat metadata)
...
Live elapsed timer next to the activity indicator (ticks once per second
during a turn, format 2m 16s), driven by the same stream_start/stream_end
hooks as the activity indicator — so it spans the whole turn (all LLM calls,
tool calls, planning, sub-agent steps). On stream_end it syncs to the
backend's authoritative elapsed_seconds before freezing; on stream_stopped/
error/disconnect it freezes at the locally-measured value.
On stream_end the model also appends a turn_meta ChatItem rendered as a dim
"⏱ 2m 16s" line below the answer — the total time the agent spent on the
user's request. The value comes from stream_end.elapsed_seconds, already
measured by the backend over the whole turn, so the TUI needs no timer for
accuracy. stream_stopped/error do not get a metadata line (only completed
requests).
Shared format_duration(): None -> "—", <60s -> "16s", <3600 -> "2m 16s",
>=3600 -> "1h 2m".
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Navi Code TUI: context-window fill gauge in the status bar
...
Add a ContextFill widget to the bottom StatusBar showing how full the LLM
context window is (absolute tokens + percent), colored by fill level:
dim < 70%, warning 70-89%, error >= 90%. Updates from stream_end and
compression_started/context_compressed events; seeded from get_session on
resume so the gauge isn't blank pre-turn. None from the backend keeps the
last known value.
Fix: name the widget attribute _ctx_fill, not _context — _context shadows
MessagePump._context (the internal context manager Textual wraps around
message processing) and hangs the app's message pump at mount time.
Polish: drop the "ctx " prefix (shows 12.0k/32.0k · 38%) and add a 1-char
margin between the activity indicator and the gauge.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
tui: load session message history into the chat panel on resume
...
Resuming or switching a session showed a blank chat — the past conversation
was never replayed. GET /sessions/{id} already returns the messages; the TUI
just ignored them.
- ChatModel.load_history maps persisted Message dicts back to the same
ChatItems the live stream produces (user/assistant text, thinking, tool
started/result, plan), skipping is_display=False (context-only user,
summaries, compression events) so only what the user would have seen
live is rebuilt.
- ChatPanel.load_history wraps it (clears the render cache + refresh).
- attach_session loads the messages from get_session, so resume/switch
replays the history before the cwd/Connected banners.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

tui: activity indicator in a bottom status bar + 1-line input
...
Add an animated "agent is working" indicator so a silent gap (long-running
tool, the pause before the first token, sub-agent reasoning) reads as
"alive" rather than "hung": a braille spinner + phase label that runs on
its own timer, independent of the WebSocket event flow.
- New ActivityIndicator widget (rotating spinner + phase label, colored
status_online), started/stopped from tui_app WS phase transitions
(thinking / responding / planning / running <tool>) and stopped on
stream_end/stopped/error and on connection drop.
- New StatusBar bottom line replaces Textual's Footer: indicator on the
left, dim key-combo summary on the right. One line instead of the menu;
the key bindings themselves stay on the App so the keys still work.
- Input box: TextArea min-height 3 -> 1, so the prompt is one line by
default (auto-resize + max-height 12 unchanged).
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
tui: drop unused Tokens/Iter rows from the status panel
...
set_tokens/set_iterations were never called and the two rows permanently
showed "-", so they were dead noise in the right column. Remove the rows
and their setters.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

tui: add --resume <session_id> and print resume hint on TUI close
...
Closing Navi Code now tells the user the session id and the exact command to
continue it, so a session is one re-run away instead of lost to state lookup.
- cli.py: add --resume <session_id> (mutex with --new-session). Raw and TUI
paths resolve the given id via api.get_session; a bad id is a hard error,
not a silent new session. After app.run() returns and the terminal is
restored, print "Session <id>" / "Resume with: navi-code --resume <id>"
when a session is attached.
- tui_app._resolve_session: make the explicit-id branch strict — a failed
get_session surfaces an error event and returns None instead of falling
through to creating a new session (a --resume typo used to silently start
a fresh session). Saved-state resume stays lenient.
- tests: resume-flag resolve/bad-id/mutex + hint format; tui strict-on-bad
explicit id and explicit-id resume.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-07-10 |
tui: bound chat history render cost (cache + visible-item cap)
...
The chat panel rebuilt every renderable on each WS event, including
per-token stream_delta — so markdown was re-parsed for the entire history
on every token and the TUI lagged as conversations grew.
- Cache renderables keyed by id(item)+signature so only the changed item
(the streaming assistant bubble) is rebuilt per token; the rest are
reused. Prune orphaned entries so cache stays bounded by live items.
- Add max_visible_items TUI setting (default 200): render only the last
N items with a "… N earlier messages not shown" hint when truncated.
Items stay in the in-memory model (non-destructive) but are not laid
out, so per-refresh cost is constant regardless of history length.
- Rename the cache attr to _chat_render_cache to avoid collision with
Textual Widget._render_cache, which it resets on mount.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
2 days ago
|
tui: render Navi's answers as markdown
...
AssistantMessageRenderer now parses its content as markdown (headings, lists,
bold/italic, inline and fenced code blocks with theme-aware syntax
highlighting) via the existing ThemedMarkdownRenderable — the same renderer
the planning cards already use — instead of plain Text. The formatting
shows live as Navi streams. User messages stay plain text.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
2 days ago
|

tui: show the currently-served model in the status panel
...
The status panel's Model line was fed the global ollama_default_model, not the
session/profile model, and the server never told the client which model
actually served a call. Now:
- Backends stamp the resolved model onto LLMChunk (first chunk) / LLMResponse.
The fallback backend reports the model that survived its server+model
priority list (may differ from the profile's first choice).
- New ModelInfo event ({"type":"model_info","model":...}) emitted once per
turn from agent._consume_stream, re-emitted only when the model changes
across iterations. Additive WS event — old clients ignore it.
- TUI: attach_session/switch fetch the profile's configured model (first of
profile.model) via api.get_profile_model so the panel shows a value before
the first request; model_info then refines it to the actually-served model.
Not forwarded to the chat panel. raw CLI prints "[model] ...".
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
2 days ago
|
| 2026-07-09 |

Navi Code TUI: sub-agent spawn card + nested styling for subagent thinking/planning
...
Sub-agent activity was either rendered as a generic tool card (spawn_agent) or
dropped to raw debug output (turn_thinking was unhandled in the TUI chat model
and the raw CLI). The webclient nests subagent events inside the spawn_agent
card; the TUI is flat, so nesting is conveyed with a distinct spawn card plus
left-indented, dimmed subagent cards.
New renderers/subagent.py:
- SpawnAgentStartedRenderer: "↘ subagent · <profile_id>" card showing the task
(+ briefing dimmed), secondary border. Registered before ToolStartedRenderer.
- SpawnAgentResultRenderer: "↙ subagent · <profile_id> ✓/✗" card with the
result, success/error border. Registered before ToolResultRenderer.
Nested subagent styling (left indent via Padding + dim):
- ThinkingRenderer: is_subagent → "subagent thinking" title, dim border, indented.
- PlanningStatusRenderer / PlanReadyRenderer: is_subagent → indented (label/title
already mark it as subagent).
- ToolStartedRenderer / ToolResultRenderer: is_subagent → indented so inner
tool calls read as nested inside the spawn card.
chat_model handles turn_thinking → thinking_block item with is_subagent meta;
chat_panel._refresh forwards is_subagent to the thinking renderer. Raw CLI
render.py prints turn_thinking as a [thinking] block (with "(subagent)" prefix).
Tests: spawn card shows profile/task and status; subagent thinking/planning/tool
cards are indented and labeled; non-subagent tool card is not indented;
turn_thinking creates a subagent thinking_block. 593 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|

Navi Code: render planning as a progress line + plan card, not raw events
...
planning_status and plan_ready events were unhandled in the TUI chat model and
the raw CLI renderer, so they fell through to raw debug output (TUI:
"plan_ready: {...}" status lines; raw CLI: "[event: plan_ready] {msg}").
TUI:
- New renderers/planning.py: PlanningStatusRenderer renders a "⚙ Planning ·
<label>" line (info color; dim + "(subagent)" prefix for subagent planning);
PlanReadyRenderer renders the plan as a Panel wrapping themed Markdown, titled
"Plan" with an accent border ("subagent plan" + dim border for subagents).
- Registered both in default_registry().
- chat_model handles the events: non-subagent planning_status rolls in place
(Analysis → Execution plan → Plan review share one line instead of stacking);
plan_ready consumes the pending planning line and appends the plan card;
subagent planning is appended as its own dim line/card so it does not bleed
into the parent turn's indicator (matching the webclient).
- chat_panel._refresh maps the new kinds to the registry.
Raw CLI (render.py): planning_status prints "[planning] <label>"; plan_ready
prints a "[plan] … [/plan]" block with the plan text.
Tests: renderer accepts/render (label, subagent prefix, plan title/content);
chat_model rolling-in-place, plan_ready consumes indicator, subagent separation.
584 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|
navi-code CLI: make -h show help instead of sending it to the model
...
The CLI uses ignore_unknown_options=True so a prompt can contain flag-like
text, but that also meant the short -h flag was not recognized as the help
flag — it fell through to the PROMPT argument and was sent to Navi as a
message ("you've asked for help again..."). --help already worked because
Click auto-generates the long form.
Add help_option_names=["-h", "--help"] to context_settings so -h is a known
help option, parsed before the ignore-unknown fallback.
Test: -h via CliRunner exits 0 and shows usage. 577 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|
Navi Code TUI: drop forced line break, keep soft-wrap + Enter to send
...
The forced line break (Ctrl+Enter) never worked: the target terminal does not
distinguish any modifier+Enter from plain Enter — all of Ctrl/Alt/Shift+Enter
arrive as key='enter' character='\r' (verified via a one-off key probe). With
Enter bound to submit, any modifier+Enter newline binding would collide with
submit and is impossible to distinguish in software.
Remove the Ctrl+Enter branch from _PromptInput._on_key. The input stays
multi-line via soft-wrap (TextArea soft_wrap=True) and height: auto growth;
Enter still submits. Placeholder and module docstring updated to drop the
Ctrl+Enter mention and note why a newline key is not wired (revisit later).
Tests: removed test_ctrl_enter_inserts_newline_without_submitting; kept the
multiline-submit and input-grows tests. 576 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|

Navi Code TUI: make the message input multi-line
...
InputBox used textual.widgets.Input, a single-line widget. Swap to TextArea so
the prompt is genuinely multi-line: soft-wrap is on, the field grows with content
(height auto, min 3, max 12, then internal scroll), and Enter no longer collides
with newline insertion.
_PromptInput(TextArea) intercepts keys in _on_key before TextArea's own handler
(which maps enter -> "\n"):
- Enter -> submit: post UserSubmitted(text), then clear the field.
- Ctrl+Enter -> insert a hard line break.
Dropped dead set_prompt_char (referenced a non-existent self._prompt) and the
on_input_* handlers (submit now lives in the subclass). CSS selectors updated
from Input to TextArea. Placeholder notes the keybindings.
Tests: _input.value -> _input.text across the TUI suites; added Ctrl+Enter
inserts a newline without submitting, Enter submits the full multi-line buffer
(newlines preserved) and clears the field, and the input height grows with
content. 577 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|

Navi Code TUI: show final answer below tools, not scrolled out of view
...
The assistant answer bubble was created at stream_start (the first event of a
turn), placing it at the TOP of the turn — above the thinking blocks and tool
cards that follow. chat_panel._refresh() calls scroll_end, so the view pinned to
the bottom (tools/thinking) and the answer at the top scrolled out of view.
Users saw tool results, planning, and thinking, but not the final answer.
Fix in chat_model.handle_ws_event (client-only, no protocol/server change):
- stream_start no longer eagerly adds an empty assistant bubble; it resets the
current-assistant pointer so the first stream_delta opens the bubble lazily
at the position where text actually arrives.
- tool_started/tool_call reset the current-assistant pointer so the next
stream_delta opens a fresh bubble BELOW the tool cards — the final answer
lands at the bottom, visible after scroll_end.
- stream_end purges empty assistant/thinking bubbles and resets pointers.
Tests: stream_start creates no empty bubble; final assistant_message renders
after the last tool_call with no empty bubbles; empty bubbles purged on
stream_end. 574 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|
fix(ws): include session_id/profile_id in session_sync, guard renderer
...
The server sent {"type": "session_sync"} without session_id/profile_id,
crashing the terminal client (render.py did None[:8]). Add the fields to
both session_sync sends and guard the renderer against a missing id.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 days ago
|