diff --git a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md index 7eafc5f5..94c72842 100644 --- a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md +++ b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md @@ -322,7 +322,7 @@ The core-side stack uses: The renderer-side stack uses: 1. `GuiRemoteProtocolAsyncJsonChannelRenderer` when network packages can arrive before the native GacUI window is ready. It queues received packages until `SetInvokeInMainThread(...)` installs an `IGuiRemoteProtocolAsyncRendererInvoker`, then drains them on the renderer UI thread. Its message version prevents callbacks captured by an old reader from running after the reader is replaced. -2. `GuiRemoteProtocolRendererChannel` to bridge the renderer JSON channel to a concrete renderer `IGuiRemoteProtocol` implementation and to serialize renderer events/responses back to the channel. +2. `GuiRemoteProtocolRendererChannel` to bridge the renderer JSON channel to a concrete renderer `IGuiRemoteProtocol` implementation and to serialize renderer events/responses back to `GacUIRemoteProtocolCoreClientId`. Construct it with the renderer-side `IJsonChannel` and the `IGuiRemoteProtocol`. ## Image Service diff --git a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md index c5f6b119..631467ea 100644 --- a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md +++ b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md @@ -85,7 +85,7 @@ The channel system converts typed protocol calls to Parser2 JSON node packages a Two channel adapters bridge typed protocol calls and JSON packages: - `GuiRemoteProtocolCoreChannel`: Implements `IGuiRemoteProtocol` and reads from an `IJsonChannel`. `RequestNAME(...)` serializes arguments with `ConvertCustomTypeToJson()`, packs an envelope with `JsonChannelPack()`, and sends it to the current renderer client. Incoming packages are unpacked with `JsonChannelUnpack()`, dispatched by name, deserialized with `ConvertJsonToCustomType()`, and delivered as `events->OnNAME(...)` or `events->RespondNAME(...)`. It also queues outgoing packages before a renderer is known and exposes `DetachRenderer(clientId)` for renderer replacement. -- `GuiRemoteProtocolRendererChannel`: Reads renderer-side JSON packages and calls the wrapped `IGuiRemoteProtocol`. It also implements `IGuiRemoteProtocolEvents` so renderer events and responses are serialized back to JSON packages and flushed through the renderer channel. +- `GuiRemoteProtocolRendererChannel`: Reads renderer-side JSON packages and calls the wrapped `IGuiRemoteProtocol`. Its constructor takes the renderer-side `IJsonChannel` and the `IGuiRemoteProtocol`; the channel sends renderer events and responses back to `GacUIRemoteProtocolCoreClientId`. ### Transport Layer diff --git a/.github/KnowledgeBase/Learning.md b/.github/KnowledgeBase/Learning.md index 30506ac6..7d8a1417 100644 --- a/.github/KnowledgeBase/Learning.md +++ b/.github/KnowledgeBase/Learning.md @@ -11,6 +11,7 @@ - Use `WString::IndexOf` with `wchar_t` (not `const wchar_t*`) [4] - Use `collections::BinarySearchLambda` on contiguous buffers (guard empty) [4] - Use `vl::Exception` for expected semantic failures and `CHECK_ERROR` for invariants [3] +- Keep design documentation aligned with code after refactoring [3] - Capture dependent lambdas explicitly [2] - Don't assume observable changes are batched [2] - Do not assume async callback owners are heap allocated [2] @@ -19,8 +20,8 @@ - Prefer simple calls before interface casts [2] - Validate expectations against implementation and existing tests [2] - Treat Debug memory leak dumps as required failures [2] -- Keep design documentation aligned with code after refactoring [3] - Prefer well-defined tests over ambiguous edge cases [1] +- Fix behavior at the owning state instead of patching symptoms [1] - Prefer `operator<=> = default` for lexicographic key structs [1] - Prefer two-pointer merge for sorted range maps [1] - Use named sentinel constants instead of raw values [1] @@ -253,3 +254,7 @@ For application refactors, remove helper wrappers that only duplicate an already ## Keep design documentation aligned with code after refactoring When a refactoring changes architecture or behavior, update the corresponding design documents in the same task rather than deferring it. After a structural change, re-read the related documents and reconcile anything that became misaligned (for example, descriptions of a transport path that no longer exists). Treat documentation drift left by a previous refactoring as part of the current cleanup. + +## Fix behavior at the owning state instead of patching symptoms + +When a bug is caused by state attached to a temporary or overly broad owner, move the state to the object or region that semantically owns it. Avoid compensating fixes such as forcing override values on every affected descendant, duplicating a template/resource just to mask inherited state, or adding side-channel code that only makes the symptom disappear. If a proposed fix looks like a patch, revisit the ownership boundary and preserve naturally correct inherited/default behavior for unaffected parts. diff --git a/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md b/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md index 0646178c..6bfdbd01 100644 --- a/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md +++ b/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md @@ -29,7 +29,7 @@ The window id is a path segment after `IO`, not a query parameter. All other met ## Starting The Service -Call `StartWindowsHttpAutomationService` from `GuiMain`, after the setup function has installed the current native controller and before entering the application event loop. Call `StopWindowsHttpAutomationService` during shutdown when the service lifetime is not already stopped through `INativeAutomationService::Stop`. +Call `StartWindowsHttpAutomationService` from `GuiMain`, after the setup function has installed the current native controller and before entering the application event loop. Every code path that calls `StartWindowsHttpAutomationService` must later call `StopWindowsHttpAutomationService` before the native controller or substituted automation service is torn down. Skipping the stop leaks the process-wide HTTP service. Use a local guard or equivalent try/catch so the stop runs after the start on normal returns and exceptions. A normal Windows application can start the service before `GetApplication()->Run`: ```c++ @@ -39,17 +39,30 @@ using namespace vl; using namespace vl::presentation; using namespace vl::presentation::controls; +class WindowsHttpAutomationServiceScope +{ +public: + WindowsHttpAutomationServiceScope(const WString& applicationName, vint port) + { + windows::StartWindowsHttpAutomationService(applicationName, port); + } + + ~WindowsHttpAutomationServiceScope() + { + windows::StopWindowsHttpAutomationService(); + } +}; + void GuiMain() { demo::MainWindow window; window.ForceCalculateSizeImmediately(); window.MoveToScreenCenter(); - windows::StartWindowsHttpAutomationService( + WindowsHttpAutomationServiceScope httpAutomationService( WString::Unmanaged(L"Automation/MyApp"), 8888); GetApplication()->Run(&window); - windows::StopWindowsHttpAutomationService(); } int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) @@ -58,7 +71,7 @@ int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) } ``` -Repeated calls do not create multiple listeners. The Windows implementation keeps one process-wide HTTP service until `StopWindowsHttpAutomationService` or `INativeAutomationService::Stop` stops it. +Repeated calls do not create multiple listeners. The Windows implementation keeps one process-wide HTTP service until `StopWindowsHttpAutomationService` stops it. ## Setup Function Cases @@ -79,19 +92,20 @@ The setup function decides which controller and service are active while `GuiMai Use `GetNativeServiceSubstitution()->Substitute(service, false)` before the automation service is first requested. The substitution layer rejects a late substitution after a service has already been used. Keep the substituted object alive until it is unsubstituted. -A remote protocol core can expose the core-side automation surface like this: +A remote protocol core can expose the core-side automation surface like this. The sample uses the same `WindowsHttpAutomationServiceScope` guard from the normal Windows application example. ```c++ void GuiMain() { RemoteProtocolAutomationService automationService; GetNativeServiceSubstitution()->Substitute(&automationService, false); - windows::StartWindowsHttpAutomationService( - WString::Unmanaged(L"Automation/RemoteCore"), - 8888); - GetApplication()->Run(mainWindow); + { + WindowsHttpAutomationServiceScope httpAutomationService( + WString::Unmanaged(L"Automation/RemoteCore"), + 8888); + GetApplication()->Run(mainWindow); + } - windows::StopWindowsHttpAutomationService(); GetNativeServiceSubstitution()->Unsubstitute(&automationService); } ``` diff --git a/.github/KnowledgeBase/manual/gacui/modes/remote_client.md b/.github/KnowledgeBase/manual/gacui/modes/remote_client.md index 0680a302..3313ee72 100644 --- a/.github/KnowledgeBase/manual/gacui/modes/remote_client.md +++ b/.github/KnowledgeBase/manual/gacui/modes/remote_client.md @@ -48,7 +48,6 @@ int StartNamedPipeRemoteRenderer() GuiRemoteProtocolAsyncJsonChannelRenderer asyncRendererChannel(channelClient.GetProtocolChannel()); GuiRemoteRendererSingle renderer(true); GuiRemoteProtocolRendererChannel rendererChannel( - &channelClient, &asyncRendererChannel, &renderer); diff --git a/.github/KnowledgeBase/manual/gacui/modes/remote_core.md b/.github/KnowledgeBase/manual/gacui/modes/remote_core.md index 1b0cc8f6..4d6742b2 100644 --- a/.github/KnowledgeBase/manual/gacui/modes/remote_core.md +++ b/.github/KnowledgeBase/manual/gacui/modes/remote_core.md @@ -5,7 +5,7 @@ A remote protocol core application is a normal GacUI application that calls Setu The standard C++ path is small: - Start a GuiRemoteProtocolNetworkChannelServer\ over an INetworkProtocolServer implementation such as NamedPipeServer or HttpServer. - Connect the core to that server with GuiRemoteProtocolLocalChannelClient. -- Wrap the core channel with GuiRemoteProtocolJsonChannelRenderer_Async and GuiRemoteProtocolCoreChannel. +- Wrap the core channel with GuiRemoteProtocolAsyncJsonChannel and GuiRemoteProtocolCoreChannel. - Pass the protocol, usually after GuiRemoteProtocolFilter and GuiRemoteProtocolDomDiffConverter, to SetupRemoteNativeController. The core client is expected to be registered as GacUIRemoteProtocolCoreClientId. A renderer created by GuiRemoteProtocolChannelClient advertises GacUIRemoteProtocolChannelName. GuiRemoteProtocolCoreChannel learns the renderer client id from the renderer's ControllerConnect event, so user code does not need to route individual remote protocol messages. @@ -69,7 +69,7 @@ void StartNamedPipeRemoteCore() server.WaitForRenderer(); - GuiRemoteProtocolJsonChannelRenderer_Async asyncChannel(coreClient->GetProtocolChannel()); + GuiRemoteProtocolAsyncJsonChannel asyncChannel(coreClient->GetProtocolChannel()); GuiRemoteProtocolCoreChannel coreProtocol( coreClient.Obj(), &asyncChannel,