Skip to content

Make local WSL setup deterministic#8

Closed
ranjeshj wants to merge 141 commits into
masterfrom
verifyubuntu
Closed

Make local WSL setup deterministic#8
ranjeshj wants to merge 141 commits into
masterfrom
verifyubuntu

Conversation

@ranjeshj

Copy link
Copy Markdown
Owner

Summary

This PR makes local gateway setup deterministic by removing the hidden dependency on an existing local Ubuntu distro. Setup now creates a fresh app-owned WSL gateway distro directly from the configured Ubuntu release every run, and fails explicitly when the host WSL version or install path cannot support that clean-start contract.

What changed

  • Replaced the old wsl --export Ubuntu-24.04 / wsl --import flow with direct named install:
    • wsl --install --distribution <BaseDistro> --name <DistroName> --location <app-owned path> --no-launch --web-download
  • Reinterpreted BaseDistro as the Ubuntu release to install from a clean WSL baseline, not a local source distro to export.
  • Added WSL capability preflight before destructive cleanup so unsupported hosts fail before setup removes existing app-owned state.
  • Reordered setup pipeline so OS/WSL preflight runs before cleanup, with the port check still after cleanup.
  • Hardened cleanup and rollback behavior:
    • removes stale app-owned install directories with retry handling
    • deletes stale regular files at the target install path
    • rejects reparse points/symlinks at the install path with a clear error
    • avoids global wsl --shutdown during partial install cleanup when --unregister succeeds
  • Improved unsupported/old WSL messaging so inbox WSL versions that do not support wsl --version tell users to update WSL.
  • Updated setup UI progress text and docs to describe a clean WSL gateway install rather than cloning/exporting Ubuntu.
  • Added regression tests covering direct install, no export/import behavior, no base distro mutation, install-path validation, WSL version parsing, cleanup behavior, and pipeline order.

Motivation

The previous setup path could be poisoned by a missing or corrupted local Ubuntu-24.04 distro because setup reused it as the export source. This PR makes setup predictable for users, support, and CI by always creating the gateway from a clean WSL install baseline and surfacing hard failures instead of falling back to hidden reuse paths.

Validation

  • ./build.ps1 — passed
  • dotnet test ./tests/OpenClaw.SetupEngine.Tests/OpenClaw.SetupEngine.Tests.csproj --no-restore --tl:off — passed, 181 tests
  • dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore --tl:off — passed, 2022 passed / 29 skipped
  • dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore --tl:off — passed, 843 tests
  • OPENCLAW_RUN_E2E=1 dotnet test ./tests/OpenClaw.E2ETests/OpenClaw.E2ETests.csproj -r win-arm64 --tl:off — passed 3 consecutive runs, each 6 passed / 0 failed / 0 skipped

Review notes

A Hanselman/adversarial review was run before finalizing. Follow-up fixes from that review are included: less disruptive partial cleanup, clearer old-WSL handling, and more robust install-path checks.

github-actions Bot and others added 30 commits May 21, 2026 01:32
…ssion key

When the gateway handshake completes but does not advertise a
mainSessionKey (or the legacy mainKey alias), the composer was showing
"Connected" with a disabled-but-unlabelled send box. The user had no
indication that the gateway needed updating.

Changes:
- OpenClawChatDataProvider: emit "Incompatible gateway" as
  ConnectionStatus when HasHandshakeSnapshot=true but MainSessionKey is
  null/empty and the socket is Connected, instead of plain "Connected".
- OpenClawChatRoot: map the "Incompatible" prefix to the new
  "incompatible-gateway" connState token.
- OpenClawComposer: handle "incompatible-gateway" → disable inputs +
  show Chat_Composer_Placeholder_IncompatibleGateway placeholder.
- Resources.resw (all 5 locales): add
  Chat_Composer_Placeholder_IncompatibleGateway string.
- OpenClawChatDataProviderTests: add two focused tests for the
  incompatible-handshake path (snapshot ConnectionStatus and
  ComposeTarget.IsReady=false).

Closes openclaw#459

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…width

Visual cleanup pass on the chat surface (no functional change):

- OpenClawComposer: right-edge padding 8 -> 14 in both ThreeRow and
  InlinePill branches so the action icons (attach / mic / settings / send)
  no longer jam against the window edge.
- OpenClawComposer: dropdowns row ColumnGap 4 -> 6 for clearer separation
  between Channel / Model / Reasoning pickers.
- OpenClawChatTimeline: cap tool-burst cards (CardOf + TaskList listCard)
  at MaxWidth=720 with HAlign.Left so a single 'exec' row no longer
  stretches across the full viewport with the Done pill floating at the
  far right edge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Symmetrize user/assistant/tool burst outer margins (16px both sides)

- Use bubbleRadius for tool CardOf/listCard and conditional header buttons

- Make tool card background Transparent so outline distinguishes it from filled assistant bubble

- Drop Plain tool burst footer; assistant follow-up bubble already carries the time

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…thing room

- Drop the 36x36 spacer that mid-run assistant bubbles inherited; continuation bubbles now sit at the same left inset as tool burst cards above them, so the agent column reads as a single straight edge.

- Add 20px top padding above the first message in the scroll content so the conversation does not crowd the window edge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ign composer inset

- When no assistant avatar shown, drop the leftSlot's right margin so assistant bubbles share the same left edge (16px) as tool burst cards.

- Tool burst row header now uses bubblePadding instead of (12,8,12,8) and a 32px MinHeight, so tool rows match chat bubble heights.

- Composer outer padding 14->16 to align dropdowns/input flush with chat bubble left edge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Assistant footer leftInset now respects per-entry avatar visibility (not the global flag), so continuation entries' timestamps align with the bubble's left edge instead of being indented 44px.

- Tool burst button hover/press alphas 0x22/0x33 -> 0x10/0x1C for a subtler reveal that doesn't darken the card on every pointer pass.

- Tool card background back to a faint LayerOnAcrylicFillColorDefault tint so the card has gentle presence instead of looking like a pure outline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
User/assistant footer insets now include bubblePadding so timestamps
sit flush with the bubble's text content edge instead of the outer
bubble border.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the user avatar is hidden, the rightSlot column still added an
8px left margin even though the column body was empty, leaving an
8px gap on the right of the bubble. Gate the margin on showUserAvatar
so the bubble actually reaches the container's right edge when the
avatar is off — this makes rightInset (= bubblePadding.Right) place
the timestamp flush with the bubble's inner text right edge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The TaskHeader burst style reserved an avatar slot (36+8px) so the
list card lined up with the assistant bubble's text edge, but the
Plain and FooterReframe styles started flush at the gutter. When the
assistant entry above the burst showed an avatar, the tool cards
appeared 44px further left than the bubble.

Extracted the avatar-slot wrap into a helper and applied it to all
three burst styles so user/assistant/tool share the same left edge
regardless of burst style.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default tool burst rendering switches from Plain (verbose per-row stack) to TaskList:
 - While any step is InProgress: auto-expanded, shows 'Working on X...'
 - When the burst completes: auto-collapses to a single one-line summary card
   ('Ran N steps' or last result snippet) with a chevron to expand
 - Click chevron to override; per-step rows still individually expandable for
   full args/output (3-tier disclosure)

Addresses Scott's feedback: 'I'd like to be able to have tool calls summarized,
or made smaller, or collapsible, so there would be some way to be clear that
work is happening, and if I want to see the verbose logs, I could.'

No data-flow changes — purely the default value of the existing ToolBurstStyle
enum. The dev exploration panel still exposes Plain/TaskHeader/CompactSummary/
FooterReframe/TaskList for tuning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Tool card aligns under bubble (toolLeftMargin = gutter + avatarSlot + 16) with right edge matched (MaxWidth -= indent). Plain/FooterReframe/CompactSummary/TaskHeader unified.
- Footer priority: hide sender/model by default, surface input/output tokens + context % pills.
- Preset record defaults updated to match (new presets inherit token/ctx ON, sender/model OFF).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Reorder timeline display within each turn so ToolCall bursts render AFTER the assistant reply (or the thinking indicator if none yet). Gateway still emits tool_start before assistant_delta; only the visual order changes.
- Inline 'agent is thinking…' indicator right after the most recent User entry instead of pinning to bottom of timeline, so tool cards visually hang below it.
- Tool burst card HAlign Left→Stretch (Plain/FooterReframe/CompactSummary/TaskHeader) so the right edge fills to the bubble's max right boundary instead of shrinking to content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assistant bubble previously used HAlign=Left with no MaxWidth, so its right edge tracked content width. Tool burst card used HAlign=Stretch with MaxWidth=704, filling further right than the bubble.

Give the assistant card MaxWidth=720 and HAlign=Stretch so it always pins to the same max right boundary as the tool card (60 + 720 = 76 + 704 = 780). Tool card now sits indented 16px inside the bubble's left edge with an identical right stroke, reading as a true child of the bubble above.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p anchored)

WinUI's HAlign=Stretch + finite MaxWidth centers the element inside its slot (rather than pinning it left), which detached the bubble from the avatar + timestamp column. Revert to HAlign=Left so the bubble grows from the avatar's edge; MaxWidth=720 still caps the right edge so long messages line up with the tool burst card's right stroke. Short messages keep the previous content-width behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-on-wide-screen)

Same WinUI quirk as the assistant bubble: HAlign=Stretch with finite MaxWidth centers the element inside an oversized slot rather than pinning it left. On wide screens the tool burst card drifted away from the bubble's left edge.

Revert all four tool burst styles (Plain, FooterReframe, CompactSummary, TaskHeader) to HAlign=Left. Both bubble and tool card now anchor on the left next to the avatar/timestamp column; right edges line up when both fill their MaxWidth (720 / 720-indent).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Star Grid

Tool card needs to fill its 704-wide slot so the right stroke aligns with the assistant bubble's right edge, but HAlign=Stretch alone centers the card on wide screens (WinUI's Stretch + finite MaxWidth quirk).

Wrap the card in an Auto/Star Grid: the Auto column sizes to the card's MaxWidth (704), keeping the card pinned to toolLeftMargin and filling the slot. The Star column absorbs the rest. Applied to Plain, FooterReframe, CompactSummary, TaskHeader.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assistant bubble is content-sized (HAlign=Left, MaxWidth=720), so we can't predict its rendered width at layout time. Result: the tool card's right edge rarely matched the bubble's right edge, especially with short replies.

Solution: thread a per-turn Border[1] slot from RenderAssistantEntry into RenderToolBurst. The assistant bubble's Border drops itself into slot[0] on materialize; the tool card subscribes to bubble.SizeChanged and sets its own Width = bubble.ActualWidth - toolIndent. The two cards' left indent and right edges now stay exactly parallel as the bubble grows, regardless of content length.

Reset slot at each User entry boundary so tool cards never bind to a prior turn's bubble. Falls back to MaxWidth/AnchorLeft when no bubble exists (tool-only turn).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scott feedback: when an agent's reply bubble lands and the tool burst beneath it is fully terminal, fold the N step rows into a single collapsed summary (clickable chevron to expand). While tools are still running, keep showing them as per-step rows so each Running/Done pill stays visible.

Added ToolBurstStyle.Auto and made it the process-wide default. Auto resolves per burst at render time:
  - count == 1                        -> Plain (one inline row)
  - count >= 2 && all terminal        -> CompactSummary (1-line + chevron)
  - count >= 2 && any InProgress      -> Plain (live status visible)

CompactSummary's existing expand machinery (expandedToolChips HashSet) is reused, so the collapse/expand state persists for the session.

Exposed Auto in the exploration panel dropdown for testing the other styles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two follow-ups from Scott's screenshot:

1. The collapsed CompactSummary header used FlexRow(ColumnGap=6)+padding(12,8,12,8)+MinHeight=22 while BuildRow used Grid[Auto,Auto,Auto,Star,Auto]+margin(6,0,0,0)+bubblePadding+MinHeight=32. Rebuilt the summary header with the exact same Grid template, margins, padding, and MinHeight as the step rows so the chevron / lightning / Task label / Done pill axes line up vertically when the burst is expanded.

2. Removed the trailing FooterCaption(timeStr/TaskFooter()) from CompactSummary, TaskHeader, and FooterReframe returns. The assistant bubble above the tool burst already shows its own timestamp/model/tokens footer, so the time under the tool card was redundant noise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When an assistant message is followed by a tool burst in the same turn,
defer the timestamp/model/tokens footer so it renders BELOW the tool
card(s) instead of between the bubble and the tools. Order becomes:

  bubble -> tool card(s) -> timestamp/model/tokens

Implementation mirrors the existing bubbleSlot pattern: RenderAssistantEntry
accepts an Element[1] footerSlot; when supplied, the built footer is
handed back to the caller and the inline footer slot in the VStack
collapses to Empty(). The outer loop precomputes turn boundaries, hands
out a footerSlot whenever a tool entry follows in the same turn, and
splices the captured footer Element into timelineRows just after the
last entry of the turn (alongside the thinking indicator splice).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the assistant has produced a reply AND the tool burst would render
as a single visible row (one chip OR a collapsed multi-step summary),
embed the tool card inside the assistant bubble's content area instead
of rendering it as a sibling card below.

  bubble {
    text...
    [tool card]   <- nested
  }

In-flight multi-step bursts (Plain expanded) and bursts arriving before
the assistant reply still render externally so live progress stays
visible. Plain / TaskHeader / TaskList / FooterReframe styles never
nest — only Auto and CompactSummary opt in.

Implementation:
- RenderAssistantEntry gains an Element? nestedTool param. When set,
  the bubble wraps its markdown text in a VStack(8, text, nestedTool)
  so the tool card sits flush below the message with an 8px top gap,
  inside the bubble's existing padding/border/radius.
- RenderToolBurst gains a bool nested flag. In nested mode CardOf
  drops MaxWidth/HAlign.Left and the bubbleSlot Width binding (the
  parent bubble already constrains us); a new Wrap helper bypasses
  AnchorLeft and the toolLeftMargin/gutter outer margin so the card
  stretches inside the bubble.
- Outer loop precomputes turn boundaries, looks ahead from the last
  assistant entry of the turn for a contiguous tool burst, and asks
  BurstIsNestable to gate the decision (count==1 OR all-terminal under
  Auto/CompactSummary). Consumed orderedIdx positions are tracked in a
  HashSet so the external render branch emits Empty() for them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Markdown text has tight line-height with no trailing descender, so an
8px top gap reads as visibly tighter than the 8px bottom padding below
the nested tool card. Set the top gap to bubblePadding.Bottom + 4 so
the optical spacing matches the gap from the card to the bubble's
bottom edge across all PaddingDensity presets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The +16 indent on external tool cards exists so the card visually nests
inside the assistant bubble's text column. When the agent is still
'thinking…' and no reply bubble has streamed, that indent makes the
tool card hang further right than the thinking indicator above it.

When bubbleSlot is null (no assistant entry seen in this turn) drop the
indent so the tool card aligns flush under the thinking indicator
instead of jutting right.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
High priority fixes:
- AutomationName: only set bubble-level name when no nested tool card
  is present so Narrator can traverse into the nested card.
- Status colors: replace hardcoded Done-green / Running-orange / pill
  white with SystemFillColorSuccessBrush / CautionBrush and
  TextOnAccentFillColorPrimaryBrush so they adapt to dark/HC themes.
- Hover/pressed: 3 button surfaces now use SubtleFillColorTertiary
  (hover) and SubtleFillColorSecondary (pressed) themed brushes. The
  tool card uses the lighter Tertiary on hover for a more subtle look
  (Scott feedback) and to stay visible in dark/HC.
- SizeChanged handler leak on the bubble-Width binding fixed: subscribe
  on Loaded, detach on Unloaded with a stable handler reference so
  re-renders don't accumulate listeners.

Medium priority fixes:
- toolCardBgBrush: LayerOnAcrylicFillColorDefaultBrush
  → CardBackgroundFillColorDefaultBrush (semantic match — the bubble
  surface is opaque, not acrylic).
- Tool card MinWidth = 360 so the 5-column header grid doesn't clip
  when the assistant reply is short and the bubble shrinks below it.
- HC border thickness bumped to 2px via AccessibilitySettings probe
  (graceful 1px fallback if API throws in unpackaged hosts).
- BurstIsNestable now returns false when any tool errored — errors
  stay external so the failure is visually prominent.

Low priority fixes:
- (int)bubblePadding.Bottom + 4 → (int)Math.Round(...) for fractional
  density values.
- Remove redundant FontSize = 12 sets after Caption() (Caption already
  sets 12). Status pills bumped 10 → 11 for readability.
- Add uniform-CornerRadius assumption comments at two card sites.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The chat UI styling Kenny iterated on was being persisted to a local
preset (%APPDATA%\\OpenClawTray\\chat-exploration-presets.json with
IsDefault=true) that overrode the in-memory defaults on startup. New
installs and other users were therefore landing on the original code
defaults (Mica / Comfortable / Both avatars / 14px / 32px send button)
instead of the look reviewed by Scott.

Bake the preset values into the actual code defaults so a fresh install
matches the design without needing the JSON preset file:

  BackdropMode      Mica          -> Acrylic
  PaddingDensity    Comfortable   -> Cozy
  AvatarMode        Both          -> AgentOnly
  ComposerIconSize  14            -> 16
  SendButtonSize    32            -> 40

Updated in three places to keep them consistent:
- ChatExplorationState (the in-memory defaults applied at startup)
- ChatExplorationPreset record (defaults when a preset omits a field
  during deserialization)
- ChatVariationPresets.Calm (so users who hit Reset or pick the Calm
  variation land on the same baseline)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wrap BuildSection in a 2-col Grid with a transparent phantom chevron in col 0 (same glyph + 6px right margin) so the section's lightning glyph and the code block beneath it land at the same x as the header lightning above, regardless of density/font metrics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Composer (OpenClawComposer.cs):
- Paste image preview rendered above text in single user bubble
- X-button hover changes circular background opacity instead of
  fading the X glyph (uses opaque SolidBackgroundFillColor* brushes)
- Strip stray top/bottom line on TextBox via theme resource overrides
  (TextControlBorderThemeThickness / focused / pointerOver)
- Tighten composer<->actions-row gap (actionsRow margin -8/-4)

Reducer (ChatTimelineReducer.cs):
- UpsertAssistant reconcile path now scans backward for the most
  recent Assistant entry, stopping at User boundary, so streams of
  shape `text -> tool -> tool output -> final text` no longer
  duplicate the assistant text into a second bubble.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- AnchorLeft Grid: single Star col so card measure is bounded by chat viewport
- headerRow / summaryHeader: outer [Star, Auto] wrapping inner content + Done pill
- Summary Caption: TextWrapping=Wrap + MaxLines=1 + CharacterEllipsis for safe trim
- CardOf nested branch: MinWidth=360; external Sync clamps bubble width with Math.Max(360, w)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bubble CornerRadius (16) equals bubblePadding.Right (16) in Cozy preset,
so the bubble's corner arc reaches the inner content edge and clips the
Done pill at the card's right side. Move the nested card in by a full
bubbleRadius (and drop MinWidth=360 which forced the card wider than
the bubble in narrow viewports) so the pill is comfortably inside the
bubble's rounded shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
shanselman and others added 28 commits May 22, 2026 15:26
# Conflicts:
#	src/OpenClaw.Tray.WinUI/Pages/WorkspacePage.xaml
# Conflicts:
#	src/OpenClaw.Tray.WinUI/Pages/SandboxPage.xaml
Restructures SessionsPage to match the Permissions/Connection shell

(ScrollViewer + StackPanel, MaxWidth 900, 4 px grid) and removes UI

drift against the openclaw-design skill:

- Replace literal emoji empty-state and ad-hoc colors with Segoe

  Fluent glyphs and SystemFillColor* theme brushes.

- Status dot is now a 4-state Fluent map (Success / Caution /

  Critical / Neutral) with a hover tooltip describing the state.

- Primary action is an AccentButton 'Open chat' (Fluent 2

  rest/hover/press/disabled via AccentButtonStyle, no Foreground

  overrides); destructive Reset/Compact/Delete moved to a MenuFlyout

  behind the row's overflow button.

- Filter out cron-spawned sessions (key slot == 'cron') so the

  conversation list mirrors the macOS companion.

- Deep-link from a session row to the Chat surface: SessionsPage

  stores the session key on HubWindow.PendingChatSessionKey;

  ChatPage.ShowFunctionalSurface consumes it and remounts the

  OpenClawChatRoot with initialThreadId, so both the timeline and

  the composer's session dropdown render the requested session.

- Robust MenuFlyout click routing via ResolveSessionKey, which

  walks back to MenuFlyout.Target when DataContext propagation into

  popup items fails (canonical WinUI binding quirk).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
_mountedThreadId only records what we asked for, not what the user

later picked inside OpenClawChatRoot's composer dropdown. Comparing

against it could leave the chat root selected on a different session

than the row the user just clicked, sending the next message to the

wrong thread (raised in PR openclaw#514 review).

Treat any pending session key from SessionsPage as an unconditional

remount so the deep-link always wins.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	src/OpenClaw.Tray.WinUI/Helpers/FluentIconCatalog.cs
#	src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml
# Conflicts:
#	src/OpenClaw.Tray.WinUI/Pages/UsagePage.xaml.cs
* Expand CI test coverage

Add CI build and coverage-wrapped test steps for OpenClaw.Connection.Tests, OpenClaw.WinNode.Cli.Tests, OpenClawTray.FunctionalUI.Tests, and OpenClawTray.OnboardingV2.Tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Tolerate hosted Windows token owner warnings

Allow the wide ACL token warning test to accept either the ACL warning or the owner warning emitted by hosted Windows runners while still requiring the WinNode warning and successful command execution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add live tools/list discovery, strengthen WinNode CLI drift coverage, and refresh MCP bearer-token docs.
…elper (openclaw#522)

Both classes had zero test coverage. These tests cover:

MenuDisplayHelper (21 tests):
- GetStatusIcon: Connected/Connecting/Error/unknown enum value
- GetChannelStatusIcon: healthy/intermediate/empty/null/unknown status
- TruncateText: short/null/whitespace/exact-max/over-max/custom-length
- FormatProviderSummary: singular vs plural
- GetNextToggleValue: on/ON/On → off; off/null/empty/other → on

MenuSizingHelper (24 tests):
- ConvertPixelsToViewUnits: 96/192 dpi, zero/negative pixels, zero dpi fallback, minimum-1 clamp
- HasDpiOrScaleChanged: same DPI+scale, different DPI, different scale, sub-tolerance scale delta, zero-DPI normalisation, NaN/0 scale normalisation
- CalculateWindowHeight: content fits/exceeds, zero/negative content, zero work area, work area smaller than minimum, custom minimum

All 45 tests pass. Pre-existing 8 failures in ExecApprovalV2NormalizationTests are Windows-path tests that fail on Linux runners — not caused by this change.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep dropping inbound chat and agent timeline events that lack a canonical sessionKey, but raise a one-shot visible diagnostic without including dropped payload content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
)

* feat: add standalone SetupEngine with transactional pipeline

New headless setup engine that installs WSL, configures OpenClaw gateway,
pairs operator/node connections, and verifies end-to-end connectivity.

- Transactional pipeline with retry, rollback, and crash-recovery journal
- Structured JSONL logging with secret redaction
- v2 signature fix for local gateways in GatewayConnectionManager
- All 15 steps working E2E in headless mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: gateway bind lan + keepalive step for reliable WSL2 connectivity

- Configure gateway with bind=lan (0.0.0.0) instead of loopback to avoid
  unreliable WSL2 localhost port forwarding
- Add StartKeepaliveStep (step 16) to keep distro alive after setup
  completes, ensuring tray connects instantly on launch
- Add default-config.json and design doc

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(setup-engine): config-driven capabilities, settings, and WSL/gateway config

- Expand SetupConfig with nested WslConfig, GatewayConfig, CapabilitiesConfig,
  TraySettingsConfig, and PairingConfig — zero hardcoded values
- Register node capabilities (stub INodeCapability) before ConnectAsync so
  gateway stores caps/commands from hello message
- Write settings.json after node pairing (EnableNodeMode=true + cap toggles)
  using merge logic that preserves existing user settings
- Make WSL wsl.conf generation config-driven (user, systemd, interop, etc.)
- Make gateway config-driven (bind mode, auth, health timeout, extra config)
- Write keepalive marker file to prevent tray duplicate keepalive
- Add fully commented default-config.json with all configurable properties

Verified: clean build (0 errors/0 warnings), full E2E run in 118s,
tray auto-connects with capabilities registered.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(setup-engine): drain pending device/node approvals at end of setup

Adds DrainPendingApprovalsAsync to VerifyEndToEndStep that iteratively
approves any remaining pending device or node pairing requests. This
ensures the tray launches with zero 'Pairing approval pending' badges.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(setup-engine): remove remote gateway mode (local-wsl only)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add SetupEngine WinUI app with fluent onboarding flow

Standalone unpackaged WinUI app (no FunctionalUI dependency) with:
- Welcome page with lobster icon, info card, V2 text strings
- Capabilities page with 2-column grid, icons, descriptions, toggles
- Progress page with step groups, badges (spinner/check/error), log viewer
- Complete page with success/error state, launch tray button

Features:
- Mica backdrop + extended title bar
- DPI-aware window sizing (720x700 logical)
- UAC manifest (asInvoker) to avoid elevation prompt
- --headless bypass for automation
- Config-driven defaults from SetupConfig

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish setup engine UI to match V2 onboarding flow

- Welcome: flex layout, full V2 text, 'Install new WSL Gateway' button,
  confirmation dialog, 'Advanced setup' link (opens tray connection page)
- Title bar: lobster icon + 'OpenClaw Setup' text, 36px height
- Permissions page: 5 rows (notifications, camera, mic, location, screen),
  live status checks, 'Open Settings' buttons, 'Refresh status'
- Complete page: party popper, amber Node Mode banner, startup toggle,
  'Finish' button with tray launch + optional registry startup entry
- Window height increased to 820px for better step row spacing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Require config file — no hardcoded defaults

- Remove all hardcoded defaults from SetupConfig (DistroName, GatewayPort, BaseDistro, etc.)
- Both UI and headless exe now require a config file to run
- UI auto-loads bundled default-config.json from AppContext.BaseDirectory
- Headless Program.cs exits with error if no config found
- Added Content Include in csproj to bundle default-config.json with exe

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix cleanup, add error UX, launch tray via deep link

- CleanupStaleDistroStep: wsl --shutdown + delete orphaned VHD directory
- CompletePage: show error message + 'View full log' link on failure
- CompletePage: kill old tray, launch via openclaw://chat protocol
- Update SETUP_ENGINE_REDESIGN.md to reflect current implementation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add setup engine wizard flow

* Polish setup wizard UI and WSL setup resilience

- add interactive gateway wizard rendering for SetupEngine UI
- make wizard messages render links and device codes inline
- refine progress/log layout and setup failure visuals
- fix wizard retry/skip behavior and credential precedence
- harden WSL cleanup, base distro reuse, and missing WSL handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Replace old onboarding/setup with out-of-process SetupEngine.UI

- Add RollbackAsync to 6 SetupEngine steps for --uninstall support
- Add UninstallAsync to SetupPipeline (reverse rollback execution)
- Add --uninstall, --confirm-destructive, --json-output CLI flags
- Replace ShowOnboardingAsync with out-of-process SetupEngine.UI launch
- Rewrite CliUninstallHandler and SettingsPage uninstall to use SetupEngine
- Delete LocalGatewaySetup (4000+ lines), Onboarding, OnboardingV2 projects
- Extract GatewayConnectorInterfaces and WslCommandRunner from deleted code
- Fix StartupSetupState to scan per-gateway dirs for device tokens
- Simplify WSL keepalive to direct wsl process spawn
- Add SetupEngine to build.ps1 with post-build copy to WinUI output
- Set production defaults: DistroName=OpenClawGateway, GatewayPort=18789

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add E2E first-time setup CI test

Create tests/OpenClaw.E2ETests/ project with end-to-end tests that exercise
the full setup pipeline headless via SetupEngine CLI, spawn the tray app,
and verify operator+node connectivity through MCP app.status/app.nodes calls.

- E2ESetupFixture: runs Program.Main() headless, patches settings for MCP,
  spawns tray process, polls connection status, cleans up via uninstall
- SetupAndConnectTests: verifies connected state and node capabilities
- McpClient: JSON-RPC client for MCP HTTP server verification
- CI workflow: parallel e2e job with test artifact upload for debugging
- SetupEngine.csproj: add RuntimeIdentifiers for RID-specific test builds

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: setup engine reliability fixes and unit tests

Hanselman adversarial review (Opus + Codex) identified 18 issues.
Fixed 12 reliability bugs across the setup engine:

HIGH consensus (both models):
- RetryExecutor: wrap action() in try/catch to prevent pipeline crashes
- CommandRunner: catch Win32Exception on process.Start()
- ConfigureGatewayStep: shell-escape ExtraConfig values

Verified LOW consensus:
- TryResetReloadModeAsync: use CancellationToken.None in finally
- TransactionJournal: catch IOException on writes
- CleanupStaleGatewayStep: delete setup-state.json from both AppData and LocalAppData
- AutoApprove: fall back to BootstrapToken when SharedGatewayToken is null
- TrayArtifactCleanup: protect DeleteFileIfExists with try/catch
- StartKeepaliveStep: remove unused stdout/stderr redirect
- PairOperatorStep: unsubscribe DeviceTokenReceived handler
- Program: run TrayArtifactCleanup on Cancelled outcome
- CleanupStaleDistroStep: retry VHD directory deletion with backoff

Added 66 unit tests in new OpenClaw.SetupEngine.Tests project:
- RetryExecutorTests (11), SetupPipelineTests (14),
  TransactionJournalTests (9), SetupLoggerTests (7),
  SetupConfigTests (18), SetupContextTests (7)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace OnboardingV2 tests with SetupEngine tests, fix E2E restore

- Replace OnboardingV2.Tests build/run steps with SetupEngine.Tests (66 unit tests)
- Remove empty OnClawTray.OnboardingV2.Tests project (superseded by SetupEngine)
- Drop --no-restore from E2E build step to fix RID-mismatch NETSDK1004

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: fix SetupEngine.Tests restore and rename e2e job

- Add -r win-x64 to SetupEngine.Tests build (allows implicit restore for RID-specific deps)
- Add -r win-x64 to SetupEngine.Tests run step
- Rename e2e job to e2etests for clarity

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address ClawSweeper P1 findings — gateway cleanup, URL validation, release packaging

- CleanupStaleGatewayStep: preserve SSH-tunneled and non-local gateway
  records instead of deleting by URL match alone
- InstallCliStep: validate HTTPS scheme, shell-quote URL, add
  --proto '=https' --tlsv1.2 to curl
- ci.yml: publish SetupEngine.UI into release package
- Add 8 unit tests covering gateway preservation and URL validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Harden setup engine: WSL lockdown, secure defaults, token handling, diagnostics

- Default gateway bind from 'lan' (0.0.0.0) to 'loopback' (127.0.0.1)
- Add ValidateWslLockdownStep: verify user, dirs, ownership after configure
- Replace --token argv with OPENCLAW_GATEWAY_TOKEN env var (9 call sites)
- Add ExistingConfigDetector for dynamic replacement dialog on WelcomePage
- Remove unimplemented OperatorScopes/NodeScopes/CliScopes from PairingConfig
- Add port conflict detection (ss -tlnp) and improved failure diagnostics
- Add RedactTokens helper for log sanitization
- Default SkipPermissions to false, add fallback tray exe path
- Fix docs drift: step count, default claims
- Add UI step group mappings for validate-wsl-lockdown and run-wizard
- 279 new lines of unit tests (lockdown, bind validation, token redaction, etc.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Harden setup engine reliability

Add setup run locking, append-only recovery journals, atomic persistence, bounded command output and rollback handling, UI cancellation/error guards, wizard loop bounds, isolated local data support, and expanded token redaction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix setup engine WSL isolation

Ensure setup runs privileged WSL configuration as root so imported base distros with non-root defaults still configure correctly. Align local AppData override handling across SetupEngine, tray setup detection, keepalive, and e2e isolation, and accept numeric setup-state phases written by the new engine.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stabilize setup engine CI tests

Serialize SetupEngine tests that mutate process-wide environment variables and make the E2E fixture wait until app.nodes reports an online node with capabilities before allowing tests to proceed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Harden setup engine approval and rollback flows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Bring setup window to foreground

Focus SetupEngine.UI when launched from the tray and briefly make the setup window topmost so user-initiated setup actions are visible. Reuse the tray icon for the setup app so both companion surfaces share branding.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use isolated identity path for setup detection

Prefer OPENCLAW_TRAY_DATA_DIR for tray identity checks in isolated runs so startup setup detection sees the per-gateway device tokens written by SetupEngine and does not relaunch setup after a successful install.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix empty cron and event stream states

Mark cron loading complete when the gateway returns an empty jobs list, and place the Event Stream empty state in the main content row so the page does not look blank when no agent events have arrived yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix event stream page localization crash

Remove an invalid x:Uid from the Event Stream clear-button TextBlock. The matching resource includes a Content property, which TextBlock does not support, causing AgentEventsPage to throw during XAML load and making navigation appear to do nothing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Harden setup engine gateway configuration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix connection dashboard token link

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Improve setup wizard option hints

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish setup UI assets and welcome animation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address setup hardening feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Harden setup approval drain

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Harden setup wizard validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish setup progress indicators

Use Fluent icon glyphs for completed and failed setup steps, add a subtle pending indicator, and give the active progress ring enough space to render cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix tray UI test runtime bootstrap

Align the UI test WinAppSDK bootstrap/runtime install with the Microsoft.WindowsAppSDK package version and add fixture startup diagnostics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix WSL keepalive startup

Use the active gateway registry record when deciding whether tray startup should keep the local WSL gateway alive, and add unit and e2e coverage for the keepalive process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address remaining setup feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Scott Hanselman <scottha@microsoft.com>
Opt the tray process into mouse-in-pointer before WinUI initialization so precision touchpad scrolling works in affected devbox/RDP-style sessions.

Includes startup diagnostics and an OPENCLAW_DISABLE_MOUSE_IN_POINTER opt-out for regression comparison.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds focused tests for ChannelStartResult and ConfigPatchResult computed properties.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces LINQ sorting with in-place list sorting and avoids repeated QR-channel lookups while preserving aggregate order.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds additive tests for ActivityStreamService item handling, filtering, clearing, events, and support-bundle formatting.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds connection helper tests and removes the generated stub state that would otherwise create a warnings-as-errors compile issue.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces timing-based auto-approve waits with deterministic synchronization and keeps production reconnect delay behavior unchanged by default.\n\nRefs openclaw#460\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds NuGetAuditMode=all to test build props so test restores audit transitive package dependencies like source restores.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gate E2E tests behind explicit local opt-in while preserving CI execution, centralize source-file discovery, reduce selected waits, and require explicit truthy E2E env values.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Polish the WinUI config editor with permission-aware state, live validation, persistent JSON diff preview, full-width layout, safer read-only/no-read handling, and maintainer fixes for stale-base retry and sensitive field detection.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes identified in comprehensive test audit:

HIGH Severity Fixes:
- SetupPipelineTests: Fix always-pass test for skippable steps
  - Now verifies StepOutcome.Skipped via progress events
- AppCapabilityTests: Expand trivial tests with edge cases
  - Added case sensitivity checks, prefix validation
  - Verified Commands/CanHandle consistency
- SshTunnelServiceTests: Improve incomplete test assertions
  - Added verification of pre-reset state
- RadioButtonsTests: Add functional behavior tests
  - Added tests for valid selection and empty items
  - Expanded from 1 to 3 tests

All modified tests now verify actual behavior rather than
just property values.

Related to: openclaw#529 test quality improvements
* Fix tray menu toggle flicker

Add a tray-menu-specific ToggleSwitch style that preserves the WinUI control shape while removing the default switch reposition animation that flickers on menu open. Wire the tray connection and permissions toggles through the shared helper so their state updates remain centralized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix tray menu toggle style lookup

Use the tray menu window instance to resolve the local ToggleSwitch style after scoping it to TrayMenuWindow resources.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: kenehong <22486640+kenehong@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add markdown rendering (incl. tables) to native chat bubbles

Renders block-level Markdown — headings, lists (incl. GFM task lists), tables, blockquotes, fenced/inline code, thematic breaks, strikethrough, underline — inside OpenClaw native chat bubbles. The previous inline-only sanitizer remains the fast path for plain-prose bubbles; bubbles containing block markdown route through a new md4c-based pipeline.

Pipeline:

  1. Vendored md4c SAX parser at src/OpenClaw.Shared/Markdown/Md4c/* (copied verbatim from microsoft-ui-reactor @01bb3fbc; see VENDORED.md).

  2. ChatMarkdownAstBuilder translates md4c callbacks into a pure-data ChatMarkdownDocument AST.

  3. ChatMarkdownRenderer maps the AST to a FunctionalUI Element tree.

  4. OpenClawChatTimeline.SafeMarkdownText gates on ContainsBlockMarkdown and falls back to plain TextBlock if rendering fails.

Security posture (preserved end-to-end):

  * Links flattened to inert `display (href)` text — never a clickable Hyperlink.

  * Images flattened to `[Image: alt]` — no BitmapImage / no remote fetch.

  * Raw HTML suppressed via md4c NoHtml flag; residual MdRawTextBlock rendered as inert monospace.

  * Hard 256 KB char input cap; oversized payloads truncate to a single inert paragraph.

  * Renderer constructs no Hyperlink / BitmapImage / click or pointer handler.

Robustness:

  * Builder Build() fully resets state per call so reuse can't bleed style/link/image/code/table state.

  * Nested links and images-in-links handled via Stack<(StringBuilder, string?)> / Stack<StringBuilder> so outer span buffers survive inner spans.

  * TextCallback handles SoftBr/Br/NullChar before link/image depth capture so line-breaks and U+FFFD aren't lost inside link display / image alt.

  * EmitInlineText routes flattened text into outer link/image buffers when the LeaveSpan emit still sits inside an outer span.

  * Soft line breaks render as a space Run; hard breaks render as a LineBreak (preserves CommonMark semantics).

  * ContainsBlockMarkdown is a cheap O(n) heuristic — handles up to 3 leading spaces (CommonMark), requires whitespace/EOL after #, detects 3+ `-/*/_` thematic breaks, and fenced code with leading indent.

  * Bounded 64-entry LRU AST cache in ChatMarkdownRenderer.Render(string) keyed by source so per-tick FunctionalUI re-renders don't reparse stable bubbles. Element trees are always rebuilt (WinUI parent-slot rule).

Tests:

  * 18 md4c parser smoke tests.

  * 24 AST builder tests covering tables, lists, headings, link/image flattening, raw-HTML suppression, the 256 KB cap.

Validation: ./build.ps1, Shared 2000 passed/29 skipped, Tray 843 passed.

Reviewed via two passes of the Hanselman dual-model (claude-opus-4.6 + gpt-5.2-codex) adversarial review — 12 findings addressed in pass 1, pass 2 clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix tight-list rendering: surface inlines as implicit paragraphs

md4c does NOT emit explicit P open/close events for the raw inline
text inside tight list items (the CommonMark-compliant behavior).
The AST builder was assuming a P would always wrap text inside Li,
so it never drained _inlines at LeaveBlock(Li). The result in real
chat traffic was:

  * every bullet rendered empty (Li.Children was empty)
  * the accumulated tight-list text bled into the next real
    paragraph in the bubble, producing a megaparagraph of all the
    list content jammed together.

Two fixes in ChatMarkdownAstBuilder:

1. LeaveBlock(Li): drain any pending _inlines into an implicit
   MdParagraph child before constructing the MdListItem, so the
   simple tight-list case renders content under the bullet.

2. EnterBlock(*): if the current parent is Li or Quote and we have
   pending inlines, flush them into an implicit MdParagraph FIRST.
   This handles the nested-block-inside-tight-Li case (e.g. a
   nested Ul) where the outer Li's text would otherwise bleed into
   the first nested item.

Adds two regression tests in ChatMarkdownAstBuilderTests:

  * TightList_LiContentSurfacesAsImplicitParagraph - asserts each
    bullet has a paragraph child with the expected inlines, and
    that the trailing paragraph contains ONLY its own text.
  * TightList_NestedItemsAlsoSurfaceContent - asserts the outer Li
    contains both a paragraph and the nested MdList, and that the
    nested items also have paragraph content.

Root cause was not caught by either Hanselman review pass because
the existing list tests only counted items and the loose-list path
(blank lines between items) does get explicit P from md4c.

Validation:
  build.ps1: green
  Shared tests: 2002 passed / 29 skipped
  Tray tests: 843 passed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Decode HTML entities in markdown chat rendering

md4c emits MarkdownTextType.Entity events for syntactically-valid
HTML entity references (e.g. &amp;, &#x41;, &copy;). The AST builder
previously fell through to the default case and dropped them,
producing rendering like "AT&amp;amp;T" instead of "AT&amp;T".

Add an Entity case that decodes the token via a new DecodeEntity
helper and routes it through the existing image-alt / link-display
/ inline-text paths, matching how Normal text is handled.

DecodeEntity covers:
- Decimal numeric refs (&#NN;)
- Hex numeric refs (&#xHH; / &#XHH;, either case)
- Named entities via the existing internal Md4cEntity.EntityLookup
  table (including two-codepoint sequences like &fjlig; -> "fj")
- CommonMark folding: NUL, surrogates, and codepoints above
  U+10FFFF collapse to U+FFFD; the in-loop > 0x10FFFF guard
  prevents int overflow on long hex inputs
- Codepoint0 == 0 sentinel (md4c's "no replacement") returns the
  raw token verbatim
- Unknown named entities pass through verbatim
- SafeConvertFromUtf32 defensively rejects out-of-range values so
  a corrupted entity table can never throw

Add 13 regression tests covering: ampersand, copyright, common
named entities (theory with 8 cases), decimal and hex numeric refs,
supplementary-plane codepoints (surrogate pair), NUL, surrogate
folding, unknown entities, entity inside a strong span, max-valid
codepoint boundary, above-max replacement, and two-codepoint
named entity decoding.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Render visible 1px gridlines in markdown tables

Tables in chat bubbles previously had no borders, making columns
visually ambiguous. The HTML dashboard renders tables with cell
borders; bring the native chat in line.

Each cell paints a 1px top stroke (when rowIndex > 0) and a 1px
left stroke (when colIndex > 0); an outer Border on the grid closes
the bottom and right edges. The result is a uniform 1px grid with
no double-thickness lines at internal cell boundaries.

Color is #40808080 (semi-transparent gray) so it reads well in both
light and dark themes without depending on a theme resource. The
header row gets a subtle Theme.CardBackground tint and slightly
larger cell padding.

A single SolidColorBrush is cached lazily in a static property
(TableGridBrush) and reused by every cell border and the outer
wrapper, avoiding per-cell brush allocation on each table render.
WinUI 3 brushes are shareable across elements; the renderer runs
on the dispatcher thread so the lazy-init is single-threaded.

ParseHex parses the hex constant into a Windows.UI.Color and falls
back to Microsoft.UI.Colors.Transparent on malformed input.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Reserve real layout spacing after markdown list markers

ClawSweeper flagged that list bullets and numbers rendered flush
against their content. The previous implementation packed two
trailing spaces into the marker TextBlock and used a zero-spacing
HStack, but WinUI's text layout collapses trailing whitespace
inside a TextBlock so no actual gap appeared on screen.

Strip the trailing whitespace from the marker strings and give the
marker TextBlock an explicit MinWidth so the content column starts
at a stable x-coordinate. Bullets use 10 px (tight, browser-like);
ordered markers use 18 px so "10." still leaves a small gap before
the content and single-digit numbers stay visually aligned with
multi-digit ones in the same list.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Regis Brid <rbrid@github.com>
* Fix native chat UI bugs in tray app

Three bugs in the WinUI 3 native chat timeline:

1. Collapsed tool-burst groups momentarily expanded each time a new tool
   started running. The CompactSummary now stays collapsed during in-flight
   work and surfaces progress via a 'running' chip on the right side that
   flips to 'done' on completion. BurstIsNestable was widened so the group
   keeps its collapsed shell while children are still streaming.

2a. User-bubble text selection was invisible against the accent-colored
    bubble background (white-on-light-blue). User bubbles now paint the
    selection band with SystemAccentColorDark2 (or the system high-contrast
    highlight color in HC mode) so the white glyphs remain readable while
    selected. Brush + AccessibilitySettings instances are cached per
    DispatcherQueue via ConditionalWeakTable to respect WinRT thread
    affinity, with in-place Color mutation when the accent changes and a
    cache reset on HighContrast read failure so a transient COM error
    isn't sticky. Off-thread callers (tests) fall back to one-shot
    allocations.

2b. The visible text of a user bubble disappeared after a drag-select +
    mouse-up triggered a reconciler re-render. Root cause: assigning
    TextBlock.Text on a selection-enabled TextBlock immediately after a
    selection gesture left the glyph layer blank until the next focus
    change. Fix: render the user-bubble body via Inlines (Run) instead of
    Text, paired with the FunctionalUI ConfigureTextBlock skip-guard that
    preserves Inlines when the incoming Text is empty. A weak per-element
    plain-string cache avoids rebuilding Inlines on no-op re-renders.

Five rounds of adversarial dual-model (Opus + Codex) review applied. All
HIGH-consensus findings addressed; LOW-consensus items either rejected
with rationale (BurstIsNestable widening is the intended behavior) or
deemed N/A (Clear+Add only affects mutating streams, user bubbles are
immutable after send).

Validated: ./build.ps1 green, Shared.Tests 1958 passed / 29 skipped,
Tray.Tests 843 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Retrigger CI

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix user bubble inline cache guard

Avoid returning from the selectable inline cache after WinUI has cleared the TextBlock inlines during a re-render.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Scott Hanselman <scott@hanselman.com>
Replace the local setup path that exported/imported an existing Ubuntu distro with a clean, app-owned WSL install flow. Tighten preflight, cleanup, install-path validation, rollback, documentation, and regression coverage so setup has explicit success and failure conditions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranjeshj

Copy link
Copy Markdown
Owner Author

Closing in favor of upstream PR: openclaw#559

@ranjeshj ranjeshj closed this May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants