PageSourceSearch

https://selebox.com/js/writer/writer-nav.js?v=parity-20260926

js selebox.com collected 2026-10-07 23:57:33 UTC 7,118 bytes, 153 lines download raw bytes

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.