fix(chat+local): reduce side-panel chat-vanish + cure local build wedges#136
Merged
Conversation
The verify step used `--query "[?contains(tags, 'latest')]"`, but JMESPath `contains()` throws on entries whose `tags` is null (the buildx provenance/SBOM attestation manifests have null tags), so the step exited 1 and turned the job red even though the multi-arch push SUCCEEDED (latest → OCI image index with amd64 + arm64 children, confirmed in ACR). Replace it with a genuine assertion: read the :latest image index's child platform architectures and fail loudly unless BOTH amd64 AND arm64 are present — so a future single-arch flatten is caught, and a successful multi-arch push reports green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…table from home
A thread started from a user's home was not creatable / not reachable: routing to
the user's partition failed "No node found", because the user had no partition.
TestUsers.SampleUsers() seeded only the User/{name} catalog entry, never the
PARTITION-ROOT node ({name} at namespace="") that real onboarding
(UserOnboardingService.CreateUser) writes — so sample users were not "properly
onboarded": their partition never existed, and a thread had nowhere to land
(routing → "No node found", a subscribe that never resolves = the not-reachable
wedge).
Seed each sample user's partition-root alongside the catalog entry (the onboarded
shape). ThreadCreatableFromHomeTest pins it: a thread is creatable from the owner's
home, lands in their writable partition (Roland/_Thread/…, not the system User
partition), and is REACHABLE (the node stream resolves — never a hung subscribe).
Verified: repro green; full MeshWeaver.Threading.Test 122/122 (the partition-root
seeding is additive — User/{name} stays — so no existing consumer breaks).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eadViewModel
Two bugs the 10-message GUI stress test (SidePanelChatTenMessagesTest) surfaced and
measured against the real DevLogin portal:
1) Routing — the home (/User/{id}) anchored a new thread in the SYSTEM 'User'
partition, which the user lacks Thread permission on (StartThread denied → no
thread → the side-panel chat tears down; on Postgres the 'user' schema may not
exist → 42P01). NormalizeContextPath now maps `User/{id}[/…]` → `{id}` so a thread
started from the user's home lands in their OWN writable partition
(Roland/_Thread/…). With this, round 1 actually runs end-to-end.
2) Serialization → vanish — the thread per-node hub serialised MeshWeaver.AI.
ThreadViewModel with the FULL-name $type because its registry lacked it; a hub that
registered it under the short name read it back as an untyped JsonElement (renders
empty / reactive waits time out) → render storm → the Blazor circuit DROPPED after a
round (the "GUI vanishes" wedge). Register ThreadViewModel under BOTH names in
AddAITypes (same dual-registration as User/UserActivityRecord).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e — cures untyped reads The polymorphic $type discriminator for an UNregistered type defaulted to the namespace-qualified full name (TypeRegistry.FormatType → type.FullName). A hub that registered the type under its short name (the standard WithType(typeof(T), nameof(T)) shape) then could not match the full-name $type and read the value back as an untyped JsonElement — "renders empty / reactive waits time out". That is the root of the whole wedge class: the chat "GUI vanishes after a round" (ThreadViewModel), the atioz untyped-storm outage (_provider/_policy, AgenticPension business types), and the sync-hub layout-type churn (PivotConfiguration / LayoutAreaDefinition / CatalogGroup / MeshNodeEditorField / GoogleMaps types …) that destabilised the composer. Default FormatType to type.Name (short) instead of type.FullName. An unregistered type now serialises as e.g. "ThreadViewModel", which any hub that registered it under its short name resolves — curing the untyped read at the default, for the whole class at once. Short-name collisions across namespaces are resolved by registering the colliding types explicitly (full registration is still recommended; the unregistered-type warning now guides it and its message is updated to match the new reality). Blast radius measured locally: only the 3 TypeRegistryTest format/warning tests that pinned the old full-name default needed updating. Messaging.Hub.Test 70/70, Data.Test 271/271, Hosting.Monolith.Test 278/279 (1 skip) — zero collisions across real domain types in cross-hub sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nish)
Submits TEN messages in one thread through the side-panel Monaco composer and asserts
after every round that the message landed AND the chat never vanished (composer stays
mounted, the Blazor circuit doesn't drop — reconnect modal stays hidden, no /login
bounce). The browser-level guard for the chat-creatable / chat-vanish work: it drove out
the partition-at-onboarding, /User/{id} routing, ThreadViewModel-untyped vanish, and the
short-name $type-default fixes. Skips when E2E is off (PortalFixture); runs against the
real DevLogin portal with a model, so the rounds actually execute.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…error law ActivityStatusExtensions: the foundation for status-driven continuations and status-gated rendering. IsTerminal (everything but Running — a transition into it triggers a continuation), IsSuccess (Succeeded/Warning — the ONLY states where typed content ContentAs<T> should be read), IsError (Failed/Cancelled — emergency mode), and the FollowupStatuses set. 18 tests pin the law, incl. the invariant that every terminal status is success XOR error (so "only-type-on-success, else-emergency" is total — no gap, which is exactly the untyped-content wedge cause). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…grouped by domain
Belt-and-suspenders on the short-name $type default: explicitly register the framework
content/config records that serialise INSIDE control/node state, so the reflection sweep
(IUiControl/Skin/StreamMessage) misses them — they surfaced untyped on sync hubs and
churned the layout. With the short-name default these resolve anyway; explicit
registration makes the contract documented + collision-safe. Grouped by what makes sense:
- Data (DataExtensions): LogMessage, UserInfo (ActivityLog content)
- AI (AddAITypes): AgentPluginReference, AgentHandoff (AgentConfiguration content)
- Layout (AddLayoutTypes): LayoutAreaDefinition; Pivot{Configuration,Dimension,Aggregate};
Catalog{GroupingConfig,SectionConfig,SortConfig,GridConfig,CatalogGroup,
SearchResultGroup,GroupedSearchResult}
- Graph (WithGraphTypes): MeshNodeEditorField
- GoogleMaps (AddGoogleMaps): MapOptions, LatLng, MapMarker, MapCircle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Poll the user/assistant bubble COUNT (>= i+1) instead of waiting on a specific .Nth(i) bubble (brittle when bubbles re-key as the side panel rebinds to the created thread), and Escape + select-all + backspace before typing each turn — the robust pattern proven by the 6-message ChatRepeatedlyNoVanishTest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er, not StartThread The 10-message test's "2nd user bubble never appears" was a real bug: threadPath (which the submit path reads to choose StartThread vs SubmitComposer) was set ONLY inside the post-creation readability subscription. So a send issued before that subscription fired saw threadPath empty and re-StartThread'd — creating a SECOND thread, and on the user home routing to the un-writable 'User' partition (denied) → the message silently dropped. Set threadPath = path the moment onCreated fires (the thread node exists once CreateNodeResponse succeeds); the REDIRECT still waits for readability (the blackout guard). Subsequent sends now deterministically drain through the existing thread via SubmitComposer → GetMeshNodeStream(threadPath).Update(PendingUserMessages) — the canonical submission pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g clobbering threadPath
Two real defects on the multi-message side-panel path, both surfaced by
SidePanelChatTenMessagesTest:
1. ns normalization was applied only to navNs, not the safeContext/createdBy fallbacks.
A raw "User/{id}" fallback routes StartThread to the un-writable system 'User'
partition (denied). Normalize the FINAL resolved ns so it always anchors in the
owner's writable partition regardless of source.
2. The ViewModel setter coalesced threadPath with `??`, but the side-panel control
rebuilds with ThreadPath = ContentPath ?? "" (an EMPTY STRING) on re-render before
the just-created thread's SetContentPath lands. `?? ` only guards null, so the empty
string CLOBBERED a freshly-set threadPath → the next send re-StartThreads a second
thread. Guard on IsNullOrEmpty (matching the sibling lines that already do).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rtition
The side panel opened on a user home (/User/Roland) resolved the chat namespace to the
bare system-managed 'User' partition: NormalizeContextPath mapped User/{id} → {id} but
left bare 'User' (1 segment) as-is, and 'User' isn't a reserved route partition, so it
survived. StartThread then targeted 'User' → AccessControlPipeline denies the
CreateNodeRequest → the DeliveryFailure reaches the portal's GLOBAL error handler and
surfaces a BLOCKING "Something went wrong: Access denied … lacks Thread permission on
'User'" modal that OVERLAYS the composer — so the editor can't be typed into and the
message never submits (the SidePanelChatTenMessagesTest "2nd message never lands" + the
"Area not found: area Roland on hub User" home both trace here).
- NormalizeContextPath: bare 'User' → "" (no {id} to map to, and 'User' is un-writable),
alongside the existing User/{id} → {id}.
- ns derivation: normalize EACH candidate and take the first NON-EMPTY result, so when
navNs/safeContext normalize to "" the thread anchors on createdBy (the owner's writable
partition) instead of an empty namespace.
Verified end-to-end: with this fix the side-panel chat runs THREE full rounds with no
denial, no modal, no "No suitable agent" (was failing at round 2). A separate round-4
side-panel reset ("New Thread") remains under investigation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…resolution re-emit The side-panel content resolution (PathResolver.ResolvePath) is a LIVE stream: right after a thread is created its node is not yet readable (first emissions are transient null / split-onto-the-parent-partition), and it re-emits again on every chat round as the thread node updates. The handler wiped the panel to an empty "New Thread" via SetContentPath(null) on ANY null/split emission — so a healthy open thread VANISHED mid-session (the SidePanelChatTenMessagesTest "chat disappears after a few rounds" — round-4 footer=0; the empty "New Thread" + blank composer in the round-4 screenshot). Filter to the FIRST VALID resolution (non-null, prefix == contentPath or empty remainder) and Take(1): transient not-ready states are skipped (so a just-created thread resolves once it is readable — no initial-null wipe), and later re-emits never wipe the open thread. A genuinely unresolvable path (deleted thread) yields no valid resolution → the Timeout clears it. Verified: all four sampled rounds keep footer=1, zero footer=0 vanishes (was a hard round-4 reset before). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ub principal The submission watcher's Throttle/Subscribe continuation captures whatever ExecutionContext was active at Subscribe time — which can be the thread hub's own address, a hub-shaped principal (sync/ mesh/ node/ activity/ portal/), or "system-security" (hub-init / SecurityService bootstrap run under it). ThreadSubmission.DispatchRound only rejected the thread-path / hub-address, so a leaked "system-security" AsyncLocal WON the userCtx resolution and the round dispatched as system instead of the user (effective=system-security in the identity trace — an intermittent race confirmed by SidePanelChatTenMessagesTest). Reject ALL non-user principals (system-security + hub-shaped) like ResolveUserHome already does, and fall through to the persistent circuit context, then the authoritative submitter rider on the pending message. Verified: DispatchRound now consistently dispatches as the real user (effective=Roland), no system-security leak. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rea @key + keep-last-good The intermittent "chat vanishes mid-conversation" (composer + messages blank at a random round) traces to three compounding causes, all addressed here: 1. @key flap. LayoutAreaView's ErrorBoundary @key used LayoutAreaReference.ToString(), which renders a string Id as `v12` but a round-tripped JsonElement Id as `"v12"` (quoted) — so the key FLAPPED between the two representations even though LayoutAreaReference.Equals normalizes them equal. The flap re-created the ErrorBoundary → the child ThreadChatView → reset its state → the blank. Key on the normalized GetHashCode (same Area/Id/Layout basis as Equals): a representation flip is stable, a genuine area switch still recreates. 2. Empty viewmodel bound through. ThreadChatView.ConvertThreadViewModel passed a transient null/empty viewmodel straight through (the cross-hub node stream momentarily reduces to an empty node), emptying Messages → HasNoMessages → the composer blanks. Now keeps the last viewmodel that had content (Messages OR PendingMessageTexts) and falls back to it on any transient empty — poison-proof (the field is only ever assigned a content-bearing value). 3. Optimistic user message dropped server-side. SubscribeThreadVm skipped Messages==0 emissions, dropping the just-sent message (Messages=0 but PendingMessageTexts=[text]) before it reached the client (the "user bubble saw N-1"). Now counts PendingMessageTexts as content. Verified via SidePanelChatTenMessagesTest: footer vanish eliminated across runs (0 footer=0) and message 1 renders. A residual intermittent ThreadChatView re-creation (resetting the keep-last-good field → an occasional round-N blank) remains under investigation — this is a substantial reduction, not yet a full elimination. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re-creation The residual intermittent chat-vanish (after 2f7f43460) was pinned with diagnostics: the ThreadChatView re-creates ~1-2x per conversation on a legitimate thread rebind (TCV-INIT), which resets its component-level keep-last-good field; the fresh component then binds a NULL data section (TCV-EMPTY resultNull=True fieldNull=True) because the new LayoutAreaHost's GetMeshNodeStream emits null before the node loads → the composer + messages blank. A per-component (or closure) field cannot survive that re-creation. Move the last-good to a per-thread-HUB singleton (ThreadVmHolder): the thread hub outlives the per-render LayoutAreaHost / ThreadChatView, so SubscribeThreadVm now SEEDS the fresh data section with the holder's last-good IMMEDIATELY — closing the null-data-section window that the fresh component would otherwise bind. The holder also replaces the local closure for the transient-empty keep-last-good guard. NOTE: e2e-verified the diagnosis (TCV-INIT/TCV-EMPTY) but the FIX's e2e verification is pending — the local machine is too contended by concurrent agent builds to get a clean verification window (load spiked to 26). Compiles; the design follows directly from the pinned root. Verify SidePanelChatTenMessagesTest holds 0 footer=0 once the machine is free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ck (wedge fix) Two agents each running `dotnet publish` (portal + 11 dependents) at once saturate the host's cores → the machine goes unresponsive (load > 18). image_build_local now acquires a lockdir mutex so concurrent memex-local builds run ONE-AT-A-TIME at full speed instead of contending — better than capping -maxcpucount (which makes every build slow). Self-heals a leaked lock via a liveness check on the holder PID (20-min ceiling). Opt out with MEMEX_NO_BUILD_LOCK=1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…her half of the wedge fix
The build lock (prior commit) serializes concurrent builds, but verification showed a SINGLE
`dotnet publish` (unbounded MSBuild parallelism) already spikes the host to load 18 on its own
— the portal + 11 dependents compile across all cores. Add -maxcpucount:"${MEMEX_BUILD_CPUS:-6}"
to both publishes so one build tops out at ~6-8 cores; with the lock (≤1 build at a time) the
host stays ~9-11 even with the Colima VM baseline, well clear of the wedge zone. Raise via
MEMEX_BUILD_CPUS if you have headroom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Reduces intermittent “side-panel chat vanishes mid-conversation” failures by hardening cross-hub type discrimination/registration, stabilizing Blazor identity keying and side-panel path resolution behavior, and adding keep-last-good thread viewmodel guards; also adds local memex-local build serialization / CPU caps to prevent host wedges.
Changes:
- Default
$typeformatting now prefers short type names and broadens explicit type registration to reduce untypedJsonElementfallbacks across hub boundaries. - Side-panel / chat UI resilience: stable
@key, filter/timeout side-panel path resolution, keep-last-goodThreadViewModel/binding, and improved tool-call display toggles. - Adds regression tests (unit + monolith integration + portal e2e stress) and local tooling improvements (
memex-localbuild lock + MSBuild parallelism cap).
Reviewed changes
Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.Threading.Test/ThreadCreatableFromHomeTest.cs | New monolith test pinning “thread creatable from user home + reachable” invariant. |
| test/MeshWeaver.Portal.E2E.Test/SidePanelChatTenMessagesTest.cs | New 10-round side-panel chat stress test to catch vanishing / wedge regressions. |
| test/MeshWeaver.Messaging.Hub.Test/TypeRegistryTest.cs | Updates expectations to match short-name discriminator behavior. |
| test/MeshWeaver.Data.Test/ActivityStatusExtensionsTest.cs | New pure-function tests pinning activity status laws (terminal/success/error). |
| src/MeshWeaver.Messaging.Hub/Serialization/TypeRegistry.cs | Changes default discriminator formatting toward short names. |
| src/MeshWeaver.Messaging.Hub/Serialization/PolymorphicTypeInfoResolver.cs | Updates warning text to match auto short-name registration behavior. |
| src/MeshWeaver.Layout/LayoutExtensions.cs | Registers additional layout-related record types used inside control state. |
| src/MeshWeaver.Hosting.Monolith.TestBase/TestUsers.cs | Seeds partition-root user nodes to match onboarding shape and ensure partitions exist. |
| src/MeshWeaver.Graph/MeshNodeExtensions.cs | Registers MeshNodeEditorField for typed GUI/editor payloads. |
| src/MeshWeaver.Data/DataExtensions.cs | Registers LogMessage/UserInfo for typed activity-log children. |
| src/MeshWeaver.Data.Contract/ActivityStatusExtensions.cs | Adds status classification helpers + follow-up set for activity/watchers/rendering. |
| src/MeshWeaver.Blazor/Components/ThreadMessageBubbleView.razor.cs | Adds per-bubble tool-call expansion toggle state. |
| src/MeshWeaver.Blazor/Components/ThreadMessageBubbleView.razor | Adds compact tool-call window + “show all” toggle UI and styles. |
| src/MeshWeaver.Blazor/Components/LayoutAreaView.razor | Stabilizes @key for areas using normalized reference hash. |
| src/MeshWeaver.Blazor.Portal/Layout/PortalLayoutBase.razor.cs | Filters/times out side-panel ResolvePath to avoid transient wipes during updates. |
| src/MeshWeaver.Blazor.Portal/Chat/ThreadChatView.razor.css | Styles “show all tool calls” toggle in portal chat. |
| src/MeshWeaver.Blazor.Portal/Chat/ThreadChatView.razor.cs | Adds keep-last-good thread VM logic, context normalization, and tool-call expansion state. |
| src/MeshWeaver.Blazor.Portal/Chat/ThreadChatView.razor | Renders compact tool-call window with expandable remainder per message. |
| src/MeshWeaver.Blazor.GoogleMaps/BlazorGoogleMapsExtensions.cs | Registers Google Maps option/config record types for polymorphic serialization. |
| src/MeshWeaver.AI/ThreadSubmission.cs | Tightens “real user” identity selection for round dispatch to avoid hub/system principals. |
| src/MeshWeaver.AI/ThreadLayoutAreas.cs | Adds per-thread-hub last-good ThreadViewModel holder + guarded subscription. |
| src/MeshWeaver.AI/AIExtensions.cs | Registers ThreadViewModel under both full-name and short-name discriminators. |
| deploy/homebrew/bin/memex-local | Adds build lockdir mutex + -maxcpucount cap to prevent local publish wedges. |
| .github/workflows/base-image-acr.yml | Verifies :latest is a true multi-arch manifest (amd64 + arm64) in ACR. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+278
to
280
| var mainTypeName = (mainType.Name ?? mainType.FullName!).Replace('\u002B', '.'); | ||
| if (!mainType.IsGenericType || mainType.IsGenericTypeDefinition) | ||
| return mainTypeName; |
Comment on lines
+747
to
+770
| PathResolver.ResolvePath(contentPath) | ||
| .Where(resolution => resolution != null | ||
| && (string.Equals(resolution.Prefix, contentPath, StringComparison.OrdinalIgnoreCase) | ||
| || string.IsNullOrEmpty(resolution.Remainder))) | ||
| .Take(1) | ||
| .Timeout(TimeSpan.FromSeconds(10)) | ||
| .Subscribe( | ||
| resolution => | ||
| { | ||
| var (area, id) = ParseSidePanelRemainder(resolution!.Remainder); | ||
| var reference = new LayoutAreaReference(area) { Id = id ?? "" }; | ||
| sidePanelViewModel = Controls.LayoutArea((Address)resolution.Prefix, reference); | ||
| InvokeAsync(StateHasChanged); | ||
| }, | ||
| _ => | ||
| { | ||
| // No valid resolution within the window → the content path is genuinely unresolvable | ||
| // (e.g. a deleted thread, or a path that resolves only onto its parent partition). Clear | ||
| // back to the new-chat state. | ||
| sidePanelViewModel = null; | ||
| SidePanelState.SetContentPath(null); | ||
| resolvedSidePanelPath = null; | ||
| InvokeAsync(StateHasChanged); | ||
| }); |
Comment on lines
+32
to
+38
| public static readonly IReadOnlySet<ActivityStatus> FollowupStatuses = new HashSet<ActivityStatus> | ||
| { | ||
| ActivityStatus.Succeeded, | ||
| ActivityStatus.Warning, | ||
| ActivityStatus.Failed, | ||
| ActivityStatus.Cancelled, | ||
| }; |
rbuergi
added a commit
that referenced
this pull request
Jun 30, 2026
… git-added (main went red) The chat-bubble feat commits committed ThreadMessageBubbleView.razor referencing ToolCallVisibility.Partition, but ToolCallVisibility.cs was left UNTRACKED in the working tree — so the solution built locally yet failed `dotnet build -c Release -warnaserror` on a clean CI checkout (CS0103: 'ToolCallVisibility' does not exist). That is what turned main red after #136 merged and what blocks the pull-based self-update (it needs a green main image to roll forward). Verified: MeshWeaver.Blazor compiles -c Release -warnaserror with the file tracked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rbuergi
added a commit
that referenced
this pull request
Jun 30, 2026
#136 (#137) #136 (fix/persist-syncbehavior) merged the MODIFIED ThreadMessageBubbleView.razor (which calls ToolCallVisibility.Partition) but NOT the new ToolCallVisibility.cs — that file was untracked and never `git add`ed, so it didn't make the merge. main referenced a class that wasn't there → `error CS0103: The name 'ToolCallVisibility' does not exist` → build job fails → test shards skipped → Continuous Delivery skipped → no environment deployed. Add ToolCallVisibility.cs (+ ToolCallVisibilityTest.cs) so main compiles again. Verified: MeshWeaver.Blazor builds clean. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rbuergi
added a commit
that referenced
this pull request
Jun 30, 2026
…orward fix(serialization): make short-name $type round-trip — green main after #136
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Chat-vanish (SidePanelChatTenMessagesTest)
Substantially reduces the intermittent "side-panel chat vanishes mid-conversation" bug, across several root causes:
@keyflap:LayoutAreaViewkeyed onLayoutAreaReference.ToString()flapped between string-Idv12and JsonElement-Id"v12"even thoughEqualsnormalizes them — re-creating the area → blank. Keyed on the normalizedGetHashCode.PendingMessageTexts).SubscribeThreadVmcountsPendingMessageTextsas content.ThreadChatViewre-creation.Status: substantial reduction, not a full cure. A residual intermittent
ThreadChatViewre-creation can still reset the keep-last-good and blank a round — documented in the holder-fix commit. Verified via the included 10-message e2e stress test.Local build wedge cure (
memex-local)Two agents' concurrent
dotnet publish, and even a single unbounded one, saturate the host (load 18-26 → unresponsive).memex-localinvocations (self-healing lockdir).-maxcpucountcap: one build tops out at ~6 cores. Verified: capped build peaks at load 5.14 vs 18.36 unbounded.🤖 Generated with Claude Code