WebSocketHandler is a provider and a handler at two different heights #86
Labels
No labels
needs-info
needs-triage
ready-for-agent
ready-for-human
v2
wayfinder:grilling
wayfinder:map
wayfinder:prototype
wayfinder:research
wayfinder:task
wayfinder:ticket
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
aiko/denpa#86
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
WebSocketHandleris two things at once: it provides the socket, and it handles the messages. Handling needs the other contexts — it readsuseVAD,useBgUrl,useLive2DConfig,useSubtitle,useChatHistory,useAiState— so it must be mounted below them. Providing needs to be above anything that talks to the socket. Those two heights are incompatible, and everything below came out of that one knot.What it has already cost
df23c97removed the context default that reportedwsState: 'CLOSED'andupstream: null, on the entirely correct grounds that a component outside the provider should fail loudly rather than read a shut socket. But four consumers are outside it by construction —VADProviderviauseInterrupt,useAudioTaskanduseSendAudio, andBgUrlProviderviabaseUrl. The throw took the tree down on first render, which on a transparent undecorated window is a black rectangle and on Android an icon that does nothing. An APK was built and installed from it. The shut-socket default had been load-bearing: it was what let the cycle stand.VADProvideris where Rust hands captured audio to the renderer, souseSendAudiothere answerednulland returned without sending. Voice input stopped working and nothing said so — the loud failure became a quiet one.Both were the same missing seam, twice, in opposite directions.
What is in place now
useSendAudiotalks towsServicedirectly, which is what the context was handing out anyway (sendMessagethere iswsService.sendMessagebound, andwsServiceis a module singleton).useInterruptanduseAudioTaskstill read the context optionally and no-op when it is absent;BgUrlProviderfalls back todefaultBaseUrl.That is a patch per consumer, and the next one to be added above the provider will make the same mistake with no test to catch it.
What to build
Split the component in two:
sendMessage,wsState,upstream,runFailureand the urls, mounted above everything that reads any of them.useWebSocketthen throws only for genuine misuse,useWebSocketOptionaland the per-consumer fallbacks disappear, andVADProviderreads a real socket instead of a stub.Note what
#37deleted and why this is not a revert. There was aWebSocketProvider. It hard-codedwsState: 'CLOSED'andupstream: null, was exported, and was mounted nowhere — a component that lied. Deleting it was right; what is wanted here is the same shape actually wired to the socket.Acceptance
sendMessagecome from a provider mounted above every consumeruseWebSocketOptionalis gone, and so is every per-consumer fallbackVADProvider's audio path sends through the same socket every other caller uses, not a singleton reached around the sideuseWebSocketthrowing means a real mistake, and a test proves the tree mounts without oneapp-mounts.test.tsx) still passes, and a second one covers a consumer above the old boundaryRelated
denpa#37(deleted the lying provider), the black-window fixf148406, the dropped-audio fix, andapp-mounts.test.tsx, which exists because none of this was catchable before.