1// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2// Writer's Lounge (Web) â wiper-proof internal navigation + LOCAL NAV STACK. 3// 4// Mechanism history (matters!): 5// ⢠hash-PUSH + direct handler call â field-proven: passed real-browser 6// runtime QA for the createâboardâeditor flows (navtab-12â¦15). 7// ⢠replaceState-first (navtab-16/17) â passed self-tests but Back died 8// in the real browser. REVERTED. We are back on the proven mechanism. 9// 10// The Lounge keeps its OWN navigation stack (PM spec): visible Back buttons 11// pop this stack and re-render internally. They NEVER use browser history, 12// which the main app pollutes (hash-wiper pushState entries). 13// 14// Diagnostics (localhost only): window.WriterLounge.navStack / lastNav / 15// lastNavError / debugBackTargets â every hop records target, handler state, 16// and (via the router) the final rendered route. 17// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 18 19function _isLocal() { 20 try { const h = window.location.hostname; return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '[::1]'; } catch (_) { return false; } 21} 22 23const _stack = []; // writer routes only, oldest â newest 24const STACK_MAX = 30; 25 26function _publishStack() { 27 if (!_isLocal()) return; 28 try { window.WriterLounge = window.WriterLounge || {}; window.WriterLounge.navStack = _stack.slice(); } catch (_) {} 29} 30 31function _recordNav(target, via) { 32 if (!_isLocal()) return; 33 try { 34 window.WriterLounge = window.WriterLounge || {}; 35 window.WriterLounge.lastNav = { 36 target: target || '(exit)', via, at: new Date().toISOString(), 37 handlerBound: !!_routeHandler, handlerRan: false, finalRoute: null, stackDepth: _stack.length, 38 }; 39 } catch (_) {} 40} 41 42function _recordNavError(stage, err) { 43 if (!_isLocal()) return; 44 try { 45 window.WriterLounge = window.WriterLounge || {}; 46 window.WriterLounge.lastNavError = { stage, error: String(err?.message || err), at: new Date().toISOString() }; 47 } catch (_) {} 48} 49 50// Local-QA back-button registry: each view declares its visible Back 51// control + intended target when it renders (window.WriterLounge.debugBackTargets). 52export function registerBackTarget(screen, label, target) { 53 if (!_isLocal()) return; 54 try { 55 window.WriterLounge = window.WriterLounge || {}; 56 const t = (window.WriterLounge.debugBackTargets = window.WriterLounge.debugBackTargets || {}); 57 t[screen] = { label, target, renderedAt: new Date().toISOString() }; 58 } catch (_) {} 59} 60 61let _routeHandler = null; 62export function setRouteHandler(fn) { _routeHandler = fn; } 63 64// ââ Browser-Back guard state ââââââââââââââââââââââââââââââââââââââââââââ 65// _lastWriterRoute: the most recently RENDERED writer route (null once the 66// Lounge has truly exited). _exitIntent: set by an explicit exit action 67// ('Back to Selebox') so the guard lets that one through. 68let _lastWriterRoute = null; 69let _exitIntent = false; 70export function getLastWriterRoute() { return _lastWriterRoute; } 71export function consumeExitIntent() { const v = _exitIntent; _exitIntent = false; return v; } 72let _exitEpoch = 0; 73export function getExitEpoch() { return _exitEpoch; } 74export function noteLoungeExited() { 75 _exitEpoch++; // any in-flight render becomes ABANDONED 76 _lastWriterRoute = null; 77 _stack.length = 0; 78 _publishStack(); 79} 80 81// The router calls this when a writer route actually RENDERS â so routes 82// reached via the tab / direct URL / hashchange also land on the stack,
83// and lastNav learns its final outcome. 84export function noteRouteRendered(route) { 85 const r = String(route || ''); 86 if (r.indexOf('#writer') !== 0) return; 87 if (_stack[_stack.length - 1] !== r) { 88 _stack.push(r); 89 if (_stack.length > STACK_MAX) _stack.splice(0, _stack.length - STACK_MAX); 90 } 91 _publishStack(); 92 _lastWriterRoute = r; 93 if (_isLocal()) { 94 try { 95 const ln = window.WriterLounge && window.WriterLounge.lastNav; 96 if (ln) { 97 ln.handlerRan = true; ln.finalRoute = r; ln.stayedInsideLounge = true; 98 const root = document.getElementById('wlRoot'); 99 ln.wlRootVisible = !!(root && root.style.display !== 'none'); 100 } 101 } catch (_) {} 102 } 103} 104 105// Field-proven navigation: set the hash (push) + pushState fallback + DIRECT 106// handler call with the explicit route, so rendering never depends on the 107// hash surviving the app's wiper. 108export function navigateTo(route, _via) { 109 const r = String(route || ''); 110 const isWriterRoute = r.indexOf('#writer') === 0; 111 if (!isWriterRoute) _exitIntent = true; // explicit exit (e.g. Back to Selebox) 112 _recordNav(r, _via || 'navigateTo'); 113 try { window.location.hash = r; } catch (e) { _recordNavError('hash-set', e); } 114 if (r && (window.location.hash || '') !== r) { 115 try { history.pushState(null, '', window.location.pathname + (window.location.search || '') + r); } catch (e) { _recordNavError('pushState', e); } 116 } 117 if (_routeHandler) { 118 try { _routeHandler(isWriterRoute ? r : undefined); } catch (e) { _recordNavError('handler', e); } 119 } else { 120 _recordNavError('handler', 'no route handler registered'); 121 } 122} 123 124// Visible Back buttons ONLY: pop the Lounge's own stack; previous Lounge 125// page wins, hard fallback otherwise. NEVER browser history, NEVER home. 126export function goBack(fallback) { 127 const fb = (String(fallback || '').indexOf('#writer') === 0) ? String(fallback) : '#writer'; 128 try { 129 _stack.pop(); // leave the current route 130 let prev = _stack.length ? _stack[_stack.length - 1] : null; 131 if (!prev || String(prev).indexOf('#writer') !== 0) prev = fb; 132 _stack.pop(); // navigateTo's render will re-push it 133 _publishStack(); 134 navigateTo(prev, 'goBack'); 135 } catch (e) { 136 _recordNavError('goBack', e); 137 navigateTo(fb, 'goBack-fallback'); // absolute fallback: Lounge dashboard 138 } 139} 140 141// PM-specified canonical names (aliases of the same controller). 142export const navigateToWriterRoute = navigateTo; 143export const goBackWithinWriterLounge = goBack; 144 145// ââ The ONE boring action (PM directive): chapter board â dashboard ââââ 146// Explicit, hard-coded, no stack, no history, no fallback-by-arrival-path. 147// Used by BOTH the delegated handler and the per-button direct listener 148// (redundant on purpose: if delegation ever fails, the button still works). 149export function goDashboardFromChapterBoard(e) { 150 try { if (e && e.preventDefault) e.preventDefault(); } catch (_) {} 151 try { if (e && e.stopImmediatePropagation) e.stopImmediatePropagation(); else if (e && e.stopPropagation) e.stopPropagation(); } catch (_) {} 152 navigateTo('#writer', 'board-dashboard'); 153}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.