@eintrek/erp-shell 0.1.77 → 0.1.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2827,6 +2827,37 @@ function reduceGuide(state, action, suite, env) {
2827
2827
  d.state = { ...d.state, pages: { ...d.state.pages, [action.key]: action.row } };
2828
2828
  break;
2829
2829
  }
2830
+ case "yield": {
2831
+ const run = d.state.pageRun;
2832
+ if (run?.kind === "flow" && run.context)
2833
+ withProgress(d, { context: run.context });
2834
+ d.state = { ...d.state, pageRun: null, replayPage: null, panelOpen: false, justDone: null, pendingCelebrate: [] };
2835
+ setScreen(d, { name: "idle" });
2836
+ break;
2837
+ }
2838
+ case "refresh": {
2839
+ const p = action.progress;
2840
+ const local = d.state.progress;
2841
+ // The server's row when it is this version and not behind this tab's.
2842
+ if (p.guide_version === local.guide_version && p.rev >= local.rev)
2843
+ d.state = { ...d.state, progress: p };
2844
+ const newer = (server, mine) => {
2845
+ const out = { ...(mine ?? {}) };
2846
+ for (const [k, r] of Object.entries(server ?? {})) {
2847
+ const m = out[k];
2848
+ if (!m || r.version > m.version || (r.version === m.version && r.rev >= m.rev))
2849
+ out[k] = r;
2850
+ }
2851
+ return out;
2852
+ };
2853
+ d.state = {
2854
+ ...d.state,
2855
+ pages: newer(action.pages, d.state.pages),
2856
+ sections: newer(action.sections, d.state.sections),
2857
+ flows: newer(action.flows, d.state.flows),
2858
+ };
2859
+ break;
2860
+ }
2830
2861
  case "startSection": {
2831
2862
  // Never offered: only on request. Never over a mission step, a
2832
2863
  // welcome, a gate or a celebration; only on screen, only theirs.
@@ -3196,9 +3227,9 @@ function conflictOf(err) {
3196
3227
  return null;
3197
3228
  }
3198
3229
  /** The GET, parsed for this suite; null (show nothing) on any failure or odd answer. */
3199
- async function loadGuides(adapter, suite, signal) {
3230
+ async function loadGuides(adapter, suite, signal, fresh = false) {
3200
3231
  try {
3201
- const raw = await adapter.fetchProgress({ app: suite.app, guideKey: suite.key, signal });
3232
+ const raw = await adapter.fetchProgress({ app: suite.app, guideKey: suite.key, signal, ...(fresh ? { fresh: true } : {}) });
3202
3233
  return parseGuidesAnswer(raw, suite);
3203
3234
  }
3204
3235
  catch {
@@ -4397,36 +4428,81 @@ const flowMirrorKey = (orgId, suite) => `erp-guide-flow-run:${orgId}:${suite.app
4397
4428
  /* One tab at a time */
4398
4429
  /* ------------------------------------------------------------------ */
4399
4430
  /**
4400
- * Whether this tab runs the tour. With two tabs open, the first holds a Web
4401
- * Lock and the second shows nothing until the first goes away.
4431
+ * Whether this tab runs the tour: one tab at a time holds a Web Lock and
4432
+ * runs steps, offers, the welcome and saves. Every other tab still reads
4433
+ * progress and shows the "?" menu (shell ≥ 0.1.78); pressing one of its
4434
+ * guide items calls `takeover`, which steals the lock. The tab it was
4435
+ * stolen from goes back to waiting its turn (and yields what it was running).
4436
+ * Without Web Locks every tab runs, as before.
4402
4437
  */
4403
4438
  function usePrimaryTab(name) {
4404
4439
  const [primary, setPrimary] = React.useState(false);
4440
+ const primaryRef = React.useRef(false);
4441
+ const takeoverRef = React.useRef(() => Promise.resolve());
4405
4442
  React.useEffect(() => {
4406
4443
  const locks = (typeof navigator !== "undefined" ? navigator.locks : undefined);
4444
+ const set = (v) => {
4445
+ primaryRef.current = v;
4446
+ setPrimary(v);
4447
+ };
4407
4448
  if (!locks?.request) {
4408
- setPrimary(true);
4449
+ set(true);
4450
+ takeoverRef.current = () => Promise.resolve();
4409
4451
  return;
4410
4452
  }
4411
- const abort = new AbortController();
4453
+ let alive = true;
4454
+ let gen = 0;
4455
+ let pending = null;
4412
4456
  let release = null;
4413
- locks
4414
- .request(name, { signal: abort.signal }, () => {
4415
- setPrimary(true);
4416
- return new Promise(resolve => {
4417
- release = resolve;
4457
+ let waiters = [];
4458
+ const queue = (steal) => {
4459
+ const my = ++gen;
4460
+ const ac = new AbortController();
4461
+ pending = steal ? null : ac;
4462
+ locks
4463
+ .request(name, steal ? { steal: true } : { signal: ac.signal }, () => {
4464
+ if (!alive || my !== gen)
4465
+ return undefined;
4466
+ pending = null;
4467
+ set(true);
4468
+ const w = waiters;
4469
+ waiters = [];
4470
+ w.forEach(f => f());
4471
+ return new Promise(resolve => {
4472
+ release = resolve;
4473
+ });
4474
+ })
4475
+ .catch(() => {
4476
+ // Stolen by another tab (or this request aborted): not the runner;
4477
+ // wait for the lock again.
4478
+ if (!alive || my !== gen)
4479
+ return;
4480
+ release = null;
4481
+ set(false);
4482
+ queue(false);
4418
4483
  });
4419
- })
4420
- .catch(() => {
4421
- /* aborted on unmount */
4484
+ };
4485
+ queue(false);
4486
+ takeoverRef.current = () => new Promise(resolve => {
4487
+ if (primaryRef.current) {
4488
+ resolve();
4489
+ return;
4490
+ }
4491
+ waiters.push(resolve);
4492
+ pending?.abort();
4493
+ queue(true);
4422
4494
  });
4423
4495
  return () => {
4424
- abort.abort();
4496
+ alive = false;
4497
+ pending?.abort();
4425
4498
  release?.();
4426
- setPrimary(false);
4499
+ waiters.forEach(f => f());
4500
+ waiters = [];
4501
+ set(false);
4427
4502
  };
4428
4503
  }, [name]);
4429
- return primary;
4504
+ const takeover = React.useCallback(() => takeoverRef.current(), []);
4505
+ return { primary, takeover };
4430
4506
  }
4431
4507
  /* ------------------------------------------------------------------ */
4432
4508
  /* Putting the page back after a replay */
@@ -4535,7 +4611,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
4535
4611
  busyRef.current = busy;
4536
4612
  // No menu: never a menu step (the machine sees it as always open).
4537
4613
  const sidebarOpen = menu ? !!menu.open : true;
4538
- const primary = usePrimaryTab(`erp-guide:${organizationId}:${suite.key}`);
4614
+ const { primary, takeover } = usePrimaryTab(`erp-guide:${organizationId}:${suite.key}`);
4615
+ const primaryRef = React.useRef(primary);
4616
+ primaryRef.current = primary;
4539
4617
  // Progress: read once this tab runs the tour and the app is ready (for
4540
4618
  // the missions, or for the page guides alone: config.pagesReady).
4541
4619
  const [answer, setAnswer] = React.useState(null);
@@ -4544,7 +4622,8 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
4544
4622
  const hasPages = React.useMemo(() => validPages(suite).length > 0 || validSections(suite).length > 0 || validFlows(suite).length > 0, [suite]);
4545
4623
  const hasFlows = React.useMemo(() => validFlows(suite).length > 0, [suite]);
4546
4624
  const hasSections = React.useMemo(() => validSections(suite).length > 0, [suite]);
4547
- const enabled = primary && (missionsReady || (hasPages && pagesReady));
4625
+ // Every tab reads progress (0.1.78): the "?" menu is shown in all of them.
4626
+ const enabled = missionsReady || (hasPages && pagesReady);
4548
4627
  const loaded = React.useRef(false);
4549
4628
  React.useEffect(() => {
4550
4629
  if (!enabled || loaded.current)
@@ -4568,8 +4647,12 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
4568
4647
  }, [enabled, suite]);
4569
4648
  // The missions run for their audience; the page guides for theirs (every
4570
4649
  // member while pages are on), each with its own ready.
4571
- const missionsLive = !!answer && answer.audience !== "none" && !answer.newerOnServer && primary && missionsReady && !hidden;
4572
- const pagesLive = !!answer && hasPages && answer.pageAudience !== "none" && primary && pagesReady && !hidden;
4650
+ // On: this person has them here (every tab, for the "?" menu). Live: this
4651
+ // tab runs them (the primary tab only): steps, offers, the welcome, saves.
4652
+ const missionsOn = !!answer && answer.audience !== "none" && !answer.newerOnServer && missionsReady && !hidden;
4653
+ const pagesOn = !!answer && hasPages && answer.pageAudience !== "none" && pagesReady && !hidden;
4654
+ const missionsLive = missionsOn && primary;
4655
+ const pagesLive = pagesOn && primary;
4573
4656
  const live = missionsLive || pagesLive;
4574
4657
  /* ---------------- machine ---------------- */
4575
4658
  const [state, setState] = React.useState(null);
@@ -4662,6 +4745,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
4662
4745
  const row = await storeGuides(adapterRef.current, suite, cur.progress);
4663
4746
  if (row)
4664
4747
  savedKey.current = payloadKey(row, suite.app);
4748
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
4749
+ if (row)
4750
+ announceSaved();
4665
4751
  if (row && stateRef.current) {
4666
4752
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
4667
4753
  dispatchRef.current({ type: "serverRow", progress: row });
@@ -4718,6 +4804,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
4718
4804
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
4719
4805
  if (row)
4720
4806
  dispatchRef.current({ type: "pageRow", key: e.key, row, kind });
4807
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
4808
+ if (row)
4809
+ announceSaved();
4721
4810
  });
4722
4811
  }
4723
4812
  }
@@ -4764,6 +4853,95 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
4764
4853
  clearTimeout(toastTimer.current);
4765
4854
  };
4766
4855
  }, [flushSave]);
4856
+ /* ---------------- other tabs (0.1.78) ---------------- */
4857
+ // Progress saved in one tab reaches the others: a message on a channel
4858
+ // per organisation and app, and a fresh read when a tab comes back into
4859
+ // view. Only the server's rows are read, never another tab's state.
4860
+ const channelRef = React.useRef(null);
4861
+ const refreshNow = React.useCallback(async () => {
4862
+ const a = await loadGuides(adapterRef.current, suite, undefined, true);
4863
+ const cur = stateRef.current;
4864
+ if (!a || !cur)
4865
+ return;
4866
+ // The runner's own state wins while it is running something.
4867
+ if (primaryRef.current && (cur.screen.name !== "idle" || saving.current))
4868
+ return;
4869
+ if (a.progress.guide_version === cur.progress.guide_version && a.progress.rev >= cur.progress.rev) {
4870
+ savedKey.current = a.progress.rev > 0 ? payloadKey(a.progress, suite.app) : null;
4871
+ }
4872
+ dispatchRef.current({ type: "refresh", progress: a.progress, pages: a.pages, sections: a.sections, flows: a.flows });
4873
+ }, [suite]);
4874
+ const refreshRef = React.useRef(refreshNow);
4875
+ refreshRef.current = refreshNow;
4876
+ React.useEffect(() => {
4877
+ if (typeof BroadcastChannel === "undefined")
4878
+ return;
4879
+ let ch;
4880
+ try {
4881
+ ch = new BroadcastChannel(`erp-guide:${organizationId}:${suite.app}`);
4882
+ }
4883
+ catch {
4884
+ return;
4885
+ }
4886
+ channelRef.current = ch;
4887
+ ch.onmessage = (e) => {
4888
+ if (e.data?.type === "saved")
4889
+ void refreshRef.current();
4890
+ };
4891
+ return () => {
4892
+ channelRef.current = null;
4893
+ ch.close();
4894
+ };
4895
+ }, [organizationId, suite.app]);
4896
+ React.useEffect(() => {
4897
+ const onVisible = () => {
4898
+ if (document.visibilityState === "visible" && stateRef.current)
4899
+ void refreshRef.current();
4900
+ };
4901
+ document.addEventListener("visibilitychange", onVisible);
4902
+ return () => document.removeEventListener("visibilitychange", onVisible);
4903
+ }, []);
4904
+ function announceSaved() {
4905
+ try {
4906
+ channelRef.current?.postMessage({ type: "saved" });
4907
+ }
4908
+ catch {
4909
+ /* a closed channel: the next visibility change reads again */
4910
+ }
4911
+ }
4912
+ // The tab that lost the lock stops cleanly: whatever it was running ends
4913
+ // silently, without writing.
4914
+ React.useEffect(() => {
4915
+ if (primary || !stateRef.current)
4916
+ return;
4917
+ const cur = stateRef.current;
4918
+ if (saveTimer.current) {
4919
+ clearTimeout(saveTimer.current);
4920
+ saveTimer.current = null;
4921
+ }
4922
+ if (cur.screen.name !== "idle" || cur.panelOpen)
4923
+ dispatchRef.current({ type: "yield" });
4924
+ }, [primary]);
4925
+ /**
4926
+ * A guide item pressed in this tab: run it here. In a tab that is not the
4927
+ * runner, take the lock first (the other tab stops), read progress fresh,
4928
+ * then do it. Nothing resumes by itself in a tab taken over this way.
4929
+ */
4930
+ const viaRunner = React.useCallback((fn) => {
4931
+ if (primaryRef.current) {
4932
+ fn();
4933
+ return;
4934
+ }
4935
+ resumed.current = true;
4936
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
4937
+ flowResumed.current = true;
4938
+ void takeover()
4939
+ .then(() => refreshRef.current().catch(() => undefined))
4940
+ .then(() => {
4941
+ if (primaryRef.current)
4942
+ fn();
4943
+ });
4944
+ }, [takeover]);
4767
4945
  /* ---------------- boot: resume, welcome ---------------- */
4768
4946
  const screen = state?.screen ?? { name: "idle" };
4769
4947
  const resumed = React.useRef(false);
@@ -5412,12 +5590,13 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5412
5590
  !due;
5413
5591
  useAfterIdle(passiveDue, PASSIVE_IDLE_MS, busy, () => dispatchRef.current({ type: "idleCelebrate" }), [state?.pendingCelebrate?.join(",")]);
5414
5592
  React.useEffect(() => {
5415
- if (!missionsLive || !state) {
5593
+ // Every tab shows "ไกด์เริ่มต้น n/N"; pressing it runs it in this tab.
5594
+ if (!missionsOn || !state) {
5416
5595
  publishGuideEntry(null, null);
5417
5596
  return;
5418
5597
  }
5419
- publishGuideEntry({ done, total }, () => dispatchRef.current({ type: "openPanel" }));
5420
- }, [missionsLive, state, done, total]);
5598
+ publishGuideEntry({ done, total }, () => viaRunner(() => dispatchRef.current({ type: "openPanel" })));
5599
+ }, [missionsOn, state, done, total, viaRunner]);
5421
5600
  React.useEffect(() => () => publishGuideEntry(null, null), []);
5422
5601
  /* ---------------- page guides: this page's, the offer, the list ---------------- */
5423
5602
  // `when` anchors (LIFF's sub-screens) come and go without a route change:
@@ -5425,7 +5604,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5425
5604
  const hasWhen = React.useMemo(() => validPages(suite).some(p => !!p.when) || validSections(suite).length > 0, [suite]);
5426
5605
  const [domTick, setDomTick] = React.useState(0);
5427
5606
  React.useEffect(() => {
5428
- if (!pagesLive || !hasWhen || typeof MutationObserver === "undefined")
5607
+ if (!pagesOn || !hasWhen || typeof MutationObserver === "undefined")
5429
5608
  return;
5430
5609
  let t = null;
5431
5610
  const obs = new MutationObserver(records => {
@@ -5449,18 +5628,18 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5449
5628
  if (t)
5450
5629
  clearTimeout(t);
5451
5630
  };
5452
- }, [pagesLive, hasWhen]);
5631
+ }, [pagesOn, hasWhen]);
5453
5632
  const hasState = !!state;
5454
- const pageNow = React.useMemo(() => (pagesLive && hasState ? pageHere(suite, env()) : null),
5633
+ const pageNow = React.useMemo(() => (pagesOn && hasState ? pageHere(suite, env()) : null),
5455
5634
  // pathname/search/factsKey/domTick make it current.
5456
5635
  // eslint-disable-next-line react-hooks/exhaustive-deps
5457
- [pagesLive, hasState, suite, env, pathname, search, factsKey, domTick]);
5458
- const listed = React.useMemo(() => (pagesLive && ctxIdle ? listedPages(suite, ctxIdle) : []), [pagesLive, suite, ctxIdle]);
5459
- const sectionsNow = React.useMemo(() => pagesLive && hasState && hasSections
5636
+ [pagesOn, hasState, suite, env, pathname, search, factsKey, domTick]);
5637
+ const listed = React.useMemo(() => (pagesOn && ctxIdle ? listedPages(suite, ctxIdle) : []), [pagesOn, suite, ctxIdle]);
5638
+ const sectionsNow = React.useMemo(() => pagesOn && hasState && hasSections
5460
5639
  ? sectionsAvailable(suite, env()).map(k => ({ key: k, title: findSection(suite, k)?.title ?? "" }))
5461
5640
  : [],
5462
5641
  // eslint-disable-next-line react-hooks/exhaustive-deps
5463
- [pagesLive, hasState, hasSections, suite, env, pathname, search, factsKey, domTick]);
5642
+ [pagesOn, hasState, hasSections, suite, env, pathname, search, factsKey, domTick]);
5464
5643
  const sectionsSig = sectionsNow.map(x => x.key).join(",");
5465
5644
  const openSection = React.useCallback((key) => {
5466
5645
  openingSection.current = key;
@@ -5471,14 +5650,14 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5471
5650
  openingSection.current = null;
5472
5651
  }
5473
5652
  }, []);
5474
- const flowsNow = React.useMemo(() => pagesLive && hasState && hasFlows
5653
+ const flowsNow = React.useMemo(() => pagesOn && hasState && hasFlows
5475
5654
  ? flowsAvailable(suite, env()).map(k => ({ key: k, title: findFlow(suite, k)?.title ?? "" }))
5476
5655
  : [],
5477
5656
  // eslint-disable-next-line react-hooks/exhaustive-deps
5478
- [pagesLive, hasState, hasFlows, suite, env, pathname, search, factsKey]);
5657
+ [pagesOn, hasState, hasFlows, suite, env, pathname, search, factsKey]);
5479
5658
  const flowsSig = flowsNow.map(x => x.key).join(",");
5480
5659
  /** The flows the panel lists: this person's, startable here or with somewhere to go. */
5481
- const flowsListed = React.useMemo(() => (pagesLive && hasFlows && ctxIdle ? shownFlows(suite, ctxIdle).filter(f => onFlowStart(f, { code, pathname, search: new URLSearchParams(search) }) || !!flowHref(f, ctxIdle)) : []), [pagesLive, hasFlows, suite, ctxIdle, code, pathname, search]);
5660
+ const flowsListed = React.useMemo(() => (pagesOn && hasFlows && ctxIdle ? shownFlows(suite, ctxIdle).filter(f => onFlowStart(f, { code, pathname, search: new URLSearchParams(search) }) || !!flowHref(f, ctxIdle)) : []), [pagesOn, hasFlows, suite, ctxIdle, code, pathname, search]);
5482
5661
  const openFlowHere = React.useCallback((key) => dispatchRef.current({ type: "startFlow", flow: key }), []);
5483
5662
  const openPageHere = React.useCallback(() => {
5484
5663
  const p = pageHere(suite, env());
@@ -5486,7 +5665,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5486
5665
  dispatchRef.current({ type: "startPage", page: p.key });
5487
5666
  }, [suite, env]);
5488
5667
  React.useEffect(() => {
5489
- if (!pagesLive || !hasState) {
5668
+ if (!pagesOn || !hasState) {
5490
5669
  publishPageGuides(null, null, null);
5491
5670
  return;
5492
5671
  }
@@ -5496,10 +5675,10 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5496
5675
  sections: sectionsNow,
5497
5676
  flows: flowsNow,
5498
5677
  flowsListed: flowsListed.length,
5499
- }, openPageHere, () => dispatchRef.current({ type: "openPanel", section: "pages" }), openSection, openFlowHere, () => dispatchRef.current({ type: "openPanel", section: "flows" }));
5678
+ }, () => viaRunner(openPageHere), () => viaRunner(() => dispatchRef.current({ type: "openPanel", section: "pages" })), (key) => viaRunner(() => openSection(key)), (key) => viaRunner(() => openFlowHere(key)), () => viaRunner(() => dispatchRef.current({ type: "openPanel", section: "flows" })));
5500
5679
  // sectionsSig / flowsSig stand for sectionsNow / flowsNow.
5501
5680
  // eslint-disable-next-line react-hooks/exhaustive-deps
5502
- }, [pagesLive, hasState, pageNow, listed.length, openPageHere, sectionsSig, openSection, flowsSig, flowsListed.length, openFlowHere]);
5681
+ }, [pagesOn, hasState, pageNow, listed.length, openPageHere, sectionsSig, openSection, flowsSig, flowsListed.length, openFlowHere, viaRunner]);
5503
5682
  React.useEffect(() => () => publishPageGuides(null, null, null), []);
5504
5683
  // The first-visit offer: in "offer" mode, nothing else up, the welcome
5505
5684
  // not due here, and a moment of nothing loading on this page.
@@ -5696,6 +5875,32 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5696
5875
  dispatchRef.current({ type: "resumeFlow", flow: saved.flow, step: saved.step, context, write: saved.write === true });
5697
5876
  // eslint-disable-next-line react-hooks/exhaustive-deps
5698
5877
  }, [pagesLive, !!state, hasFlows, missionsLive]);
5878
+ // The layer sits above every dialog, and a dialog's buttons sit at its
5879
+ // bottom right, where the chip is: the chip covered them in every app.
5880
+ // While an app dialog is open, the chip is not drawn.
5881
+ const [appDialogShown, setAppDialogShown] = React.useState(false);
5882
+ React.useEffect(() => {
5883
+ if (!missionsLive || typeof MutationObserver === "undefined" || typeof document === "undefined" || !document.body)
5884
+ return;
5885
+ let raf = 0;
5886
+ const check = () => {
5887
+ raf = 0;
5888
+ setAppDialogShown(appDialogOpen());
5889
+ };
5890
+ check();
5891
+ const obs = new MutationObserver(records => {
5892
+ if (records.every(r => inGuideLayer(r.target)))
5893
+ return;
5894
+ if (!raf)
5895
+ raf = requestAnimationFrame(check);
5896
+ });
5897
+ obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-state", "role"] });
5898
+ return () => {
5899
+ obs.disconnect();
5900
+ if (raf)
5901
+ cancelAnimationFrame(raf);
5902
+ };
5903
+ }, [missionsLive]);
5699
5904
  if (!live || !state || !layer || !ctxIdle)
5700
5905
  return null;
5701
5906
  /* ---------------- render ---------------- */
@@ -5803,7 +6008,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
5803
6008
  .join(", ")
5804
6009
  : undefined,
5805
6010
  }));
5806
- const showChip = missionsLive && chipVisible(state, suite, ctxIdle);
6011
+ const showChip = missionsLive && !appDialogShown && chipVisible(state, suite, ctxIdle);
5807
6012
  // The panel: the missions (when this person has them, and not "คู่มือทุกหน้า"),
5808
6013
  // then the page guides they have.
5809
6014
  const panelMissions = missionsLive && total > 0 && state.panelSection === "all";
@@ -5956,4 +6161,4 @@ exports.useGuideEntry = useGuideEntry;
5956
6161
  exports.usePageGuideEntry = usePageGuideEntry;
5957
6162
  exports.usePageGuides = usePageGuides;
5958
6163
  exports.useSectionGuideValues = useSectionGuideValues;
5959
- //# sourceMappingURL=format-DQLPHq7h.cjs.map
6164
+ //# sourceMappingURL=format-CFXhGhta.cjs.map