Onboarding latches denpa.onboarded with no reset path #37
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#37
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?
Onboarding writes
denpa.onboardedto localStorage on 開局 (src/renderer/src/hooks/receiver/use-onboarding.ts:48, key atcomponents/receiver/onboarding-spec.ts:103) and nothing anywhere can clear it.grep -rn ONBOARDED_KEY src/returns exactly two call sites: the constant and that one hook. The 設定 sheet has no reset.App.tsx:116gates the entire application on that flag — onboarding is not a screen the app can return to, it is the app until the flag flips. So a device whose flag istruewhile it is not paired shows the receiver chrome over an inert connection: no code, no 周波数, no way back, forever.This is reachable, not theoretical
aiko/ollvt-hermes-bridge#23describes the bypass that latched exactly this flag: an unpaired device that trippedMAX_PENDING_PER_PLATFORMreceived chain-start, concluded it was paired, and wrotedenpa.onboarded=true. The client half was fixed in#28(c57af9b) — but the fix does not un-write the flag on devices that already ran an affected build. The Android APK and the desktop dev shell both ran one.What is needed
denpa.onboardedand returns to 待受. It is also the only way to re-pair a device deliberately.App.tsx:116currently makes it mean.Also in this slice, same file family
isAndroidresolves async.App.tsx:58-63startsfalseand fills in fromgetTauriPlatform().stepsFor(android)therefore grows 5→6 mid-flow, andlast(components/receiver/onboarding.tsx:330) shifts with it. Narrow window, real: on a slow resolve the finish key stops being on 開局.WebSocketProvider(src/renderer/src/context/websocket-context.tsx:58-82) hard-codeswsState: 'CLOSED'andupstream: null. It is exported and never mounted —WebSocketHandlerprovides the real value (services/websocket-handler.tsx:320-335). Mount it by mistake andcanAdvancecan never open. Delete it.Acceptance
denpa.onboardedcan be cleared from the running app, and clearing it returns the app to 待受lastis derived so a late resolve cannot move the finish keyWebSocketProvideris gone, or is the real providerunknowndoes not render the receiver chrome as if workingRuled out of scope for v1 on the wayfinder map, the conversation works (v1). Labelled
v2; staying open rather than closed, because the scope call lives on the map and this is still real work.v1 is: a real voice conversation — you speak, she answers in her own voice, Live2D renders, the log records it — on the Windows desktop, the OnePlus Pad and an Android phone, for one operator on hardware they already own.
Three of the five are done and merged. The other two are addressed rather than outstanding — details below, since one of them was solved by a different ticket.
denpa.onboardedcan be cleared from the running app, and clearing it returns the app to 待受. 最初からやる (start) now clears the flag alongside the tuning and address keys. It already forgot the credential Rust holds and reloaded, andsetupNeededreopens setup when the credential is gone — so the return to 待受 worked before this; what was left was stored state going on claiming the device had been through setup when it had not. Level one still keeps the flag: that is tuning, not enrolment.#66, not here.setupNeeded(flag, credentialed, refusal)reads the credential Rust holds and adevicerefusal, so a latched flag alone opens nothing.lastis derived so a late resolve cannot move the finish key. Taken the second way:lastwasstate.index === steps.length - 1and the list grows 5 → 6 whenisAndroidresolves, so a slow resolve moved the finish key off 開局 mid-flow. It now reads the step's own key.WebSocketProvideris gone. It hard-codedwsState: 'CLOSED'andupstream: null, was exported, and was mounted nowhere — mounting it by mistake would have leftcanAdvanceunable to open, ever. Deleted with the imports and the eslint-disable that existed only for it.unknowndoes not render the receiver chrome as if working.The last one is the only real gap.
setupNeededdistinguishescredentialed === null(not answered yet) fromfalse, which is the state that acceptance item names — but nothing pins the render against it. That test belongs with the hook that owns the third state rather than bolted onto this batch, and it wants a decision I did not want to make alone: what the app should draw during that window, given it is the same window#67deliberately holds the titlebar back for.Leaving this open for it. Everything else here is merged: 66 test files, 907 tests,
npm run typecheckexits 0.Still
v2— the scope call on the map is unchanged.Closed by
6d620a3. 最初からやる now clears the latch:settings-spec.ts:301-313'sresetKeysdropsdenpa.onboardedat the session level and names this issue in the comment above it. A device can be walked through setup again.