Keep popup instructions readable until continued
Show concise progress during first room create/join; reserve persistent recovery instructions for saved/uncertain attempts. Retain completed-action text until a local Continue button, without gating rules or AI. Preserve unread text across backgrounding, bounded summaries, mute and private-safe feedback. Fix the container QA helper path-import collision. Verified: typecheck, lint, 5200 offline tests, 52 HTTP tests and production build. Delayed/lost-response browser checks prove progress, refresh recovery and exact retry without duplicates. Notice stayed for over 47 seconds while the QA game progressed; Continue advances only presentation. Mobile 16px/44px controls verified. Existing games preserved. Container/deployed acceptance follows publication.
This commit is contained in:
1 parent
c2d392e155
commit
f29d05620c
10 files changed
+238
-115
No files matched your search
@@ -11,6 +11,36 @@ export type QueuedActionNotice = AutomaticActionEvent & { count: number };
|
||||
|
||||
export type ActionNoticeCursor = { seenSeq: number; pending: QueuedActionNotice[] };
|
||||
|
||||
export type ActionNoticePresentation = ActionNoticeCursor & { active: QueuedActionNotice | null };
|
||||
|
||||
type ActionNoticeUpdate =
|
||||
| { type: 'events'; events: readonly AutomaticActionEvent[]; enabled: boolean; visible: boolean }
|
||||
| { type: 'continue' }
|
||||
| { type: 'hide' }
|
||||
| { type: 'clear' };
|
||||
|
||||
/** Existing history establishes a silent baseline, including after refresh. */
|
||||
export function initialActionNotices(events: readonly AutomaticActionEvent[]): ActionNoticePresentation {
|
||||
return { seenSeq: events.reduce((last, event) => Math.max(last, event.seq), -1), pending: [], active: null };
|
||||
}
|
||||
|
||||
/** Local presentation only: reading or dismissing a notice never advances the game. */
|
||||
export function updateActionNotices(state: ActionNoticePresentation, update: ActionNoticeUpdate): ActionNoticePresentation {
|
||||
if (update.type === 'hide') return { ...state, pending: [] };
|
||||
if (update.type === 'clear') return { ...state, active: null, pending: [] };
|
||||
if (update.type === 'continue') return { ...state, active: state.pending[0] ?? null, pending: state.pending.slice(1) };
|
||||
|
||||
const active = update.enabled ? state.active : null;
|
||||
const cursor = advanceActionNotices(state, update.events, {
|
||||
active: !!active,
|
||||
show: update.enabled && update.visible,
|
||||
});
|
||||
// New updates never replace text that the player has not dismissed.
|
||||
return active
|
||||
? { ...cursor, active }
|
||||
: { ...cursor, active: cursor.pending[0] ?? null, pending: cursor.pending.slice(1) };
|
||||
}
|
||||
|
||||
/** Consume every observed sequence, even when presentation is disabled. */
|
||||
export function advanceActionNotices(
|
||||
cursor: ActionNoticeCursor,
|
||||
@@ -30,7 +60,7 @@ export function advanceActionNotices(
|
||||
};
|
||||
}
|
||||
|
||||
/** Keep at most three 1.5-second notices, including the one already on screen. */
|
||||
/** Keep at most three notices, including the one awaiting local dismissal. */
|
||||
export function appendActionNotices(
|
||||
pending: readonly QueuedActionNotice[],
|
||||
incoming: readonly AutomaticActionEvent[],
|
||||
|
||||
Reference in new issue
Block a user