PURPOSE
Feedback modal triggered by the in-canvas ”?” button. Captures a screenshot of the current view, collects free-text feedback from the player, and writes the record to Supabase. Pauses the active mission while open so gameplay doesn’t continue underneath the modal.
OWNS
- Module-level
_activeMissionand_gameCanvasrefs holding the current mission handle and game canvas. - Exports
setActiveMission(mission, canvas)andclearActiveMission()used by GameScreen to register/clear the active mission. - Local React state:
open,text,screenshot,sending,sent,error. didPauseReftracking whether this component is the one that paused the game (so it only resumes what it paused).- Two side-effect flows:
handleOpen(pause + capture + open) andhandleClose(resume + close). handleSubmitbuilding the feedback payload and inserting it into theplayer_feedbacktable.
READS FROM
usePlayerStore— pullsprofileforplayer_idanddisplayName; renders nothing whenprofileis null._activeMission/_gameCanvasmodule refs._gameCanvas.toDataURL('image/png')for the in-game screenshot.html2canvas(document.body, ...)fallback for menu screens.window.location.pathname,navigator.userAgent,window.screen.width,window.screen.heightfor context fields.MissionHandletype from@starship-survivors/engine/bridge.
PUSHES TO
supabase.from('player_feedback').insert(payload)withplayer_id,display_name,feedback_text,screenshot_data,screen,user_agent,screen_w,screen_h,created_at._activeMission.pause()on open,_activeMission.resume()on close (gated bydidPauseRef).- Dispatches
ss:mission-changedonwindowfrom bothsetActiveMissionandclearActiveMission. - Renders a
MedPanelModalcontaining aMedPanel variant="doors"with textarea, screenshot well, send/cancel buttons, and a success state.
DOES NOT
- Does not render a FAB button. The on-screen FAB has been removed from every screen; the canvas ”?” hit-region in
hud.tsis the only opener. - Does not auto-open on errors or unhandled rejections; only opens on the
ss:open-feedbackevent. - Does not retry failed inserts or queue offline submissions; failures surface as an inline error string.
- Does not upload the screenshot as a binary blob — it inlines the PNG data URL into
screenshot_data. - Does not resume a mission it did not pause (the
didPauseRefflag prevents stomping on an unrelated pause state). - Does not gate by network status or rate-limit submissions.
Signals
- Listens for window event
ss:open-feedback. - Emits window event
ss:mission-changedwheneversetActiveMissionorclearActiveMissionis called.
Entry points
setActiveMission(mission, canvas)— called by GameScreen when a mission is created.clearActiveMission()— called by GameScreen when a mission is destroyed.<FeedbackFAB />— mounted once in the metagame shell; renders null untilss:open-feedbackarrives.ss:open-feedbackevent — dispatched from the canvas HUD ”?” hit-region (and any other UI surface that wants to invoke the modal).
Pattern notes
- Third member of the modal-form trio refactored to medical-UI styling:
WelcomeModal(tick 74),ClaimAccountModal(tick 75),FeedbackFAB(tick 76). UsesMedPanelModal+MedPanel variant="doors"plus.med-textareaand.med-image-wellCSS classes. - Module-level mutable singletons (
_activeMission,_gameCanvas) are intentional: the feedback modal lives outside the GameScreen subtree but needs direct access to the mission handle and canvas. Thess:mission-changedevent lets other listeners observe transitions without subscribing through React. - Screenshot strategy branches on
_gameCanvaspresence — directtoDataURLduring gameplay (cheap, mission already paused),html2canvasof the DOM on menu screens (heavier path, used only when there is no canvas). - The pause/resume contract is one-sided: only resumes if this component performed the pause. Other pause sources (e.g., system pause overlay) are not disturbed.
- Returns
nulluntilopenis true, so the component contributes nothing to the DOM in steady state.