@epic-web/workshop-app 5.0.0 → 5.0.2

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.
Files changed (133) hide show
  1. package/build/client/assets/{_-D0Tgngwe.js → _-ZHCWB__B.js} +2 -2
  2. package/build/client/assets/{_-D0Tgngwe.js.map → _-ZHCWB__B.js.map} +1 -1
  3. package/build/client/assets/_exerciseNumber-BFTlBdr4.js +2 -0
  4. package/build/client/assets/_exerciseNumber-BFTlBdr4.js.map +1 -0
  5. package/build/client/assets/{_exerciseNumber_._stepNumber-7kSd_6hH.js → _exerciseNumber_._stepNumber-_687iGFh.js} +2 -2
  6. package/build/client/assets/{_exerciseNumber_._stepNumber-7kSd_6hH.js.map → _exerciseNumber_._stepNumber-_687iGFh.js.map} +1 -1
  7. package/build/client/assets/_exerciseNumber_.finished-BEfn-nJi.js +2 -0
  8. package/build/client/assets/_exerciseNumber_.finished-BEfn-nJi.js.map +1 -0
  9. package/build/client/assets/{_layout-BwzhY4NI.js → _layout-BLJr2x2F.js} +2 -2
  10. package/build/client/assets/{_layout-BwzhY4NI.js.map → _layout-BLJr2x2F.js.map} +1 -1
  11. package/build/client/assets/_layout-BriOqd2R.js +2 -0
  12. package/build/client/assets/_layout-BriOqd2R.js.map +1 -0
  13. package/build/client/assets/{_layout-BUs3av-e.js → _layout-Cfbi6StB.js} +2 -2
  14. package/build/client/assets/{_layout-BUs3av-e.js.map → _layout-Cfbi6StB.js.map} +1 -1
  15. package/build/client/assets/_layout-frPHZWgR.js +6 -0
  16. package/build/client/assets/_layout-frPHZWgR.js.map +1 -0
  17. package/build/client/assets/{accordion-OfO-5m5D.js → accordion-DuE9VejZ.js} +2 -2
  18. package/build/client/assets/{accordion-OfO-5m5D.js.map → accordion-DuE9VejZ.js.map} +1 -1
  19. package/build/client/assets/{account-BatJhmSV.js → account-DDuV9rZX.js} +2 -2
  20. package/build/client/assets/{account-BatJhmSV.js.map → account-DDuV9rZX.js.map} +1 -1
  21. package/build/client/assets/app-wbMCZEiv.js +2 -0
  22. package/build/client/assets/{app-CJ9ElQg6.js.map → app-wbMCZEiv.js.map} +1 -1
  23. package/build/client/assets/{button-EE0aPg10.js → button-CMkJ8p0a.js} +2 -2
  24. package/build/client/assets/{button-EE0aPg10.js.map → button-CMkJ8p0a.js.map} +1 -1
  25. package/build/client/assets/{components-CME-nGId.js → components-DZ8XIeZ3.js} +2 -2
  26. package/build/client/assets/{components-CME-nGId.js.map → components-DZ8XIeZ3.js.map} +1 -1
  27. package/build/client/assets/diff-B6thd_Sf.js +2 -0
  28. package/build/client/assets/diff-B6thd_Sf.js.map +1 -0
  29. package/build/client/assets/diff-BEk79KPK.js +2 -0
  30. package/build/client/assets/{diff-BEEJhGiC.js.map → diff-BEk79KPK.js.map} +1 -1
  31. package/build/client/assets/{discord-BRTW4Rnh.js → discord-C9bVfiZ6.js} +2 -2
  32. package/build/client/assets/{discord-BRTW4Rnh.js.map → discord-C9bVfiZ6.js.map} +1 -1
  33. package/build/client/assets/discord-DYeU0QX6.js +2 -0
  34. package/build/client/assets/discord-DYeU0QX6.js.map +1 -0
  35. package/build/client/assets/{entry.client-3M2p-8I3.js → entry.client-CW5CUf_W.js} +2 -2
  36. package/build/client/assets/{entry.client-3M2p-8I3.js.map → entry.client-CW5CUf_W.js.map} +1 -1
  37. package/build/client/assets/{epic-video-DZzPuXR8.js → epic-video-bs7WmhbC.js} +122 -122
  38. package/build/client/assets/{epic-video-DZzPuXR8.js.map → epic-video-bs7WmhbC.js.map} +1 -1
  39. package/build/client/assets/{error-boundary-COkPRBOZ.js → error-boundary-BcGxKpte.js} +2 -2
  40. package/build/client/assets/{error-boundary-COkPRBOZ.js.map → error-boundary-BcGxKpte.js.map} +1 -1
  41. package/build/client/assets/finished-C2dgX1d-.js +2 -0
  42. package/build/client/assets/finished-C2dgX1d-.js.map +1 -0
  43. package/build/client/assets/{index-BwhlO_gF.js → index-BczhSZ3e.js} +2 -2
  44. package/build/client/assets/{index-BwhlO_gF.js.map → index-BczhSZ3e.js.map} +1 -1
  45. package/build/client/assets/{index-CXyf3Reb.js → index-BjNhezSK.js} +2 -2
  46. package/build/client/assets/{index-CXyf3Reb.js.map → index-BjNhezSK.js.map} +1 -1
  47. package/build/client/assets/{index-YNIH4TH8.js → index-BvihEwfB.js} +2 -2
  48. package/build/client/assets/{index-YNIH4TH8.js.map → index-BvihEwfB.js.map} +1 -1
  49. package/build/client/assets/index-C2yr7Uiu.js +2 -0
  50. package/build/client/assets/index-C2yr7Uiu.js.map +1 -0
  51. package/build/client/assets/{index-D-l_qaLC.js → index-CuV1bRbu.js} +2 -2
  52. package/build/client/assets/{index-D-l_qaLC.js.map → index-CuV1bRbu.js.map} +1 -1
  53. package/build/client/assets/{index-Dx5GmdYq.js → index-DBrRQJxF.js} +2 -2
  54. package/build/client/assets/{index-Dx5GmdYq.js.map → index-DBrRQJxF.js.map} +1 -1
  55. package/build/client/assets/{index-1cKOJFpX.js → index-DF_XBInP.js} +2 -2
  56. package/build/client/assets/{index-1cKOJFpX.js.map → index-DF_XBInP.js.map} +1 -1
  57. package/build/client/assets/{index-B-hHvmeV.js → index-YtpQLUzj.js} +2 -2
  58. package/build/client/assets/{index-B-hHvmeV.js.map → index-YtpQLUzj.js.map} +1 -1
  59. package/build/client/assets/{loading-sXkYDMsx.js → loading-Br41_Pbf.js} +2 -2
  60. package/build/client/assets/{loading-sXkYDMsx.js.map → loading-Br41_Pbf.js.map} +1 -1
  61. package/build/client/assets/{login-Cc73KLYm.js → login-kjV7hrVt.js} +2 -2
  62. package/build/client/assets/{login-Cc73KLYm.js.map → login-kjV7hrVt.js.map} +1 -1
  63. package/build/client/assets/manifest-32ad9742.js +1 -0
  64. package/build/client/assets/{mdx-DwC5Oacq.js → mdx-CRxPouxB.js} +2 -2
  65. package/build/client/assets/{mdx-DwC5Oacq.js.map → mdx-CRxPouxB.js.map} +1 -1
  66. package/build/client/assets/{misc-CxCgA-_O.js → misc-BE75ioh8.js} +2 -2
  67. package/build/client/assets/{misc-CxCgA-_O.js.map → misc-BE75ioh8.js.map} +1 -1
  68. package/build/client/assets/{nav-chevrons-g-C0ilNz.js → nav-chevrons-DYiI8EMU.js} +2 -2
  69. package/build/client/assets/{nav-chevrons-g-C0ilNz.js.map → nav-chevrons-DYiI8EMU.js.map} +1 -1
  70. package/build/client/assets/{onboarding-DE9gclYS.js → onboarding-B4Z_yevk.js} +2 -2
  71. package/build/client/assets/{onboarding-DE9gclYS.js.map → onboarding-B4Z_yevk.js.map} +1 -1
  72. package/build/client/assets/{pe-CUZaIcdt.js → pe-CvPIToj6.js} +2 -2
  73. package/build/client/assets/{pe-CUZaIcdt.js.map → pe-CvPIToj6.js.map} +1 -1
  74. package/build/client/assets/{presence-Cr--lRCr.js → presence-Dd98AJ_5.js} +2 -2
  75. package/build/client/assets/{presence-Cr--lRCr.js.map → presence-Dd98AJ_5.js.map} +1 -1
  76. package/build/client/assets/{preview-C7dtR2VR.js → preview-DZcdG4kw.js} +2 -2
  77. package/build/client/assets/{preview-C7dtR2VR.js.map → preview-DZcdG4kw.js.map} +1 -1
  78. package/build/client/assets/{product-BAWG1Vut.js → product-mjsTrqXs.js} +2 -2
  79. package/build/client/assets/{product-BAWG1Vut.js.map → product-mjsTrqXs.js.map} +1 -1
  80. package/build/client/assets/{progress-BFm2U-l5.js → progress-Co-59mG2.js} +2 -2
  81. package/build/client/assets/{progress-BFm2U-l5.js.map → progress-Co-59mG2.js.map} +1 -1
  82. package/build/client/assets/{progress-bar-D3kudPcr.js → progress-bar-F8_2mvYp.js} +2 -2
  83. package/build/client/assets/{progress-bar-D3kudPcr.js.map → progress-bar-F8_2mvYp.js.map} +1 -1
  84. package/build/client/assets/{request-info-CEhUGODY.js → request-info-DGnmXtfj.js} +2 -2
  85. package/build/client/assets/{request-info-CEhUGODY.js.map → request-info-DGnmXtfj.js.map} +1 -1
  86. package/build/client/assets/revalidation-ws-DcvYvzyj.js +2 -0
  87. package/build/client/assets/revalidation-ws-DcvYvzyj.js.map +1 -0
  88. package/build/client/assets/root-Cl86OUog.js +11 -0
  89. package/build/client/assets/root-Cl86OUog.js.map +1 -0
  90. package/build/client/assets/{set-playground-DW0yVaNn.js → set-playground-pMKmtPtz.js} +2 -2
  91. package/build/client/assets/set-playground-pMKmtPtz.js.map +1 -0
  92. package/build/client/assets/{support-hcqGIpir.js → support-B0E_F4Zh.js} +2 -2
  93. package/build/client/assets/{support-hcqGIpir.js.map → support-B0E_F4Zh.js.map} +1 -1
  94. package/build/client/assets/test-B6zIK2V6.js +2 -0
  95. package/build/client/assets/{test-N2HloCnX.js.map → test-B6zIK2V6.js.map} +1 -1
  96. package/build/client/assets/{tests-DUg6VXec.js → tests-BeAEgPAw.js} +3 -3
  97. package/build/client/assets/tests-BeAEgPAw.js.map +1 -0
  98. package/build/client/assets/{tooltip-kD4kSf1i.js → tooltip-6-WS-Xux.js} +2 -2
  99. package/build/client/assets/{tooltip-kD4kSf1i.js.map → tooltip-6-WS-Xux.js.map} +1 -1
  100. package/build/client/assets/{use-event-source-A_0lEOPX.js → use-event-source-CCGBLG92.js} +2 -2
  101. package/build/client/assets/{use-event-source-A_0lEOPX.js.map → use-event-source-CCGBLG92.js.map} +1 -1
  102. package/build/client/assets/{user-DvujSs-t.js → user-Boua6jiU.js} +2 -2
  103. package/build/client/assets/{user-DvujSs-t.js.map → user-Boua6jiU.js.map} +1 -1
  104. package/build/client/assets/{workshop-config-CL4F08kr.js → workshop-config-Ce9sSc3I.js} +2 -2
  105. package/build/client/assets/{workshop-config-CL4F08kr.js.map → workshop-config-Ce9sSc3I.js.map} +1 -1
  106. package/build/server/index.js +529 -412
  107. package/build/server/index.js.map +1 -1
  108. package/dist/server/index.js +83 -34
  109. package/package.json +5 -5
  110. package/build/client/assets/_exerciseNumber-j9COGU-R.js +0 -2
  111. package/build/client/assets/_exerciseNumber-j9COGU-R.js.map +0 -1
  112. package/build/client/assets/_exerciseNumber_.finished-DDNPeo8x.js +0 -2
  113. package/build/client/assets/_exerciseNumber_.finished-DDNPeo8x.js.map +0 -1
  114. package/build/client/assets/_layout-BPwIOXxN.js +0 -6
  115. package/build/client/assets/_layout-BPwIOXxN.js.map +0 -1
  116. package/build/client/assets/_layout-DnttUdzs.js +0 -2
  117. package/build/client/assets/_layout-DnttUdzs.js.map +0 -1
  118. package/build/client/assets/app-CJ9ElQg6.js +0 -2
  119. package/build/client/assets/diff-BEEJhGiC.js +0 -2
  120. package/build/client/assets/diff-BXHLJqTK.js +0 -2
  121. package/build/client/assets/diff-BXHLJqTK.js.map +0 -1
  122. package/build/client/assets/discord-BhzUjmbI.js +0 -2
  123. package/build/client/assets/discord-BhzUjmbI.js.map +0 -1
  124. package/build/client/assets/finished-Dop_5v80.js +0 -2
  125. package/build/client/assets/finished-Dop_5v80.js.map +0 -1
  126. package/build/client/assets/index-ZZCxObNp.js +0 -2
  127. package/build/client/assets/index-ZZCxObNp.js.map +0 -1
  128. package/build/client/assets/manifest-e53d022a.js +0 -1
  129. package/build/client/assets/root-9wVBEzOq.js +0 -68
  130. package/build/client/assets/root-9wVBEzOq.js.map +0 -1
  131. package/build/client/assets/set-playground-DW0yVaNn.js.map +0 -1
  132. package/build/client/assets/test-N2HloCnX.js +0 -2
  133. package/build/client/assets/tests-DUg6VXec.js.map +0 -1
@@ -1,4 +1,3 @@
1
- var _a;
2
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
2
  import { PassThrough } from "stream";
4
3
  import { createReadableStreamFromReadable, redirect, json as json$1, createCookieSessionStorage, defer } from "@remix-run/node";
@@ -16,12 +15,11 @@ import { LRUCache } from "lru-cache";
16
15
  import md5 from "md5-hex";
17
16
  import { z } from "zod";
18
17
  import fs$1 from "node:fs";
18
+ import chokidar from "chokidar";
19
19
  import "@total-typescript/ts-reset";
20
+ import closeWithGrace from "close-with-grace";
20
21
  import { execa } from "execa";
21
- import { glob } from "glob";
22
22
  import { isGitIgnored, globby } from "globby";
23
- import chokidar from "chokidar";
24
- import closeWithGrace from "close-with-grace";
25
23
  import fs from "fs";
26
24
  import { remarkCodeBlocksShiki } from "@kentcdodds/md-temp";
27
25
  import { bundleMDX } from "mdx-bundler";
@@ -35,13 +33,14 @@ import net from "node:net";
35
33
  import chalk from "chalk";
36
34
  import fkill from "fkill";
37
35
  import { cssBundleHref } from "@remix-run/css-bundle";
38
- import * as React from "react";
39
- import React__default, { useRef, useEffect, useMemo, useState, createContext, useContext, useSyncExternalStore, forwardRef, useImperativeHandle, Suspense, useReducer } from "react";
36
+ import { promiseHash } from "remix-utils/promise";
40
37
  import { useSpinDelay } from "spin-delay";
41
38
  import { Index as Index$1 } from "confetti-react";
42
39
  import { ClientOnly } from "remix-utils/client-only";
43
40
  import slugify from "@sindresorhus/slugify";
44
41
  import clsx$1, { clsx } from "clsx";
42
+ import * as React from "react";
43
+ import React__default, { useRef, useEffect, useMemo, useState, createContext, useContext, useSyncExternalStore, forwardRef, useImperativeHandle, Suspense, useReducer } from "react";
45
44
  import { extendTailwindMerge } from "tailwind-merge";
46
45
  import { Toaster, toast } from "sonner";
47
46
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
@@ -219,6 +218,9 @@ const compiledMarkdownCache = makeSingletonCache(
219
218
  const compiledCodeCache = makeSingletonCache("CompiledCodeCache");
220
219
  const ogCache = makeSingletonCache("OgCache");
221
220
  const compiledInstructionMarkdownCache = makeSingletonFsCache("CompiledInstructionMarkdownCache");
221
+ const dirModifiedTimeCache = makeSingletonCache(
222
+ "DirModifiedTimeCache"
223
+ );
222
224
  const cacheDir = path.join(os.homedir(), ".epicshop", "cache");
223
225
  const fsCache = makeSingletonFsCache("FsCache");
224
226
  async function getAllFileCacheEntries() {
@@ -410,12 +412,12 @@ async function getAuthInfo() {
410
412
  return (data == null ? void 0 : data.authInfo) ?? null;
411
413
  }
412
414
  async function getUserInfo() {
413
- var _a2, _b;
415
+ var _a, _b;
414
416
  const db = await readDb();
415
417
  if (!(db == null ? void 0 : db.authInfo)) return null;
416
418
  return {
417
419
  id: db.authInfo.id,
418
- name: ((_a2 = db.discordMember) == null ? void 0 : _a2.displayName) ?? db.authInfo.name ?? null,
420
+ name: ((_a = db.discordMember) == null ? void 0 : _a.displayName) ?? db.authInfo.name ?? null,
419
421
  email: db.authInfo.email,
420
422
  avatarUrl: ((_b = db.discordMember) == null ? void 0 : _b.avatarURL) ?? getGravatar({ email: db.authInfo.email, size: 288 })
421
423
  };
@@ -598,35 +600,6 @@ function uniqueUsers(users) {
598
600
  return true;
599
601
  });
600
602
  }
601
- let watcher = global.__change_tracker_watcher__;
602
- function getWatcher() {
603
- if (process.env.EPICSHOP_DEPLOYED ?? process.env.EPICSHOP_ENABLE_WATCHER !== "true") {
604
- return null;
605
- }
606
- if (watcher) return watcher;
607
- const workshopRoot2 = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd();
608
- watcher = chokidar.watch(workshopRoot2, {
609
- ignoreInitial: true,
610
- ignored: [
611
- "**/.git/**",
612
- "**/node_modules/**",
613
- "**/build/**",
614
- "**/public/build/**",
615
- "**/playwright-report/**",
616
- "**/dist/**",
617
- "**/.cache/**"
618
- ]
619
- });
620
- global.__change_tracker_watcher__ = watcher;
621
- return watcher;
622
- }
623
- function getOptionalWatcher() {
624
- return watcher;
625
- }
626
- (_a = global.__change_tracker_close_with_grace_return__) == null ? void 0 : _a.uninstall();
627
- global.__change_tracker_close_with_grace_return__ = closeWithGrace(
628
- () => watcher == null ? void 0 : watcher.close()
629
- );
630
603
  function trimCodeBlocks() {
631
604
  return async function transformer(tree) {
632
605
  visit(tree, "element", (preNode) => {
@@ -683,10 +656,7 @@ async function compileMdx(file, {
683
656
  forceFresh = await shouldForceFresh({ forceFresh, request, key });
684
657
  const existingCacheEntry = await compiledInstructionMarkdownCache.get(key);
685
658
  if (!forceFresh && existingCacheEntry) {
686
- const compiledTime = existingCacheEntry.metadata.createdTime;
687
- if (stat.mtimeMs <= compiledTime) {
688
- return existingCacheEntry.value;
689
- }
659
+ forceFresh = stat.mtimeMs > existingCacheEntry.metadata.createdTime;
690
660
  }
691
661
  return cachified({
692
662
  key,
@@ -787,22 +757,23 @@ async function compileMarkdownString(markdownString) {
787
757
  }
788
758
  });
789
759
  }
790
- let _queue = null;
791
- async function getQueue() {
792
- if (_queue) return _queue;
793
- _queue = new PQueue({
760
+ let _queue$1 = null;
761
+ async function getQueue$1() {
762
+ if (_queue$1) return _queue$1;
763
+ _queue$1 = new PQueue({
794
764
  concurrency: 1,
795
765
  throwOnTimeout: true,
796
766
  timeout: 1e3 * 60
797
767
  });
798
- return _queue;
768
+ return _queue$1;
799
769
  }
800
770
  async function queuedBundleMDX(...args) {
801
- const queue = await getQueue();
771
+ const queue = await getQueue$1();
802
772
  const result = await queue.add(() => bundleMDX(...args));
803
773
  return result;
804
774
  }
805
775
  const workshopRoot$1 = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd();
776
+ const rootPkgJson = path$1.join(workshopRoot$1, "package.json");
806
777
  const StackBlitzConfigSchema = z.object({
807
778
  // we default this to `${exerciseTitle} (${type})`
808
779
  title: z.string().optional(),
@@ -867,12 +838,14 @@ const WorkshopConfigSchema = z.object({
867
838
  }
868
839
  };
869
840
  });
870
- let cachedConfig = null;
871
- function bustWorkshopConfigCache() {
872
- cachedConfig = null;
873
- }
841
+ const configCache = {
842
+ config: null,
843
+ modified: 0
844
+ };
874
845
  function getWorkshopConfig() {
875
- if (cachedConfig) return cachedConfig;
846
+ if (configCache.config && configCache.modified > fs$1.statSync(rootPkgJson).mtimeMs) {
847
+ return configCache.config;
848
+ }
876
849
  const packageJsonPath = path$1.join(workshopRoot$1, "package.json");
877
850
  let packageJson;
878
851
  try {
@@ -909,7 +882,8 @@ function getWorkshopConfig() {
909
882
  }
910
883
  try {
911
884
  const parsedConfig = WorkshopConfigSchema.parse(epicshopConfig);
912
- cachedConfig = parsedConfig;
885
+ configCache.config = parsedConfig;
886
+ configCache.modified = fs$1.statSync(rootPkgJson).mtimeMs;
913
887
  return parsedConfig;
914
888
  } catch (error) {
915
889
  if (error instanceof z.ZodError) {
@@ -928,7 +902,7 @@ async function getStackBlitzUrl({
928
902
  title,
929
903
  type
930
904
  }) {
931
- var _a2;
905
+ var _a;
932
906
  const workshopConfig = getWorkshopConfig();
933
907
  const appConfig = await getAppConfig(fullPath);
934
908
  if (appConfig.stackBlitzConfig === null) return null;
@@ -939,7 +913,7 @@ async function getStackBlitzUrl({
939
913
  const githubPart = githubRootUrl.pathname;
940
914
  const stackBlitzConfig = {
941
915
  ...appConfig.stackBlitzConfig,
942
- title: ((_a2 = appConfig.stackBlitzConfig) == null ? void 0 : _a2.title) ?? `${title} (${type})`
916
+ title: ((_a = appConfig.stackBlitzConfig) == null ? void 0 : _a.title) ?? `${title} (${type})`
943
917
  };
944
918
  const params = new URLSearchParams(stackBlitzConfig);
945
919
  const relativePath = fullPath.replace(`${workshopRoot$1}${path$1.sep}`, "");
@@ -950,7 +924,7 @@ async function getStackBlitzUrl({
950
924
  return stackBlitzUrl.toString();
951
925
  }
952
926
  async function getAppConfig(fullPath) {
953
- var _a2, _b;
927
+ var _a, _b;
954
928
  const workshopConfig = getWorkshopConfig();
955
929
  let epicshopConfig = {};
956
930
  let scripts = {};
@@ -972,7 +946,7 @@ async function getAppConfig(fullPath) {
972
946
  };
973
947
  }),
974
948
  testTab: z.object({
975
- enabled: z.boolean().optional().default(((_a2 = workshopConfig.testTab) == null ? void 0 : _a2.enabled) ?? true)
949
+ enabled: z.boolean().optional().default(((_a = workshopConfig.testTab) == null ? void 0 : _a.enabled) ?? true)
976
950
  }).default({}),
977
951
  scripts: z.object({
978
952
  test: z.string().optional(),
@@ -1030,6 +1004,64 @@ function getEnv() {
1030
1004
  EPICSHOP_DEPLOYED: process.env.EPICSHOP_DEPLOYED === "true" || process.env.EPICSHOP_DEPLOYED === "1"
1031
1005
  };
1032
1006
  }
1007
+ async function getDirModifiedTime(dir, { forceFresh = false } = {}) {
1008
+ const result = await cachified({
1009
+ key: dir,
1010
+ cache: dirModifiedTimeCache,
1011
+ ttl: 200,
1012
+ forceFresh,
1013
+ getFreshValue: () => getDirModifiedTimeImpl(dir)
1014
+ });
1015
+ return result;
1016
+ }
1017
+ async function getDirModifiedTimeImpl(dir) {
1018
+ const isIgnored = await isGitIgnored({ cwd: dir });
1019
+ const files = await fs$1.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
1020
+ const modifiedTimes2 = [];
1021
+ for (const file of files) {
1022
+ if (isIgnored(file.name)) continue;
1023
+ const filePath = path$1.join(dir, file.name);
1024
+ if (file.isDirectory()) {
1025
+ modifiedTimes2.push(await getDirModifiedTime(filePath));
1026
+ } else {
1027
+ try {
1028
+ const { mtimeMs } = await fs$1.promises.stat(filePath);
1029
+ modifiedTimes2.push(mtimeMs);
1030
+ } catch {
1031
+ }
1032
+ }
1033
+ }
1034
+ try {
1035
+ const { mtimeMs } = await fs$1.promises.stat(dir);
1036
+ modifiedTimes2.push(mtimeMs);
1037
+ } catch {
1038
+ }
1039
+ return Math.max(-1, ...modifiedTimes2);
1040
+ }
1041
+ async function modifiedMoreRecentlyThan(time2, ...dirs) {
1042
+ const modifiedTimePromises = dirs.map((dir) => getDirModifiedTime(dir));
1043
+ const allFinishedPromise = Promise.all(modifiedTimePromises);
1044
+ const firstMoreRecentPromise = modifiedTimePromises.map(
1045
+ (p) => p.then((t) => t > time2 ? true : allFinishedPromise.then(() => false))
1046
+ );
1047
+ const firstMoreRecent = await Promise.race(firstMoreRecentPromise);
1048
+ return firstMoreRecent;
1049
+ }
1050
+ let _queue = null;
1051
+ function getQueue() {
1052
+ if (_queue) return _queue;
1053
+ _queue = new PQueue({
1054
+ concurrency: 10,
1055
+ throwOnTimeout: true,
1056
+ timeout: 1e3 * 60
1057
+ });
1058
+ return _queue;
1059
+ }
1060
+ async function queuedGetDirModifiedTime(...args) {
1061
+ const queue = getQueue();
1062
+ const result = await queue.add(() => getDirModifiedTime(...args));
1063
+ return result || -1;
1064
+ }
1033
1065
  function getErrorMessage$1(error) {
1034
1066
  if (typeof error === "string") return error;
1035
1067
  if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
@@ -1042,9 +1074,9 @@ const isDeployed$1 = process.env.EPICSHOP_DEPLOYED === "true" || process.env.EPI
1042
1074
  const devProcesses = remember("dev_processes", getDevProcessesMap);
1043
1075
  const testProcesses = remember("test_processes", getTestProcessesMap);
1044
1076
  function getDevProcessesMap() {
1045
- var _a2;
1077
+ var _a;
1046
1078
  const procs = /* @__PURE__ */ new Map();
1047
- (_a2 = global.__process_dev_close_with_grace_return__) == null ? void 0 : _a2.uninstall();
1079
+ (_a = global.__process_dev_close_with_grace_return__) == null ? void 0 : _a.uninstall();
1048
1080
  global.__process_dev_close_with_grace_return__ = closeWithGrace(async () => {
1049
1081
  for (const [name, proc] of procs.entries()) {
1050
1082
  console.log("closing", name);
@@ -1054,9 +1086,9 @@ function getDevProcessesMap() {
1054
1086
  return procs;
1055
1087
  }
1056
1088
  function getTestProcessesMap() {
1057
- var _a2;
1089
+ var _a;
1058
1090
  const procs = /* @__PURE__ */ new Map();
1059
- (_a2 = global.__process_test_close_with_grace_return__) == null ? void 0 : _a2.uninstall();
1091
+ (_a = global.__process_test_close_with_grace_return__) == null ? void 0 : _a.uninstall();
1060
1092
  global.__process_test_close_with_grace_return__ = closeWithGrace(async () => {
1061
1093
  for (const [id, proc] of procs.entries()) {
1062
1094
  if (proc.process) {
@@ -1285,7 +1317,7 @@ async function waitForPortToBeAvailable(port2) {
1285
1317
  console.error("Timed out waiting for the port to become available");
1286
1318
  }
1287
1319
  }
1288
- let initialized$1 = false;
1320
+ global.__epicshop_apps_initialized__ ?? (global.__epicshop_apps_initialized__ = false);
1289
1321
  const workshopRoot = process.env.EPICSHOP_CONTEXT_CWD = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd();
1290
1322
  const playgroundAppNameInfoPath = path$1.join(
1291
1323
  workshopRoot,
@@ -1417,28 +1449,40 @@ const modifiedTimes = remember(
1417
1449
  "modified_times",
1418
1450
  () => /* @__PURE__ */ new Map()
1419
1451
  );
1420
- function init() {
1421
- var _a2;
1422
- if (initialized$1) return;
1423
- initialized$1 = true;
1452
+ async function init() {
1453
+ if (global.__epicshop_apps_initialized__) return;
1454
+ global.__epicshop_apps_initialized__ = true;
1424
1455
  const config = getWorkshopConfig();
1425
1456
  process.env.EPICSHOP_GITHUB_REPO = config.githubRepo;
1426
1457
  process.env.EPICSHOP_GITHUB_ROOT = config.githubRoot;
1427
1458
  init$1();
1428
1459
  global.ENV = getEnv();
1429
- async function handleFileChanges(event, filePath) {
1430
- if (filePath === path$1.join(workshopRoot, "package.json")) {
1431
- bustWorkshopConfigCache();
1432
- }
1433
- const apps = await getApps();
1434
- for (const app of apps) {
1435
- if (filePath.startsWith(app.fullPath)) {
1436
- modifiedTimes.set(app.fullPath, Date.now());
1437
- break;
1460
+ if (!ENV.EPICSHOP_DEPLOYED) {
1461
+ const isIgnored = await isGitIgnored({ cwd: workshopRoot });
1462
+ const filesToWatch = ["README.mdx", "FINISHED.mdx", "package.json"];
1463
+ const chok = chokidar.watch(["examples", "playground", "exercises"], {
1464
+ cwd: workshopRoot,
1465
+ ignoreInitial: true,
1466
+ ignored(filePath, stats) {
1467
+ if (isIgnored(filePath)) return true;
1468
+ if (filePath.includes(".git")) return true;
1469
+ if (stats == null ? void 0 : stats.isDirectory()) {
1470
+ if (filePath.endsWith("playground")) return false;
1471
+ const pathParts = filePath.split(path$1.sep);
1472
+ if (pathParts.at(-2) === "examples") return false;
1473
+ if (pathParts.at(-3) === "exercises") return false;
1474
+ if (pathParts.at(-2) === "exercises") return false;
1475
+ if (pathParts.at(-1) === "exercises") return false;
1476
+ return true;
1477
+ }
1478
+ return (stats == null ? void 0 : stats.isFile()) ? !filesToWatch.some((file) => filePath.endsWith(file)) : false;
1438
1479
  }
1439
- }
1480
+ });
1481
+ chok.on("all", (_event, filePath) => {
1482
+ setModifiedTimesForAppDirs(path$1.join(workshopRoot, filePath));
1483
+ });
1484
+ closeWithGrace(() => chok.close());
1440
1485
  }
1441
- (_a2 = getWatcher()) == null ? void 0 : _a2.on("all", handleFileChanges);
1442
1486
  }
1443
1487
  function getForceFresh$1(cacheEntry) {
1444
1488
  if (!cacheEntry) return true;
@@ -1446,6 +1490,17 @@ function getForceFresh$1(cacheEntry) {
1446
1490
  if (!latestModifiedTime) return void 0;
1447
1491
  return latestModifiedTime > cacheEntry.metadata.createdTime ? true : void 0;
1448
1492
  }
1493
+ function setModifiedTimesForAppDirs(...filePaths) {
1494
+ const now = Date.now();
1495
+ for (const filePath of filePaths) {
1496
+ const appDir2 = getAppPathFromFilePath(filePath);
1497
+ if (appDir2) {
1498
+ modifiedTimes.set(appDir2, now);
1499
+ } else {
1500
+ console.warn(`filePath ${filePath} does not match any app dir`);
1501
+ }
1502
+ }
1503
+ }
1449
1504
  function getForceFreshForDir(cacheEntry, ...dirs) {
1450
1505
  const truthyDirs = dirs.filter(Boolean);
1451
1506
  for (const d of truthyDirs) {
@@ -1499,9 +1554,9 @@ function getAppDirInfo(appDir2) {
1499
1554
  return { stepNumber, type, subtitle };
1500
1555
  }
1501
1556
  function extractExerciseNumber(dir) {
1502
- var _a2, _b;
1557
+ var _a, _b;
1503
1558
  const regex = /^(?<number>\d+)\./;
1504
- const number = (_b = (_a2 = regex.exec(dir)) == null ? void 0 : _a2.groups) == null ? void 0 : _b.number;
1559
+ const number = (_b = (_a = regex.exec(dir)) == null ? void 0 : _a.groups) == null ? void 0 : _b.number;
1505
1560
  if (!number) {
1506
1561
  return null;
1507
1562
  }
@@ -1555,7 +1610,7 @@ async function getApps({
1555
1610
  request,
1556
1611
  forceFresh
1557
1612
  } = {}) {
1558
- if (!initialized$1) init();
1613
+ await init();
1559
1614
  const key = "apps";
1560
1615
  const apps = await cachified({
1561
1616
  key,
@@ -1568,10 +1623,24 @@ async function getApps({
1568
1623
  ttl: 1e3 * 60 * 60 * 24,
1569
1624
  forceFresh: forceFresh ?? getForceFresh$1(appsCache.get(key)),
1570
1625
  getFreshValue: async () => {
1571
- const playgroundApp = await getPlaygroundApp({ request, timings });
1572
- const problemApps = await getProblemApps({ request, timings });
1573
- const solutionApps = await getSolutionApps({ request, timings });
1574
- const exampleApps = await getExampleApps({ request, timings });
1626
+ const [playgroundApp, problemApps, solutionApps, exampleApps] = await Promise.all([
1627
+ time(() => getPlaygroundApp({ request, timings }), {
1628
+ type: "getPlaygroundApp",
1629
+ timings
1630
+ }),
1631
+ time(() => getProblemApps({ request, timings }), {
1632
+ type: "getProblemApps",
1633
+ timings
1634
+ }),
1635
+ time(() => getSolutionApps({ request, timings }), {
1636
+ type: "getSolutionApps",
1637
+ timings
1638
+ }),
1639
+ time(() => getExampleApps({ request, timings }), {
1640
+ type: "getExampleApps",
1641
+ timings
1642
+ })
1643
+ ]);
1575
1644
  const sortedApps = [
1576
1645
  playgroundApp,
1577
1646
  ...problemApps,
@@ -1623,7 +1692,7 @@ const AppIdInfoSchema = z.object({
1623
1692
  type: z.union([z.literal("problem"), z.literal("solution")])
1624
1693
  });
1625
1694
  function extractNumbersAndTypeFromAppNameOrPath(fullPathOrAppName) {
1626
- var _a2;
1695
+ var _a;
1627
1696
  const info = {};
1628
1697
  if (fullPathOrAppName.includes(path$1.sep)) {
1629
1698
  const relativePath = fullPathOrAppName.replace(
@@ -1634,7 +1703,7 @@ function extractNumbersAndTypeFromAppNameOrPath(fullPathOrAppName) {
1634
1703
  if (!exerciseNumberPart || !stepNumberPart) return null;
1635
1704
  const exerciseNumber = exerciseNumberPart.split(".")[0];
1636
1705
  const stepNumber = stepNumberPart.split(".")[0];
1637
- const type = (_a2 = stepNumberPart.split(".")[1]) == null ? void 0 : _a2.split(".")[0];
1706
+ const type = (_a = stepNumberPart.split(".")[1]) == null ? void 0 : _a.split(".")[0];
1638
1707
  info.exerciseNumber = exerciseNumber;
1639
1708
  info.stepNumber = stepNumber;
1640
1709
  info.type = type;
@@ -1650,18 +1719,28 @@ function extractNumbersAndTypeFromAppNameOrPath(fullPathOrAppName) {
1650
1719
  }
1651
1720
  async function getProblemDirs() {
1652
1721
  const exercisesDir = path$1.join(workshopRoot, "exercises");
1653
- const problemDirs = (await glob("**/*.problem*", {
1654
- cwd: exercisesDir,
1655
- ignore: "node_modules/**"
1656
- })).map((p) => path$1.join(exercisesDir, p));
1722
+ const problemDirs = [];
1723
+ const exerciseSubDirs = await readDir(exercisesDir);
1724
+ for (const subDir of exerciseSubDirs) {
1725
+ const fullSubDir = path$1.join(exercisesDir, subDir);
1726
+ const subDirContents = await readDir(fullSubDir).catch(() => null);
1727
+ if (!subDirContents) continue;
1728
+ const problemSubDirs = subDirContents.filter((dir) => dir.includes(".problem")).map((dir) => path$1.join(fullSubDir, dir));
1729
+ problemDirs.push(...problemSubDirs);
1730
+ }
1657
1731
  return problemDirs;
1658
1732
  }
1659
1733
  async function getSolutionDirs() {
1660
1734
  const exercisesDir = path$1.join(workshopRoot, "exercises");
1661
- const solutionDirs = (await glob("**/*.solution*", {
1662
- cwd: exercisesDir,
1663
- ignore: "node_modules/**"
1664
- })).map((p) => path$1.join(exercisesDir, p));
1735
+ const solutionDirs = [];
1736
+ const exerciseSubDirs = await readDir(exercisesDir);
1737
+ for (const subDir of exerciseSubDirs) {
1738
+ const fullSubDir = path$1.join(exercisesDir, subDir);
1739
+ const subDirContents = await readDir(fullSubDir).catch(() => null);
1740
+ if (!subDirContents) continue;
1741
+ const solutionSubDirs = subDirContents.filter((dir) => dir.includes(".solution")).map((dir) => path$1.join(fullSubDir, dir));
1742
+ solutionDirs.push(...solutionSubDirs);
1743
+ }
1665
1744
  return solutionDirs;
1666
1745
  }
1667
1746
  function getPathname(fullPath) {
@@ -1833,10 +1912,10 @@ async function getPlaygroundApp({
1833
1912
  getTestInfo({ fullPath: playgroundDir }),
1834
1913
  getDevInfo({ fullPath: playgroundDir, portNumber })
1835
1914
  ]);
1836
- const appModifiedTime = await getDirModifiedTime(
1915
+ const appModifiedTime = await queuedGetDirModifiedTime(
1837
1916
  await getFullPathFromAppName(baseAppName)
1838
1917
  );
1839
- const playgroundAppModifiedTime = await getDirModifiedTime(playgroundDir);
1918
+ const playgroundAppModifiedTime = await queuedGetDirModifiedTime(playgroundDir);
1840
1919
  const type = "playground";
1841
1920
  const title = (compiledReadme == null ? void 0 : compiledReadme.title) ?? name;
1842
1921
  return {
@@ -1897,7 +1976,9 @@ async function getExampleApps({
1897
1976
  request
1898
1977
  } = {}) {
1899
1978
  const examplesDir = path$1.join(workshopRoot, "examples");
1900
- const exampleDirs = (await glob("*", { cwd: examplesDir, ignore: "node_modules/**" })).map((p) => path$1.join(examplesDir, p));
1979
+ const exampleDirs = (await readDir(examplesDir)).map(
1980
+ (p) => path$1.join(examplesDir, p)
1981
+ );
1901
1982
  const exampleApps = [];
1902
1983
  for (const exampleDir of exampleDirs) {
1903
1984
  const index = exampleDirs.indexOf(exampleDir);
@@ -1910,10 +1991,14 @@ async function getExampleApps({
1910
1991
  timingKey: exampleDir.replace(`${examplesDir}${path$1.sep}`, ""),
1911
1992
  request,
1912
1993
  forceFresh: getForceFreshForDir(exampleAppCache.get(key), exampleDir),
1913
- getFreshValue: () => getExampleAppFromPath(exampleDir, index, request).catch((error) => {
1914
- console.error(error);
1915
- return null;
1916
- })
1994
+ getFreshValue: async () => {
1995
+ return getExampleAppFromPath(exampleDir, index, request).catch(
1996
+ (error) => {
1997
+ console.error(error);
1998
+ return null;
1999
+ }
2000
+ );
2001
+ }
1917
2002
  });
1918
2003
  if (exampleApp) exampleApps.push(exampleApp);
1919
2004
  }
@@ -1982,10 +2067,12 @@ async function getSolutionApps({
1982
2067
  solutionAppCache.get(solutionDir),
1983
2068
  solutionDir
1984
2069
  ),
1985
- getFreshValue: () => getSolutionAppFromPath(solutionDir, request).catch((error) => {
1986
- console.error(error);
1987
- return null;
1988
- })
2070
+ getFreshValue: async () => {
2071
+ return getSolutionAppFromPath(solutionDir, request).catch((error) => {
2072
+ console.error(error);
2073
+ return null;
2074
+ });
2075
+ }
1989
2076
  });
1990
2077
  if (solutionApp) solutionApps.push(solutionApp);
1991
2078
  }
@@ -2056,10 +2143,12 @@ async function getProblemApps({
2056
2143
  problemDir,
2057
2144
  solutionDir
2058
2145
  ),
2059
- getFreshValue: () => getProblemAppFromPath(problemDir).catch((error) => {
2060
- console.error(error);
2061
- return null;
2062
- })
2146
+ getFreshValue: async () => {
2147
+ return getProblemAppFromPath(problemDir).catch((error) => {
2148
+ console.error(error);
2149
+ return null;
2150
+ });
2151
+ }
2063
2152
  });
2064
2153
  if (problemApp) problemApps.push(problemApp);
2065
2154
  }
@@ -2149,11 +2238,8 @@ async function getAppFromFile(filePath) {
2149
2238
  return apps.find((app) => filePath.startsWith(app.fullPath));
2150
2239
  }
2151
2240
  async function setPlayground(srcDir, { reset } = {}) {
2152
- var _a2, _b;
2153
- const isIgnored = await isGitIgnored({ cwd: srcDir });
2154
2241
  const destDir = path$1.join(workshopRoot, "playground");
2155
- const playgroundFiles = path$1.join(destDir, "**");
2156
- (_a2 = getOptionalWatcher()) == null ? void 0 : _a2.unwatch(playgroundFiles);
2242
+ const isIgnored = await isGitIgnored({ cwd: srcDir });
2157
2243
  const playgroundApp = await getAppByName("playground");
2158
2244
  const playgroundWasRunning = playgroundApp ? isAppRunning(playgroundApp) : false;
2159
2245
  if (playgroundApp && reset) {
@@ -2241,12 +2327,11 @@ async function setPlayground(srcDir, { reset } = {}) {
2241
2327
  }
2242
2328
  });
2243
2329
  }
2330
+ modifiedTimes.set(destDir, Date.now());
2244
2331
  if (playgroundApp && restartPlayground) {
2245
2332
  await runAppDev(playgroundApp);
2246
2333
  await waitOnApp(playgroundApp);
2247
2334
  }
2248
- (_b = getOptionalWatcher()) == null ? void 0 : _b.add(playgroundFiles);
2249
- modifiedTimes.set(destDir, Date.now());
2250
2335
  }
2251
2336
  async function getPlaygroundAppName() {
2252
2337
  if (!await exists(playgroundAppNameInfoPath)) {
@@ -2264,27 +2349,6 @@ async function getPlaygroundAppName() {
2264
2349
  return null;
2265
2350
  }
2266
2351
  }
2267
- async function getDirModifiedTime(dir) {
2268
- const isIgnored = await isGitIgnored({ cwd: dir });
2269
- const files = await fs$1.promises.readdir(dir, { withFileTypes: true });
2270
- const modifiedTimes2 = await Promise.all(
2271
- files.map(async (file) => {
2272
- if (isIgnored(file.name)) return 0;
2273
- const filePath = path$1.join(dir, file.name);
2274
- if (file.isDirectory()) {
2275
- return getDirModifiedTime(filePath);
2276
- } else {
2277
- try {
2278
- const { mtimeMs } = await fs$1.promises.stat(filePath);
2279
- return mtimeMs;
2280
- } catch {
2281
- return 0;
2282
- }
2283
- }
2284
- })
2285
- );
2286
- return Math.max(0, ...modifiedTimes2);
2287
- }
2288
2352
  function getAppDisplayName(a, allApps) {
2289
2353
  let displayName = `${a.title} (${a.type})`;
2290
2354
  if (isExerciseStepApp(a)) {
@@ -2348,6 +2412,23 @@ const playgroundPath = path$1.join(workshopRoot, "playground/");
2348
2412
  function getRelativePath$1(filePath) {
2349
2413
  return path$1.normalize(filePath).replace(playgroundPath, `playground${path$1.sep}`).replace(exercisesPath, "");
2350
2414
  }
2415
+ function getAppPathFromFilePath(filePath) {
2416
+ const [, withinWorkshopRootHalf] = filePath.split(workshopRoot);
2417
+ if (!withinWorkshopRootHalf) {
2418
+ return null;
2419
+ }
2420
+ const [part1, part2, part3] = withinWorkshopRootHalf.split(path$1.sep).filter(Boolean);
2421
+ if (part1 === "playground") {
2422
+ return path$1.join(workshopRoot, "playground");
2423
+ }
2424
+ if (part1 === "examples" && part2) {
2425
+ return path$1.join(workshopRoot, "examples", part2);
2426
+ }
2427
+ if (part1 === "exercises" && part2 && part3) {
2428
+ return path$1.join(workshopRoot, "exercises", part2, part3);
2429
+ }
2430
+ return null;
2431
+ }
2351
2432
  function Confetti({ id }) {
2352
2433
  if (!id) return null;
2353
2434
  return /* @__PURE__ */ jsx(ClientOnly, { children: () => /* @__PURE__ */ jsx(
@@ -2583,7 +2664,7 @@ const extendedTheme = {
2583
2664
  }
2584
2665
  };
2585
2666
  const AnchorOrLink = React.forwardRef(function AnchorOrLink2(props, ref) {
2586
- var _a2;
2667
+ var _a;
2587
2668
  const {
2588
2669
  to,
2589
2670
  href,
@@ -2604,7 +2685,7 @@ const AnchorOrLink = React.forwardRef(function AnchorOrLink2(props, ref) {
2604
2685
  }
2605
2686
  if (!shouldUserRegularAnchor && typeof to === "object") {
2606
2687
  toUrl = `${to.pathname ?? ""}${to.hash ? `#${to.hash}` : ""}${to.search ? `?${to.search}` : ""}`;
2607
- shouldUserRegularAnchor = Boolean((_a2 = to.pathname) == null ? void 0 : _a2.includes(":"));
2688
+ shouldUserRegularAnchor = Boolean((_a = to.pathname) == null ? void 0 : _a.includes(":"));
2608
2689
  }
2609
2690
  if (shouldUserRegularAnchor) {
2610
2691
  return /* @__PURE__ */ jsx("a", { ...rest, download, href: href ?? toUrl, ref, children });
@@ -2768,8 +2849,8 @@ function EpicProgress() {
2768
2849
  const transition = useNavigation();
2769
2850
  const fetchers = useFetchers().filter(
2770
2851
  (fetcher) => {
2771
- var _a2;
2772
- return ((_a2 = fetcher.formData) == null ? void 0 : _a2.get("show-progress-bar")) === "true";
2852
+ var _a;
2853
+ return ((_a = fetcher.formData) == null ? void 0 : _a.get("show-progress-bar")) === "true";
2773
2854
  }
2774
2855
  );
2775
2856
  const states = [transition.state, ...fetchers.map((f) => f.state)];
@@ -3009,17 +3090,17 @@ function ThemeSwitch() {
3009
3090
  ] });
3010
3091
  }
3011
3092
  function useTheme() {
3012
- var _a2;
3093
+ var _a;
3013
3094
  const hints = useHints();
3014
3095
  const requestInfo = useRequestInfo();
3015
3096
  const fetchers = useFetchers();
3016
3097
  const fetcher = fetchers.find(
3017
3098
  (f) => {
3018
- var _a3;
3019
- return ((_a3 = f.formData) == null ? void 0 : _a3.get("intent")) === "update-theme";
3099
+ var _a2;
3100
+ return ((_a2 = f.formData) == null ? void 0 : _a2.get("intent")) === "update-theme";
3020
3101
  }
3021
3102
  );
3022
- const optimisticTheme = (_a2 = fetcher == null ? void 0 : fetcher.formData) == null ? void 0 : _a2.get("theme");
3103
+ const optimisticTheme = (_a = fetcher == null ? void 0 : fetcher.formData) == null ? void 0 : _a.get("theme");
3023
3104
  if (optimisticTheme === "system") return hints.theme;
3024
3105
  if (optimisticTheme === "light" || optimisticTheme === "dark") {
3025
3106
  return optimisticTheme;
@@ -3293,7 +3374,7 @@ function getProgressForLesson(epicLessonSlug, {
3293
3374
  workshopFinished,
3294
3375
  exercises
3295
3376
  }) {
3296
- var _a2;
3377
+ var _a;
3297
3378
  const hasEmbed = (embed) => embed == null ? void 0 : embed.some((e) => e.split("/").at(-1) === epicLessonSlug);
3298
3379
  if (workshopInstructions.compiled.status === "success" && hasEmbed(workshopInstructions.compiled.epicVideoEmbeds)) {
3299
3380
  return { type: "workshop-instructions" };
@@ -3315,7 +3396,7 @@ function getProgressForLesson(epicLessonSlug, {
3315
3396
  };
3316
3397
  }
3317
3398
  for (const step of exercise.steps.filter(Boolean)) {
3318
- if (hasEmbed((_a2 = step.problem) == null ? void 0 : _a2.epicVideoEmbeds)) {
3399
+ if (hasEmbed((_a = step.problem) == null ? void 0 : _a.epicVideoEmbeds)) {
3319
3400
  return {
3320
3401
  type: "step",
3321
3402
  exerciseNumber: exercise.exerciseNumber,
@@ -3413,7 +3494,7 @@ async function userHasAccessToWorkshop({
3413
3494
  request,
3414
3495
  forceFresh
3415
3496
  }) {
3416
- var _a2;
3497
+ var _a;
3417
3498
  const config = getWorkshopConfig();
3418
3499
  const {
3419
3500
  product: { host, slug }
@@ -3423,7 +3504,7 @@ async function userHasAccessToWorkshop({
3423
3504
  const cookieHeader = request.headers.get("Cookie");
3424
3505
  if (!cookieHeader) return false;
3425
3506
  const cookies = cookie__default.parse(cookieHeader);
3426
- return ((_a2 = cookies.skill) == null ? void 0 : _a2.split(",").includes(slug)) ?? false;
3507
+ return ((_a = cookies.skill) == null ? void 0 : _a.split(",").includes(slug)) ?? false;
3427
3508
  }
3428
3509
  const authInfo = await getAuthInfo();
3429
3510
  if (!authInfo) return false;
@@ -3455,9 +3536,9 @@ async function userHasAccessToWorkshop({
3455
3536
  }
3456
3537
  const PresenceContext = createContext(null);
3457
3538
  function usePresencePreferences() {
3458
- var _a2;
3539
+ var _a;
3459
3540
  const data = useRouteLoaderData("root");
3460
- return ((_a2 = data == null ? void 0 : data.preferences) == null ? void 0 : _a2.presence) ?? null;
3541
+ return ((_a = data == null ? void 0 : data.preferences) == null ? void 0 : _a.presence) ?? null;
3461
3542
  }
3462
3543
  function useOptionalWorkshopTitle() {
3463
3544
  const data = useRouteLoaderData("root");
@@ -3536,10 +3617,10 @@ function usePresenceSocket(user) {
3536
3617
  }
3537
3618
  function scoreUsers(location, users) {
3538
3619
  const scoredUsers = users.map((user) => {
3539
- var _a2, _b, _c, _d;
3620
+ var _a, _b, _c, _d;
3540
3621
  let score = 0;
3541
3622
  const available = 4;
3542
- if ((location == null ? void 0 : location.workshopTitle) === ((_a2 = user.location) == null ? void 0 : _a2.workshopTitle)) {
3623
+ if ((location == null ? void 0 : location.workshopTitle) === ((_a = user.location) == null ? void 0 : _a.workshopTitle)) {
3543
3624
  score += 1;
3544
3625
  if (((_b = location == null ? void 0 : location.exercise) == null ? void 0 : _b.exerciseNumber) && location.exercise.exerciseNumber === ((_d = (_c = user.location) == null ? void 0 : _c.exercise) == null ? void 0 : _d.exerciseNumber)) {
3545
3626
  score += 1;
@@ -3691,33 +3772,42 @@ async function loader$y({ request }) {
3691
3772
  throw redirect("/onboarding");
3692
3773
  }
3693
3774
  }
3694
- const preferences = await getPreferences();
3695
- const progress = await getProgress({ timings }).catch((e) => {
3696
- console.error("Failed to get progress", e);
3697
- const emptyProgress = [];
3698
- return emptyProgress;
3699
- });
3700
- const { toast: toast2, headers: toastHeaders } = await getToast(request);
3701
- const { confettiId, headers: confettiHeaders } = getConfetti(request);
3702
- const discordMember = await getDiscordMember();
3703
3775
  const theme = getTheme(request);
3704
- const user = await getUserInfo();
3705
- const userHasAccess = await userHasAccessToWorkshop({ request, timings });
3706
- const apps = await getApps({ request, timings });
3707
- const presentUsers = await getPresentUsers(user, { request, timings });
3776
+ const { confettiId, headers: confettiHeaders } = getConfetti(request);
3777
+ const { toast: toast2, headers: toastHeaders } = await getToast(request);
3778
+ const asyncStuff = await promiseHash({
3779
+ preferences: getPreferences(),
3780
+ progress: getProgress({ timings }).catch((e) => {
3781
+ console.error("Failed to get progress", e);
3782
+ const emptyProgress = [];
3783
+ return emptyProgress;
3784
+ }),
3785
+ discordMember: getDiscordMember(),
3786
+ user: getUserInfo(),
3787
+ userHasAccess: userHasAccessToWorkshop({ request, timings }),
3788
+ apps: getApps({ request, timings })
3789
+ });
3790
+ const presentUsers = await getPresentUsers(asyncStuff.user, {
3791
+ request,
3792
+ timings
3793
+ });
3708
3794
  return json$1(
3709
3795
  {
3796
+ ...asyncStuff,
3710
3797
  workshopConfig,
3711
3798
  workshopTitle,
3712
3799
  workshopSubtitle,
3713
3800
  instructor,
3714
- apps: apps.map(({ name, fullPath, relativePath }) => ({
3801
+ apps: asyncStuff.apps.map(({ name, fullPath, relativePath }) => ({
3715
3802
  name,
3716
3803
  fullPath,
3717
3804
  relativePath
3718
3805
  })),
3719
3806
  ENV: getEnv(),
3720
3807
  requestInfo: {
3808
+ protocol: new URL(request.url).protocol,
3809
+ hostname: new URL(request.url).hostname,
3810
+ port: new URL(request.url).port,
3721
3811
  origin: new URL(request.url).origin,
3722
3812
  domain: getDomainUrl(request),
3723
3813
  hints: getHints(request),
@@ -3725,11 +3815,6 @@ async function loader$y({ request }) {
3725
3815
  session: { theme },
3726
3816
  separator: path$1.sep
3727
3817
  },
3728
- progress,
3729
- preferences,
3730
- discordMember,
3731
- user,
3732
- userHasAccess,
3733
3818
  toast: toast2,
3734
3819
  confettiId,
3735
3820
  presence: {
@@ -3755,11 +3840,6 @@ function Document({
3755
3840
  env = {},
3756
3841
  className
3757
3842
  }) {
3758
- const revalidator = useRevalidator();
3759
- useEffect(() => {
3760
- window.__epicshop ?? (window.__epicshop = {});
3761
- window.__epicshop.handleFileChange = revalidator.revalidate;
3762
- }, [revalidator]);
3763
3843
  return /* @__PURE__ */ jsxs("html", { lang: "en", className, children: [
3764
3844
  /* @__PURE__ */ jsxs("head", { children: [
3765
3845
  /* @__PURE__ */ jsx(ClientHintCheck, {}),
@@ -3779,8 +3859,7 @@ function Document({
3779
3859
  /* @__PURE__ */ jsxs("body", { className: "bg-background text-foreground scrollbar-thin scrollbar-thumb-scrollbar h-screen-safe", children: [
3780
3860
  children,
3781
3861
  /* @__PURE__ */ jsx(ScrollRestoration, {}),
3782
- /* @__PURE__ */ jsx(Scripts, {}),
3783
- ENV.EPICSHOP_DEPLOYED ? null : /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: getWebsocketJS() } })
3862
+ /* @__PURE__ */ jsx(Scripts, {})
3784
3863
  ] })
3785
3864
  ] });
3786
3865
  }
@@ -3819,70 +3898,6 @@ function AppWithProviders() {
3819
3898
  function ErrorBoundary$6() {
3820
3899
  return /* @__PURE__ */ jsx(Document, { className: "h-screen-safe", children: /* @__PURE__ */ jsx(GeneralErrorBoundary, {}) });
3821
3900
  }
3822
- function getWebsocketJS() {
3823
- const js = (
3824
- /* javascript */
3825
- `
3826
- function epicLiveReloadConnect(config) {
3827
- const protocol = location.protocol === "https:" ? "wss:" : "ws:";
3828
- const host = location.hostname;
3829
- const port = location.port;
3830
- const socketPath = protocol + "//" + host + ":" + port + "/__ws";
3831
- const ws = new WebSocket(socketPath);
3832
- function handleFileChange(changedFiles) {
3833
- console.log(
3834
- ['🐨 Reloading', window.frameElement?.getAttribute('title')]
3835
- .filter(Boolean)
3836
- .join(' '),
3837
- changedFiles
3838
- );
3839
- if (typeof window.__epicshop?.handleFileChange === "function") {
3840
- window.__epicshop?.handleFileChange();
3841
- } else {
3842
- setTimeout(() => window.location.reload(), 200);
3843
- }
3844
- }
3845
- function debounce(fn, ms) {
3846
- let timeout;
3847
- return function debouncedFn(...args) {
3848
- clearTimeout(timeout);
3849
- timeout = setTimeout(() => fn(...args), ms);
3850
- };
3851
- }
3852
- const debouncedHandleFileChange = debounce(handleFileChange, 50);
3853
- ws.onmessage = (message) => {
3854
- const event = JSON.parse(message.data);
3855
- if (event.type !== 'epicshop:file-change') return;
3856
- const { filePaths } = event.data;
3857
- debouncedHandleFileChange(filePaths);
3858
- };
3859
- ws.onopen = () => {
3860
- if (config && typeof config.onOpen === "function") {
3861
- config.onOpen();
3862
- }
3863
- };
3864
- ws.onclose = (event) => {
3865
- if (event.code === 1006) {
3866
- console.log("EpicShop dev server web socket closed. Reconnecting...");
3867
- setTimeout(
3868
- () =>
3869
- epicLiveReloadConnect({
3870
- onOpen: () => window.location.reload(),
3871
- }),
3872
- 1000
3873
- );
3874
- }
3875
- };
3876
- ws.onerror = (error) => {
3877
- console.log("EpicShop dev server web socket error:");
3878
- console.error(error);
3879
- };
3880
- }
3881
- epicLiveReloadConnect();
3882
- `
3883
- );
3884
- return js;
3885
- }
3886
3901
  const route0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
3887
3902
  __proto__: null,
3888
3903
  ErrorBoundary: ErrorBoundary$6,
@@ -4019,6 +4034,63 @@ function Logo({
4019
4034
  }
4020
4035
  return logoElement;
4021
4036
  }
4037
+ const eventSchema = z.object({
4038
+ type: z.literal("epicshop:file-change"),
4039
+ data: z.object({
4040
+ filePaths: z.array(z.string())
4041
+ })
4042
+ });
4043
+ function useRevalidationWSImpl({ watchPaths }) {
4044
+ const requestInfo = useRequestInfo();
4045
+ const revalidator = useRevalidator();
4046
+ const latestRevalidatorRef = useRef(revalidator);
4047
+ useEffect(() => {
4048
+ latestRevalidatorRef.current = revalidator;
4049
+ }, [revalidator]);
4050
+ const socketParams = new URLSearchParams();
4051
+ for (const path2 of watchPaths) {
4052
+ socketParams.append("watch", path2);
4053
+ }
4054
+ const socketQuery = socketParams.toString();
4055
+ const protocol = requestInfo.protocol === "https:" ? "wss:" : "ws:";
4056
+ const host = requestInfo.hostname;
4057
+ const port2 = requestInfo.port;
4058
+ const socketPath = `${protocol}//${host}:${port2}/__ws?${socketQuery}`;
4059
+ useEffect(() => {
4060
+ if (!socketQuery) return;
4061
+ let ws = null;
4062
+ function createWebSocket() {
4063
+ if (ws) ws.close();
4064
+ ws = new WebSocket(socketPath);
4065
+ ws.onmessage = (message) => {
4066
+ const eventParsed = eventSchema.safeParse(JSON.parse(message.data));
4067
+ if (!eventParsed.success) return;
4068
+ const { data: event } = eventParsed;
4069
+ if (event.type !== "epicshop:file-change") return;
4070
+ console.log(
4071
+ "🐨 Revalidating due to file changes:",
4072
+ event.data.filePaths
4073
+ );
4074
+ latestRevalidatorRef.current.revalidate();
4075
+ };
4076
+ ws.onclose = (event) => {
4077
+ if (event.code === 1006) {
4078
+ setTimeout(() => {
4079
+ createWebSocket();
4080
+ }, 1e3);
4081
+ }
4082
+ };
4083
+ ws.onerror = (error) => {
4084
+ console.error("🐨 EpicShop WebSocket error:", error);
4085
+ };
4086
+ }
4087
+ createWebSocket();
4088
+ return () => {
4089
+ ws == null ? void 0 : ws.close();
4090
+ };
4091
+ }, [socketQuery, socketPath]);
4092
+ }
4093
+ const useRevalidationWS = ENV.EPICSHOP_DEPLOYED ? () => null : useRevalidationWSImpl;
4022
4094
  const Dialog = DialogPrimitive.Root;
4023
4095
  const DialogTrigger = DialogPrimitive.Trigger;
4024
4096
  const DialogPortal = DialogPrimitive.Portal;
@@ -4126,14 +4198,14 @@ function useEpicProgress() {
4126
4198
  const data = useRouteLoaderData("root");
4127
4199
  const progressFetcher = useFetchers().find(
4128
4200
  (f) => {
4129
- var _a2;
4130
- return f.formAction === "/progress" && ((_a2 = f.formData) == null ? void 0 : _a2.has("complete"));
4201
+ var _a;
4202
+ return f.formAction === "/progress" && ((_a = f.formData) == null ? void 0 : _a.has("complete"));
4131
4203
  }
4132
4204
  );
4133
4205
  if (!progressFetcher || !(data == null ? void 0 : data.progress)) return (data == null ? void 0 : data.progress) ?? null;
4134
4206
  return data.progress.map((p) => {
4135
- var _a2, _b;
4136
- const optimisticCompleted = ((_a2 = progressFetcher.formData) == null ? void 0 : _a2.get("complete")) === "true";
4207
+ var _a, _b;
4208
+ const optimisticCompleted = ((_a = progressFetcher.formData) == null ? void 0 : _a.get("complete")) === "true";
4137
4209
  const optimisticLessonSlug = (_b = progressFetcher.formData) == null ? void 0 : _b.get("lessonSlug");
4138
4210
  if (optimisticLessonSlug === p.epicLessonSlug) {
4139
4211
  return {
@@ -4293,13 +4365,13 @@ function ProgressToggle({
4293
4365
  className,
4294
4366
  ...progressItemSearch
4295
4367
  }) {
4296
- var _a2, _b, _c, _d;
4368
+ var _a, _b, _c, _d;
4297
4369
  const progressFetcher = useFetcher();
4298
4370
  const peRedirectInput = usePERedirectInput();
4299
4371
  const progressItem = useProgressItem(progressItemSearch);
4300
4372
  const animationRef = React.useRef(null);
4301
4373
  const buttonRef = React.useRef(null);
4302
- const optimisticCompleted = ((_a2 = progressFetcher.formData) == null ? void 0 : _a2.has("complete")) ? progressFetcher.formData.get("complete") === "true" : Boolean(progressItem == null ? void 0 : progressItem.epicCompletedAt);
4374
+ const optimisticCompleted = ((_a = progressFetcher.formData) == null ? void 0 : _a.has("complete")) ? progressFetcher.formData.get("complete") === "true" : Boolean(progressItem == null ? void 0 : progressItem.epicCompletedAt);
4303
4375
  const [startAnimation, setStartAnimation] = React.useState(false);
4304
4376
  const location = useLocation();
4305
4377
  const navigation = useNavigation();
@@ -4307,11 +4379,11 @@ function ProgressToggle({
4307
4379
  const navigationLocationPathname = (_d = navigation.location) == null ? void 0 : _d.pathname;
4308
4380
  const locationPathname = location.pathname;
4309
4381
  React.useEffect(() => {
4310
- var _a3;
4382
+ var _a2;
4311
4383
  if (navigationLocationStateFrom === "continue next lesson button") {
4312
4384
  if (locationPathname === navigationLocationPathname) {
4313
4385
  setStartAnimation(true);
4314
- (_a3 = buttonRef.current) == null ? void 0 : _a3.focus();
4386
+ (_a2 = buttonRef.current) == null ? void 0 : _a2.focus();
4315
4387
  }
4316
4388
  }
4317
4389
  }, [
@@ -4419,7 +4491,7 @@ const route36 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
4419
4491
  useRequireEpicProgress
4420
4492
  }, Symbol.toStringTag, { value: "Module" }));
4421
4493
  async function loader$w({ request }) {
4422
- var _a2;
4494
+ var _a;
4423
4495
  const timings = makeTimings("appLayoutLoader");
4424
4496
  const { title: workshopTitle } = getWorkshopConfig();
4425
4497
  const [exercises, playgroundAppName] = await Promise.all([
@@ -4429,7 +4501,7 @@ async function loader$w({ request }) {
4429
4501
  const playground = {
4430
4502
  appName: playgroundAppName,
4431
4503
  exerciseNumber: Number(
4432
- (_a2 = extractNumbersAndTypeFromAppNameOrPath(playgroundAppName ?? "")) == null ? void 0 : _a2.exerciseNumber
4504
+ (_a = extractNumbersAndTypeFromAppNameOrPath(playgroundAppName ?? "")) == null ? void 0 : _a.exerciseNumber
4433
4505
  )
4434
4506
  };
4435
4507
  const result = json$1(
@@ -4523,7 +4595,7 @@ function FacePile({ isMenuOpened }) {
4523
4595
  return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [
4524
4596
  (shouldShowNumberOverLimit ? users.slice(0, limit) : users).map(
4525
4597
  ({ user, score }) => {
4526
- var _a2, _b;
4598
+ var _a, _b;
4527
4599
  const scoreClassNames = getScoreClassNames(score);
4528
4600
  const locationLabel = getLocationLabel(user.location);
4529
4601
  return /* @__PURE__ */ jsxs(Tooltip, { children: [
@@ -4554,7 +4626,7 @@ function FacePile({ isMenuOpened }) {
4554
4626
  /* @__PURE__ */ jsxs("span", { children: [
4555
4627
  user.name || `${displayNameShort} Dev`,
4556
4628
  " ",
4557
- locationLabel ? ` is ${((_b = (_a2 = user.location) == null ? void 0 : _a2.origin) == null ? void 0 : _b.includes("localhost")) ? "working" : "learning"} ${score === 1 && (loggedInUser == null ? void 0 : loggedInUser.id) !== user.id ? "with you" : ""} on` : null
4629
+ locationLabel ? ` is ${((_b = (_a = user.location) == null ? void 0 : _a.origin) == null ? void 0 : _b.includes("localhost")) ? "working" : "learning"} ${score === 1 && (loggedInUser == null ? void 0 : loggedInUser.id) !== user.id ? "with you" : ""} on` : null
4558
4630
  ] }),
4559
4631
  (locationLabel == null ? void 0 : locationLabel.line1) ? /* @__PURE__ */ jsx("span", { children: locationLabel.line1 }) : null,
4560
4632
  (locationLabel == null ? void 0 : locationLabel.line2) ? /* @__PURE__ */ jsx("span", { children: locationLabel.line2 }) : null
@@ -4595,6 +4667,7 @@ function App() {
4595
4667
  const isWide = useIsWide();
4596
4668
  const isHydrated = useHydrated();
4597
4669
  const [isMenuOpened, setMenuOpened] = React.useState(false);
4670
+ useRevalidationWS({ watchPaths: ["./exercises/README.mdx"] });
4598
4671
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
4599
4672
  user ? null : /* @__PURE__ */ jsx(NoUserBanner, {}),
4600
4673
  isHydrated && isWide ? null : /* @__PURE__ */ jsx(
@@ -5431,9 +5504,9 @@ function NavToggle({
5431
5504
  React.useEffect(() => {
5432
5505
  if (!isMenuOpened) return;
5433
5506
  function handleKeyUp(event) {
5434
- var _a2;
5507
+ var _a;
5435
5508
  if (event.key === "Escape") {
5436
- (_a2 = menuButtonRef.current) == null ? void 0 : _a2.click();
5509
+ (_a = menuButtonRef.current) == null ? void 0 : _a.click();
5437
5510
  }
5438
5511
  }
5439
5512
  document.addEventListener("keyup", handleKeyUp);
@@ -5785,7 +5858,7 @@ async function getForceFresh(filePath, cacheEntry) {
5785
5858
  if (!cacheEntry) return true;
5786
5859
  const app = await getAppFromFile(filePath);
5787
5860
  if (!app) return true;
5788
- const appModified = modifiedTimes.get(app.fullPath) ?? 0;
5861
+ const appModified = await queuedGetDirModifiedTime(app.fullPath);
5789
5862
  const cacheModified = cacheEntry.metadata.createdTime;
5790
5863
  return !cacheModified || appModified > cacheModified || void 0;
5791
5864
  }
@@ -6039,6 +6112,10 @@ async function loader$r({ request, params }) {
6039
6112
  return redirect(getBaseUrl({ request, port: app.dev.portNumber }));
6040
6113
  }
6041
6114
  const relevantPaths = Array.from(/* @__PURE__ */ new Set([app.fullPath, fileApp.fullPath]));
6115
+ const watchParams = new URLSearchParams();
6116
+ for (const path2 of relevantPaths) {
6117
+ watchParams.append("watch", path2);
6118
+ }
6042
6119
  const js = (
6043
6120
  /* javascript */
6044
6121
  `
@@ -6046,20 +6123,19 @@ async function loader$r({ request, params }) {
6046
6123
  const protocol = location.protocol === "https:" ? "wss:" : "ws:";
6047
6124
  const host = location.hostname;
6048
6125
  const port = location.port;
6049
- const socketPath = protocol + "//" + host + ":" + port + "/__ws";
6126
+ const socketPath = protocol + "//" + host + ":" + port + "/__ws?" + ${JSON.stringify(watchParams.toString())};
6050
6127
  const ws = new WebSocket(socketPath);
6051
6128
  ws.onmessage = (message) => {
6052
6129
  const event = JSON.parse(message.data);
6053
6130
  if (event.type !== 'epicshop:file-change') return;
6054
6131
  const { filePaths } = event.data;
6055
- if (${JSON.stringify(relevantPaths)}.some(p => filePaths.some(filePath => filePath.startsWith(p)))) {
6056
- console.log(
6057
- ['🐨 Reloading', window.frameElement?.getAttribute('title')]
6058
- .filter(Boolean)
6059
- .join(' '),
6060
- );
6061
- window.location.reload();
6062
- }
6132
+ console.log(
6133
+ ['🐨 Reloading', window.frameElement?.getAttribute('title'), 'due to file changes:']
6134
+ .filter(Boolean)
6135
+ .join(' '),
6136
+ filePaths
6137
+ );
6138
+ window.location.reload();
6063
6139
  };
6064
6140
  ws.onopen = () => {
6065
6141
  if (config && typeof config.onOpen === "function") {
@@ -6098,7 +6174,7 @@ const route6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
6098
6174
  loader: loader$r
6099
6175
  }, Symbol.toStringTag, { value: "Module" }));
6100
6176
  async function loader$q({ request, params }) {
6101
- var _a2;
6177
+ var _a;
6102
6178
  const timings = makeTimings("app");
6103
6179
  const { fileApp, app } = await resolveApps({ request, params, timings });
6104
6180
  const baseApp = isPlaygroundApp(app) ? await getAppByName(app.appName) : app;
@@ -6146,7 +6222,7 @@ async function loader$q({ request, params }) {
6146
6222
  const { title: workshopTitle } = getWorkshopConfig();
6147
6223
  const baseAppTitle = isExerciseStepApp(baseApp) ? [
6148
6224
  `${baseApp.stepNumber.toString().padStart(2, "0")}. ${baseApp.title}`,
6149
- `${baseApp.exerciseNumber.toString().padStart(2, "0")}. ${((_a2 = await getExercise(baseApp.exerciseNumber, { request, timings })) == null ? void 0 : _a2.title) ?? "Unknown"}`,
6225
+ `${baseApp.exerciseNumber.toString().padStart(2, "0")}. ${((_a = await getExercise(baseApp.exerciseNumber, { request, timings })) == null ? void 0 : _a.title) ?? "Unknown"}`,
6150
6226
  workshopTitle
6151
6227
  ] : [(baseApp == null ? void 0 : baseApp.title) ?? "N/A"];
6152
6228
  const title = (isExerciseStepApp(app) ? [
@@ -6186,7 +6262,7 @@ const route7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
6186
6262
  loader: loader$q
6187
6263
  }, Symbol.toStringTag, { value: "Module" }));
6188
6264
  async function loader$p({ request, params }) {
6189
- var _a2;
6265
+ var _a;
6190
6266
  const timings = makeTimings("app_test_loader");
6191
6267
  const userHasAccess = await userHasAccessToWorkshop({
6192
6268
  request
@@ -6289,7 +6365,7 @@ ${testScriptTag}`;
6289
6365
  const title = (isExerciseStepApp(app) ? [
6290
6366
  isProblemApp(app) ? "🧪💪" : isSolutionApp(app) ? "🧪🏁" : null,
6291
6367
  `${app.stepNumber.toString().padStart(2, "0")}. ${app.title}`,
6292
- `${app.exerciseNumber.toString().padStart(2, "0")}. ${((_a2 = await getExercise(app.exerciseNumber, { request, timings })) == null ? void 0 : _a2.title) ?? "Unknown"}`,
6368
+ `${app.exerciseNumber.toString().padStart(2, "0")}. ${((_a = await getExercise(app.exerciseNumber, { request, timings })) == null ? void 0 : _a.title) ?? "Unknown"}`,
6293
6369
  workshopTitle
6294
6370
  ] : ["🧪", appTitle]).filter(Boolean).join(" | ");
6295
6371
  const html = (
@@ -6470,18 +6546,29 @@ const PlaybackTimeSchema = z.object({
6470
6546
  return { time: Number(data.time), expiresAt: new Date(data.expiresAt) };
6471
6547
  });
6472
6548
  function usePlayerPreferences() {
6473
- var _a2;
6549
+ var _a;
6474
6550
  const data = useRouteLoaderData("root");
6475
- return ((_a2 = data == null ? void 0 : data.preferences) == null ? void 0 : _a2.player) ?? null;
6551
+ return ((_a = data == null ? void 0 : data.preferences) == null ? void 0 : _a.player) ?? null;
6476
6552
  }
6477
6553
  const ignoredInputs = [
6478
- "input",
6479
- "select",
6480
- "button",
6481
- "textarea",
6482
- "mux-player",
6483
- "summary"
6554
+ "INPUT",
6555
+ "SELECT",
6556
+ "BUTTON",
6557
+ "TEXTAREA",
6558
+ "MUX-PLAYER",
6559
+ "SUMMARY"
6484
6560
  ];
6561
+ const ignoredRoles = ["button", "option", "combobox", "tab", "tablist"];
6562
+ function shouldIgnoreHotkey(el) {
6563
+ let current = el;
6564
+ while (current) {
6565
+ if (!(current instanceof HTMLElement)) return false;
6566
+ const isIgnored = ignoredInputs.includes(current.tagName) || ignoredRoles.includes(current.getAttribute("role") || "") || current.isContentEditable;
6567
+ if (isIgnored) return true;
6568
+ current = current.parentElement;
6569
+ }
6570
+ return false;
6571
+ }
6485
6572
  async function action$7({ request }) {
6486
6573
  const result = PlayerPreferencesSchema.safeParse(await request.json());
6487
6574
  if (!result.success) {
@@ -6526,24 +6613,23 @@ function MuxPlayer({
6526
6613
  function handleUserKeyPress(e) {
6527
6614
  if (!muxPlayerRef.current) return;
6528
6615
  const activeElement = document.activeElement;
6529
- const isContentEditable = activeElement instanceof HTMLElement ? activeElement.contentEditable === "true" : false;
6530
- if (activeElement && !ignoredInputs.includes(activeElement.tagName.toLowerCase()) && !isContentEditable) {
6531
- if (e.key === " ") {
6532
- e.preventDefault();
6533
- void (muxPlayerRef.current.paused ? muxPlayerRef.current.play() : muxPlayerRef.current.pause());
6534
- }
6535
- if (e.key === "ArrowRight") {
6536
- e.preventDefault();
6537
- muxPlayerRef.current.currentTime = muxPlayerRef.current.currentTime + (muxPlayerRef.current.forwardSeekOffset || 10);
6538
- }
6539
- if (e.key === "ArrowLeft") {
6540
- e.preventDefault();
6541
- muxPlayerRef.current.currentTime = muxPlayerRef.current.currentTime - (muxPlayerRef.current.forwardSeekOffset || 10);
6542
- }
6543
- if (e.key === "f" && !e.metaKey && !e.ctrlKey) {
6544
- e.preventDefault();
6545
- void (document.fullscreenElement ? document.exitFullscreen() : muxPlayerRef.current.requestFullscreen());
6546
- }
6616
+ if (shouldIgnoreHotkey(activeElement)) return;
6617
+ if (shouldIgnoreHotkey(e.target)) return;
6618
+ if (e.key === " ") {
6619
+ e.preventDefault();
6620
+ void (muxPlayerRef.current.paused ? muxPlayerRef.current.play() : muxPlayerRef.current.pause());
6621
+ }
6622
+ if (e.key === "ArrowRight") {
6623
+ e.preventDefault();
6624
+ muxPlayerRef.current.currentTime = muxPlayerRef.current.currentTime + (muxPlayerRef.current.forwardSeekOffset || 10);
6625
+ }
6626
+ if (e.key === "ArrowLeft") {
6627
+ e.preventDefault();
6628
+ muxPlayerRef.current.currentTime = muxPlayerRef.current.currentTime - (muxPlayerRef.current.forwardSeekOffset || 10);
6629
+ }
6630
+ if (e.key === "f" && !e.metaKey && !e.ctrlKey) {
6631
+ e.preventDefault();
6632
+ void (document.fullscreenElement ? document.exitFullscreen() : muxPlayerRef.current.requestFullscreen());
6547
6633
  }
6548
6634
  }
6549
6635
  window.document.addEventListener("keydown", handleUserKeyPress);
@@ -6570,9 +6656,9 @@ function MuxPlayer({
6570
6656
  });
6571
6657
  }, 300);
6572
6658
  React.useEffect(() => {
6573
- var _a2, _b;
6659
+ var _a, _b;
6574
6660
  if (!metadataLoaded) return;
6575
- const textTracks = (_a2 = muxPlayerRef.current) == null ? void 0 : _a2.textTracks;
6661
+ const textTracks = (_a = muxPlayerRef.current) == null ? void 0 : _a.textTracks;
6576
6662
  if (!textTracks) return;
6577
6663
  const subtitlePref = (_b = playerPreferencesRef.current) == null ? void 0 : _b.subtitle;
6578
6664
  if (subtitlePref == null ? void 0 : subtitlePref.id) {
@@ -6614,11 +6700,11 @@ function MuxPlayer({
6614
6700
  defaultHiddenCaptions: true,
6615
6701
  currentTime,
6616
6702
  onTimeUpdate: () => {
6617
- var _a2;
6703
+ var _a;
6618
6704
  return sessionStorage.setItem(
6619
6705
  currentTimeSessionKey,
6620
6706
  JSON.stringify({
6621
- time: (_a2 = muxPlayerRef.current) == null ? void 0 : _a2.currentTime,
6707
+ time: (_a = muxPlayerRef.current) == null ? void 0 : _a.currentTime,
6622
6708
  expiresAt: new Date(Date.now() + 1e3 * 60 * 30).toISOString()
6623
6709
  })
6624
6710
  );
@@ -6702,8 +6788,8 @@ function useInterval(callback, delay = 1e3) {
6702
6788
  }, [callback]);
6703
6789
  useEffect(() => {
6704
6790
  function tick() {
6705
- var _a2;
6706
- (_a2 = savedCallback.current) == null ? void 0 : _a2.call(savedCallback);
6791
+ var _a;
6792
+ (_a = savedCallback.current) == null ? void 0 : _a.call(savedCallback);
6707
6793
  }
6708
6794
  if (delay !== null) {
6709
6795
  const id = setInterval(tick, delay);
@@ -6831,7 +6917,7 @@ function extractEpicTitle(urlString) {
6831
6917
  "useFetcherType"
6832
6918
  ];
6833
6919
  const title = titleWords.filter(Boolean).map((word, index) => {
6834
- var _a2;
6920
+ var _a;
6835
6921
  const lowerWord = word.toLowerCase();
6836
6922
  const literalWord = literalWords.find(
6837
6923
  (w) => w.toLowerCase() === lowerWord
@@ -6840,7 +6926,7 @@ function extractEpicTitle(urlString) {
6840
6926
  if (lowerCaseWords.includes(lowerWord) && index > 0) {
6841
6927
  return lowerWord;
6842
6928
  }
6843
- return ((_a2 = lowerWord[0]) == null ? void 0 : _a2.toUpperCase()) + lowerWord.slice(1);
6929
+ return ((_a = lowerWord[0]) == null ? void 0 : _a.toUpperCase()) + lowerWord.slice(1);
6844
6930
  }).join(" ");
6845
6931
  if (isSolution) {
6846
6932
  return `${title} (🏁 solution)`;
@@ -7310,7 +7396,7 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber, colNumber, work
7310
7396
  return [fileName];
7311
7397
  }
7312
7398
  function guessEditor() {
7313
- var _a2;
7399
+ var _a;
7314
7400
  if (process.env.EPICSHOP_EDITOR) {
7315
7401
  return shellQuote.parse(process.env.EPICSHOP_EDITOR).map((a) => String(a));
7316
7402
  }
@@ -7331,7 +7417,7 @@ function guessEditor() {
7331
7417
  ).toString();
7332
7418
  const runningProcesses = output.split("\r\n");
7333
7419
  for (let i = 0; i < runningProcesses.length; i++) {
7334
- const processPath = (_a2 = runningProcesses[i]) == null ? void 0 : _a2.trim();
7420
+ const processPath = (_a = runningProcesses[i]) == null ? void 0 : _a.trim();
7335
7421
  if (!processPath) continue;
7336
7422
  const processName = path.basename(processPath);
7337
7423
  if (COMMON_EDITORS_WIN.includes(processName)) {
@@ -7440,7 +7526,7 @@ File names may consist only of alphanumeric characters (all languages), periods,
7440
7526
  _childProcess.kill("SIGKILL");
7441
7527
  }
7442
7528
  return new Promise((res) => {
7443
- var _a2;
7529
+ var _a;
7444
7530
  if (process.platform === "win32") {
7445
7531
  _childProcess = child_process.spawn(
7446
7532
  "cmd.exe",
@@ -7452,7 +7538,7 @@ File names may consist only of alphanumeric characters (all languages), periods,
7452
7538
  stdio: ["inherit", "inherit", "pipe"]
7453
7539
  });
7454
7540
  }
7455
- (_a2 = _childProcess.stderr) == null ? void 0 : _a2.on("data", (data) => {
7541
+ (_a = _childProcess.stderr) == null ? void 0 : _a.on("data", (data) => {
7456
7542
  const message = String(data);
7457
7543
  if (!message.includes("Node.js environment variables are disabled")) {
7458
7544
  process.stderr.write(data);
@@ -7622,7 +7708,7 @@ function LaunchEditorImpl({
7622
7708
  children,
7623
7709
  onUpdate
7624
7710
  }) {
7625
- var _a2;
7711
+ var _a;
7626
7712
  const fetcher = useLaunchFetcher(onUpdate);
7627
7713
  const peRedirectInput = usePERedirectInput();
7628
7714
  const fileList = typeof appFile === "string" ? [appFile] : appFile;
@@ -7656,7 +7742,7 @@ function LaunchEditorImpl({
7656
7742
  className: clsx(
7657
7743
  "launch_button",
7658
7744
  fetcher.state === "idle" ? null : "cursor-progress",
7659
- ((_a2 = fetcher.data) == null ? void 0 : _a2.status) === "error" ? "cursor-not-allowed" : null
7745
+ ((_a = fetcher.data) == null ? void 0 : _a.status) === "error" ? "cursor-not-allowed" : null
7660
7746
  ),
7661
7747
  children
7662
7748
  }
@@ -7767,19 +7853,19 @@ function OpenInEditor({
7767
7853
  "data-start": start,
7768
7854
  "data-type": type
7769
7855
  }) {
7770
- var _a2;
7856
+ var _a;
7771
7857
  const data = useLoaderData();
7772
7858
  if (type === "other" || !buttons || !filename || !fullPath) return null;
7773
- const currentAppFullPath = safePath(((_a2 = data[data.type]) == null ? void 0 : _a2.fullPath) ?? "");
7859
+ const currentAppFullPath = safePath(((_a = data[data.type]) == null ? void 0 : _a.fullPath) ?? "");
7774
7860
  const isFileFromDifferentApp = !fullPath.startsWith(currentAppFullPath);
7775
7861
  const validButtons = ENV.EPICSHOP_DEPLOYED ? ["problem", "solution"] : ["problem", "solution", "playground"];
7776
7862
  const buttonList = buttons.split(",");
7777
7863
  const apps = validButtons.filter((button) => buttonList.includes(button));
7778
7864
  return /* @__PURE__ */ jsx(Fragment, { children: apps.map((type2) => {
7779
- var _a3;
7865
+ var _a2;
7780
7866
  const app = data[type2];
7781
7867
  if (type2 === "playground") {
7782
- const isDifferentApp = data.playground && data.playground.appName !== ((_a3 = data.problem) == null ? void 0 : _a3.name);
7868
+ const isDifferentApp = data.playground && data.playground.appName !== ((_a2 = data.problem) == null ? void 0 : _a2.name);
7783
7869
  if (!app || isDifferentApp || isFileFromDifferentApp) {
7784
7870
  return /* @__PURE__ */ jsxs(
7785
7871
  "button",
@@ -7823,10 +7909,10 @@ function CopyButton() {
7823
7909
  {
7824
7910
  className: cn(buttonClassName, "w-12 uppercase"),
7825
7911
  onClick: (event) => {
7826
- var _a2, _b, _c;
7912
+ var _a, _b, _c;
7827
7913
  setCopied(true);
7828
7914
  const button = event.currentTarget;
7829
- const code = ((_c = (_b = (_a2 = button.parentElement) == null ? void 0 : _a2.parentElement) == null ? void 0 : _b.querySelector("pre")) == null ? void 0 : _c.textContent) || "";
7915
+ const code = ((_c = (_b = (_a = button.parentElement) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector("pre")) == null ? void 0 : _c.textContent) || "";
7830
7916
  void navigator.clipboard.writeText(code);
7831
7917
  },
7832
7918
  children: copied ? "copied" : "copy"
@@ -7935,9 +8021,9 @@ const meta$4 = ({
7935
8021
  data,
7936
8022
  matches
7937
8023
  }) => {
7938
- var _a2;
8024
+ var _a;
7939
8025
  const number = data == null ? void 0 : data.exercise.exerciseNumber.toString().padStart(2, "0");
7940
- const rootData = (_a2 = matches.find((m) => m.id === "root")) == null ? void 0 : _a2.data;
8026
+ const rootData = (_a = matches.find((m) => m.id === "root")) == null ? void 0 : _a.data;
7941
8027
  if (!data || !rootData) return [{ title: "🦉 | Error" }];
7942
8028
  return getSeoMetaTags({
7943
8029
  title: `📝 | ${number}. ${data.exercise.title} | ${rootData == null ? void 0 : rootData.workshopTitle}`,
@@ -8005,9 +8091,12 @@ const headers$9 = ({ loaderHeaders, parentHeaders }) => {
8005
8091
  };
8006
8092
  const mdxComponents$4 = { h1: () => null };
8007
8093
  function ExerciseNumberRoute() {
8008
- var _a2;
8094
+ var _a;
8009
8095
  const data = useLoaderData();
8010
- const firstStepNumber = String(((_a2 = data.firstStep) == null ? void 0 : _a2.stepNumber) ?? "01").padStart(
8096
+ useRevalidationWS({
8097
+ watchPaths: [data.exerciseReadme.file]
8098
+ });
8099
+ const firstStepNumber = String(((_a = data.firstStep) == null ? void 0 : _a.stepNumber) ?? "01").padStart(
8011
8100
  2,
8012
8101
  "0"
8013
8102
  );
@@ -8218,11 +8307,11 @@ function diffPathToRelative(filePath) {
8218
8307
  return relativePath.join(path.sep);
8219
8308
  }
8220
8309
  function getLanguage(ext) {
8221
- var _a2;
8222
- return ((_a2 = bundledLanguagesInfo.find((l) => {
8223
- var _a3;
8224
- return l.id === ext || ((_a3 = l.aliases) == null ? void 0 : _a3.includes(ext));
8225
- })) == null ? void 0 : _a2.id) ?? "text";
8310
+ var _a;
8311
+ return ((_a = bundledLanguagesInfo.find((l) => {
8312
+ var _a2;
8313
+ return l.id === ext || ((_a2 = l.aliases) == null ? void 0 : _a2.includes(ext));
8314
+ })) == null ? void 0 : _a.id) ?? "text";
8226
8315
  }
8227
8316
  function getFileCodeblocks(file, filePathApp1, filePathApp2, type) {
8228
8317
  if (!file.chunks.length) {
@@ -8403,15 +8492,23 @@ async function getDiffIgnore(filePath) {
8403
8492
  (content) => content.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("#")).filter(Boolean)
8404
8493
  ) : [];
8405
8494
  }
8406
- function getForceFreshForDiff(app1, app2, cacheEntry) {
8407
- if (!cacheEntry) return true;
8495
+ async function getForceFreshForDiff(app1, app2, cacheEntry) {
8496
+ const cacheModified = cacheEntry == null ? void 0 : cacheEntry.metadata.createdTime;
8497
+ if (!cacheModified) return true;
8408
8498
  const app1Modified = modifiedTimes.get(app1.fullPath) ?? 0;
8499
+ if (app1Modified > cacheModified) return true;
8409
8500
  const app2Modified = modifiedTimes.get(app2.fullPath) ?? 0;
8410
- const cacheModified = cacheEntry.metadata.createdTime;
8411
- return !cacheModified || app1Modified > cacheModified || app2Modified > cacheModified || void 0;
8501
+ if (app2Modified > cacheModified) return true;
8502
+ const modifiedMoreRecently = await modifiedMoreRecentlyThan(
8503
+ cacheModified,
8504
+ app1.fullPath,
8505
+ app2.fullPath
8506
+ );
8507
+ if (modifiedMoreRecently) return true;
8508
+ return void 0;
8412
8509
  }
8413
8510
  async function getDiffFiles(app1, app2, {
8414
- forceFresh = false,
8511
+ forceFresh,
8415
8512
  timings,
8416
8513
  request
8417
8514
  } = {}) {
@@ -8420,7 +8517,7 @@ async function getDiffFiles(app1, app2, {
8420
8517
  const result = await cachified({
8421
8518
  key,
8422
8519
  cache: diffFilesCache,
8423
- forceFresh: forceFresh || getForceFreshForDiff(app1, app2, cacheEntry),
8520
+ forceFresh: forceFresh || await getForceFreshForDiff(app1, app2, cacheEntry),
8424
8521
  timings,
8425
8522
  request,
8426
8523
  getFreshValue: () => getDiffFilesImpl(app1, app2)
@@ -8470,7 +8567,7 @@ async function getDiffFilesImpl(app1, app2) {
8470
8567
  })).filter((file) => !testFiles.includes(file.path));
8471
8568
  }
8472
8569
  async function getDiffCode(app1, app2, {
8473
- forceFresh = false,
8570
+ forceFresh,
8474
8571
  timings,
8475
8572
  request
8476
8573
  } = {}) {
@@ -8479,7 +8576,7 @@ async function getDiffCode(app1, app2, {
8479
8576
  const result = await cachified({
8480
8577
  key,
8481
8578
  cache: diffCodeCache,
8482
- forceFresh: forceFresh || getForceFreshForDiff(app1, app2, cacheEntry),
8579
+ forceFresh: forceFresh || await getForceFreshForDiff(app1, app2, cacheEntry),
8483
8580
  timings,
8484
8581
  request,
8485
8582
  getFreshValue: () => getDiffCodeImpl(app1, app2)
@@ -8631,7 +8728,7 @@ async function action$5({ request }) {
8631
8728
  const apps = await getApps({ forceFresh: true });
8632
8729
  const playground = apps.find(isPlaygroundApp);
8633
8730
  if (playground && converseApp) {
8634
- await getDiffCode(playground, converseApp, { forceFresh: true });
8731
+ void getDiffCode(playground, converseApp, { forceFresh: true });
8635
8732
  }
8636
8733
  return jsonWithPE(formData, { status: "success" });
8637
8734
  }
@@ -8641,7 +8738,7 @@ function SetPlayground({
8641
8738
  tooltipText,
8642
8739
  ...buttonProps
8643
8740
  }) {
8644
- var _a2;
8741
+ var _a;
8645
8742
  const fetcher = useFetcher();
8646
8743
  const peRedirectInput = usePERedirectInput();
8647
8744
  const submitButton = /* @__PURE__ */ jsx(
@@ -8652,7 +8749,7 @@ function SetPlayground({
8652
8749
  className: clsx(
8653
8750
  buttonProps.className,
8654
8751
  fetcher.state !== "idle" ? "cursor-progress" : null,
8655
- ((_a2 = fetcher.data) == null ? void 0 : _a2.status) === "error" ? "cursor-not-allowed" : null
8752
+ ((_a = fetcher.data) == null ? void 0 : _a.status) === "error" ? "cursor-not-allowed" : null
8656
8753
  )
8657
8754
  }
8658
8755
  );
@@ -8676,7 +8773,7 @@ function PlaygroundChooser({
8676
8773
  playgroundAppName,
8677
8774
  allApps
8678
8775
  }) {
8679
- var _a2;
8776
+ var _a;
8680
8777
  const fetcher = useFetcher();
8681
8778
  return /* @__PURE__ */ jsxs(
8682
8779
  Select.Root,
@@ -8697,7 +8794,7 @@ function PlaygroundChooser({
8697
8794
  className: clsx(
8698
8795
  "flex h-full w-full items-center justify-between text-left radix-placeholder:text-gray-500 focus-visible:outline-none",
8699
8796
  fetcher.state !== "idle" ? "cursor-progress" : null,
8700
- ((_a2 = fetcher.data) == null ? void 0 : _a2.status) === "error" ? "cursor-not-allowed" : null
8797
+ ((_a = fetcher.data) == null ? void 0 : _a.status) === "error" ? "cursor-not-allowed" : null
8701
8798
  ),
8702
8799
  children: [
8703
8800
  /* @__PURE__ */ jsx("span", { className: "w-80 flex-1 overflow-hidden text-ellipsis whitespace-nowrap scrollbar-thin scrollbar-thumb-scrollbar", children: /* @__PURE__ */ jsx(
@@ -8843,10 +8940,10 @@ function DiffLink({
8843
8940
  );
8844
8941
  }
8845
8942
  function getAppName2(input) {
8846
- var _a2;
8943
+ var _a;
8847
8944
  if (typeof input === "number") {
8848
8945
  const stepIndex = data.exerciseIndex + input;
8849
- return (_a2 = data.allApps[stepIndex]) == null ? void 0 : _a2.name;
8946
+ return (_a = data.allApps[stepIndex]) == null ? void 0 : _a.name;
8850
8947
  }
8851
8948
  if (!input) return null;
8852
8949
  for (const { name, stepName } of data.allApps) {
@@ -8928,7 +9025,7 @@ function LinkToApp({
8928
9025
  children = /* @__PURE__ */ jsx("code", { children: appTo.toString() }),
8929
9026
  ...props
8930
9027
  }) {
8931
- var _a2;
9028
+ var _a;
8932
9029
  const [searchParams] = useSearchParams();
8933
9030
  const to = `?${withParam$1(
8934
9031
  searchParams,
@@ -8942,7 +9039,7 @@ function LinkToApp({
8942
9039
  const previewAppUrl = (app == null ? void 0 : app.dev.type) === "script" ? getBaseUrl({
8943
9040
  domain: requestInfo.domain,
8944
9041
  port: app.dev.portNumber
8945
- }) : ((_a2 = data.playground) == null ? void 0 : _a2.dev.type) === "browser" ? data.playground.dev.pathname : null;
9042
+ }) : ((_a = data.playground) == null ? void 0 : _a.dev.type) === "browser" ? data.playground.dev.pathname : null;
8946
9043
  const { inBrowserBrowserRef } = useStepContext();
8947
9044
  const href = previewAppUrl ? previewAppUrl.slice(0, -1) + appTo.toString() : null;
8948
9045
  return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center justify-between gap-1", children: [
@@ -8956,9 +9053,9 @@ function LinkToApp({
8956
9053
  }),
8957
9054
  title: ENV.EPICSHOP_DEPLOYED ? "Cannot link to app in deployed version" : void 0,
8958
9055
  onClick: (event) => {
8959
- var _a3, _b;
9056
+ var _a2, _b;
8960
9057
  if (ENV.EPICSHOP_DEPLOYED) event.preventDefault();
8961
- (_a3 = props.onClick) == null ? void 0 : _a3.call(props, event);
9058
+ (_a2 = props.onClick) == null ? void 0 : _a2.call(props, event);
8962
9059
  (_b = inBrowserBrowserRef.current) == null ? void 0 : _b.handleExtrnalNavigation(appTo.toString());
8963
9060
  },
8964
9061
  children
@@ -8985,14 +9082,14 @@ function LinkToApp({
8985
9082
  function TouchedFiles({
8986
9083
  diffFilesPromise
8987
9084
  }) {
8988
- var _a2, _b;
9085
+ var _a, _b;
8989
9086
  const data = useLoaderData();
8990
9087
  const [open, setOpen] = React.useState(false);
8991
9088
  const contentRef = React.useRef(null);
8992
9089
  function handleLaunchUpdate() {
8993
9090
  setOpen(false);
8994
9091
  }
8995
- const appName = (_a2 = data.playground) == null ? void 0 : _a2.appName;
9092
+ const appName = (_a = data.playground) == null ? void 0 : _a.appName;
8996
9093
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
8997
9094
  /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
8998
9095
  "button",
@@ -9051,12 +9148,12 @@ function TouchedFiles({
9051
9148
  }
9052
9149
  ) }) : null,
9053
9150
  diffFiles.map((file) => {
9054
- var _a3;
9151
+ var _a2;
9055
9152
  return /* @__PURE__ */ jsx("li", { "data-state": file.status, children: /* @__PURE__ */ jsx(
9056
9153
  LaunchEditor,
9057
9154
  {
9058
9155
  appFile: `${file.path},${file.line},1`,
9059
- appName: ENV.EPICSHOP_DEPLOYED ? ((_a3 = data.problem) == null ? void 0 : _a3.name) ?? "playground" : "playground",
9156
+ appName: ENV.EPICSHOP_DEPLOYED ? ((_a2 = data.problem) == null ? void 0 : _a2.name) ?? "playground" : "playground",
9060
9157
  onUpdate: handleLaunchUpdate,
9061
9158
  children: /* @__PURE__ */ jsx("code", { children: file.path })
9062
9159
  }
@@ -9074,14 +9171,14 @@ function TouchedFiles({
9074
9171
  ] }) });
9075
9172
  }
9076
9173
  function pageTitle(data, workshopTitle) {
9077
- var _a2;
9174
+ var _a;
9078
9175
  const exerciseNumber = (data == null ? void 0 : data.exerciseStepApp.exerciseNumber.toString().padStart(2, "0")) ?? "00";
9079
9176
  const stepNumber = (data == null ? void 0 : data.exerciseStepApp.stepNumber.toString().padStart(2, "0")) ?? "00";
9080
9177
  const emoji2 = {
9081
9178
  problem: "💪",
9082
9179
  solution: "🏁"
9083
9180
  }[(data == null ? void 0 : data.type) ?? "problem"];
9084
- const title = ((_a2 = data == null ? void 0 : data[data.type]) == null ? void 0 : _a2.title) ?? "N/A";
9181
+ const title = ((_a = data == null ? void 0 : data[data.type]) == null ? void 0 : _a.title) ?? "N/A";
9085
9182
  return {
9086
9183
  emoji: emoji2,
9087
9184
  stepNumber,
@@ -9097,8 +9194,8 @@ const meta$3 = ({
9097
9194
  matches,
9098
9195
  params
9099
9196
  }) => {
9100
- var _a2;
9101
- const rootData = (_a2 = matches.find((m) => m.id === "root")) == null ? void 0 : _a2.data;
9197
+ var _a;
9198
+ const rootData = (_a = matches.find((m) => m.id === "root")) == null ? void 0 : _a.data;
9102
9199
  if (!data || !rootData) return [{ title: "🦉 | Error" }];
9103
9200
  const { emoji: emoji2, stepNumber, title, exerciseNumber, exerciseTitle } = pageTitle(data);
9104
9201
  return getSeoMetaTags({
@@ -9111,8 +9208,8 @@ const meta$3 = ({
9111
9208
  });
9112
9209
  };
9113
9210
  async function loader$l({ request, params }) {
9114
- var _a2, _b;
9115
- const timings = makeTimings("exerciseStepTypeLoader");
9211
+ var _a, _b;
9212
+ const timings = makeTimings("exerciseStepTypeLayoutLoader");
9116
9213
  const url = new URL(request.url);
9117
9214
  const { title: workshopTitle } = getWorkshopConfig();
9118
9215
  const cacheOptions = { request, timings };
@@ -9170,7 +9267,7 @@ async function loader$l({ request, params }) {
9170
9267
  const exerciseId = getStepId(exerciseStepApp);
9171
9268
  const exerciseIndex = allApps.findIndex((step) => step.stepId === exerciseId);
9172
9269
  const exerciseApps = allAppsFull.filter(isExerciseStepApp).filter((app) => app.exerciseNumber === exerciseStepApp.exerciseNumber);
9173
- const isLastStep = ((_a2 = exerciseApps[exerciseApps.length - 1]) == null ? void 0 : _a2.name) === exerciseStepApp.name;
9270
+ const isLastStep = ((_a = exerciseApps[exerciseApps.length - 1]) == null ? void 0 : _a.name) === exerciseStepApp.name;
9174
9271
  const isFirstStep = ((_b = exerciseApps[0]) == null ? void 0 : _b.name) === exerciseStepApp.name;
9175
9272
  const nextApp = await getNextExerciseApp(exerciseStepApp, cacheOptions);
9176
9273
  const prevApp = await getPrevExerciseApp(exerciseStepApp, cacheOptions);
@@ -9246,10 +9343,13 @@ const headers$7 = ({ loaderHeaders, parentHeaders }) => {
9246
9343
  return headers2;
9247
9344
  };
9248
9345
  function ExercisePartRoute$1() {
9249
- var _a2;
9346
+ var _a;
9250
9347
  const data = useLoaderData();
9251
9348
  const inBrowserBrowserRef = useRef(null);
9252
9349
  const titleBits = pageTitle(data);
9350
+ useRevalidationWS({
9351
+ watchPaths: [`${data.exerciseStepApp.relativePath}/README.mdx`]
9352
+ });
9253
9353
  return /* @__PURE__ */ jsx("div", { className: "flex max-w-full flex-grow flex-col", children: /* @__PURE__ */ jsxs("main", { className: "flex flex-grow flex-col sm:grid sm:h-full sm:min-h-[800px] sm:grid-cols-1 sm:grid-rows-2 md:min-h-[unset] lg:grid-cols-2 lg:grid-rows-1", children: [
9254
9354
  /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col sm:col-span-1 sm:row-span-1 sm:h-full lg:border-r", children: [
9255
9355
  /* @__PURE__ */ jsx("h1", { className: "h-14 border-b pl-10 pr-5 text-sm font-medium leading-tight", children: /* @__PURE__ */ jsxs("div", { className: "flex h-14 flex-wrap items-center justify-between gap-x-2 py-2", children: [
@@ -9278,7 +9378,7 @@ function ExercisePartRoute$1() {
9278
9378
  ")"
9279
9379
  ] })
9280
9380
  ] }),
9281
- data.problem && ((_a2 = data.playground) == null ? void 0 : _a2.appName) !== data.problem.name ? /* @__PURE__ */ jsx("div", { className: "hidden md:block", children: /* @__PURE__ */ jsx(SetAppToPlayground, { appName: data.problem.name }) }) : null
9381
+ data.problem && ((_a = data.playground) == null ? void 0 : _a.appName) !== data.problem.name ? /* @__PURE__ */ jsx("div", { className: "hidden md:block", children: /* @__PURE__ */ jsx(SetAppToPlayground, { appName: data.problem.name }) }) : null
9282
9382
  ] }) }),
9283
9383
  /* @__PURE__ */ jsxs(
9284
9384
  "article",
@@ -9456,10 +9556,10 @@ async function action$4({ request }) {
9456
9556
  throw new Error(`Unknown intent: ${intent}`);
9457
9557
  }
9458
9558
  function AppStopper({ name }) {
9459
- var _a2;
9559
+ var _a;
9460
9560
  const fetcher = useFetcher();
9461
9561
  const peRedirectInput = usePERedirectInput();
9462
- const inFlightIntent = (_a2 = fetcher.formData) == null ? void 0 : _a2.get("intent");
9562
+ const inFlightIntent = (_a = fetcher.formData) == null ? void 0 : _a.get("intent");
9463
9563
  const inFlightState = inFlightIntent === "stop" ? "Stopping App" : inFlightIntent === "restart" ? "Restarting App" : null;
9464
9564
  const altDown = useAltDown();
9465
9565
  return /* @__PURE__ */ jsxs(fetcher.Form, { method: "POST", action: "/start", children: [
@@ -9489,10 +9589,10 @@ function PortStopper({ port: port2 }) {
9489
9589
  ] });
9490
9590
  }
9491
9591
  function AppStarter({ name }) {
9492
- var _a2;
9592
+ var _a;
9493
9593
  const fetcher = useFetcher();
9494
9594
  const peRedirectInput = usePERedirectInput();
9495
- if (((_a2 = fetcher.data) == null ? void 0 : _a2.status) === "app-not-started") {
9595
+ if (((_a = fetcher.data) == null ? void 0 : _a.status) === "app-not-started") {
9496
9596
  if (fetcher.data.error === "port-unavailable") {
9497
9597
  return /* @__PURE__ */ jsxs("div", { children: [
9498
9598
  "The port is unavailable. Would you like to stop whatever is running on that port and try again?",
@@ -9618,8 +9718,8 @@ function InBrowserBrowserForRealzImpl({ baseUrl, id, name, initialRoute }, ref)
9618
9718
  });
9619
9719
  useEffect(() => {
9620
9720
  function handleMessage(messageEvent) {
9621
- var _a2;
9622
- if (messageEvent.source !== ((_a2 = iframeRef.current) == null ? void 0 : _a2.contentWindow)) return;
9721
+ var _a;
9722
+ if (messageEvent.source !== ((_a = iframeRef.current) == null ? void 0 : _a.contentWindow)) return;
9623
9723
  const result = messageSchema.safeParse(messageEvent.data, {
9624
9724
  path: ["messageEvent", "data"]
9625
9725
  });
@@ -9720,7 +9820,7 @@ function InBrowserBrowserForRealzImpl({ baseUrl, id, name, initialRoute }, ref)
9720
9820
  }
9721
9821
  }, [iframePathname]);
9722
9822
  const navigateChild = (...params) => {
9723
- var _a2, _b;
9823
+ var _a, _b;
9724
9824
  const to = params[0];
9725
9825
  if (typeof to === "number") {
9726
9826
  lastDirectionRef.current = to > 0 ? "forward" : "back";
@@ -9733,7 +9833,7 @@ function InBrowserBrowserForRealzImpl({ baseUrl, id, name, initialRoute }, ref)
9733
9833
  lastDirectionTimeout.current = setTimeout(() => {
9734
9834
  lastDirectionRef.current = "new";
9735
9835
  }, 100);
9736
- (_b = (_a2 = iframeRef.current) == null ? void 0 : _a2.contentWindow) == null ? void 0 : _b.postMessage(
9836
+ (_b = (_a = iframeRef.current) == null ? void 0 : _a.contentWindow) == null ? void 0 : _b.postMessage(
9737
9837
  { type: "epicshop:navigate-call", params },
9738
9838
  "*"
9739
9839
  );
@@ -10131,6 +10231,18 @@ const mdxComponents$3 = {
10131
10231
  // override the pre-with-buttons
10132
10232
  pre
10133
10233
  };
10234
+ function RevalidateApps({
10235
+ app1: app1Name,
10236
+ app2: app2Name
10237
+ }) {
10238
+ const apps = useApps();
10239
+ const app1 = apps.find((app) => app.name === app1Name);
10240
+ const app2 = apps.find((app) => app.name === app2Name);
10241
+ useRevalidationWS({
10242
+ watchPaths: [app1 == null ? void 0 : app1.fullPath, app2 == null ? void 0 : app2.fullPath].filter(Boolean)
10243
+ });
10244
+ return null;
10245
+ }
10134
10246
  function Diff({
10135
10247
  diff,
10136
10248
  allApps
@@ -10219,7 +10331,8 @@ function Diff({
10219
10331
  `${diff2.app1}${diff2.app2}`
10220
10332
  )
10221
10333
  ] }),
10222
- /* @__PURE__ */ jsx("div", { className: "flex-grow overflow-y-scroll scrollbar-thin scrollbar-thumb-scrollbar", children: diff2.diffCode ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Accordion.Root, { className: "w-full", type: "multiple", children: /* @__PURE__ */ jsx(Mdx, { code: diff2.diffCode, components: mdxComponents$3 }) }) }) : diff2.app1 && diff2.app2 ? /* @__PURE__ */ jsx("p", { className: "m-5 inline-flex items-center justify-center bg-foreground px-1 py-0.5 font-mono text-sm uppercase text-background", children: "There was a problem generating the diff" }) : /* @__PURE__ */ jsx("p", { className: "m-5 inline-flex items-center justify-center bg-foreground px-1 py-0.5 font-mono text-sm uppercase text-background", children: "Select two apps to compare" }) })
10334
+ /* @__PURE__ */ jsx("div", { className: "flex-grow overflow-y-scroll scrollbar-thin scrollbar-thumb-scrollbar", children: diff2.diffCode ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Accordion.Root, { className: "w-full", type: "multiple", children: /* @__PURE__ */ jsx(Mdx, { code: diff2.diffCode, components: mdxComponents$3 }) }) }) : diff2.app1 && diff2.app2 ? /* @__PURE__ */ jsx("p", { className: "m-5 inline-flex items-center justify-center bg-foreground px-1 py-0.5 font-mono text-sm uppercase text-background", children: "There was a problem generating the diff" }) : /* @__PURE__ */ jsx("p", { className: "m-5 inline-flex items-center justify-center bg-foreground px-1 py-0.5 font-mono text-sm uppercase text-background", children: "Select two apps to compare" }) }),
10335
+ /* @__PURE__ */ jsx(RevalidateApps, { app1: diff2.app1, app2: diff2.app2 })
10223
10336
  ] })
10224
10337
  }
10225
10338
  )
@@ -10359,13 +10472,13 @@ const EpicForumResponseSchema = z.object({
10359
10472
  })
10360
10473
  );
10361
10474
  async function fetchDiscordPosts({ request }) {
10362
- var _a2;
10475
+ var _a;
10363
10476
  const config = getWorkshopConfig();
10364
10477
  const forceFresh = await shouldForceFresh({ request });
10365
10478
  const searchParams = new URLSearchParams({
10366
10479
  channelId: config.product.discordChannelId
10367
10480
  });
10368
- if ((_a2 = config.product.discordTags) == null ? void 0 : _a2.length) {
10481
+ if ((_a = config.product.discordTags) == null ? void 0 : _a.length) {
10369
10482
  for (const tag of config.product.discordTags) {
10370
10483
  searchParams.append("tagId", tag);
10371
10484
  }
@@ -10703,8 +10816,8 @@ function InBrowserTestRunner({
10703
10816
  const [testSteps, setTestSteps] = useState([]);
10704
10817
  useEffect(() => {
10705
10818
  function handleMessage(messageEvent) {
10706
- var _a2;
10707
- if (messageEvent.source !== ((_a2 = iframeRef.current) == null ? void 0 : _a2.contentWindow)) return;
10819
+ var _a;
10820
+ if (messageEvent.source !== ((_a = iframeRef.current) == null ? void 0 : _a.contentWindow)) return;
10708
10821
  if ("request" in messageEvent.data) return;
10709
10822
  const result = testRunnerDataSchema.safeParse(messageEvent.data, {
10710
10823
  path: ["messageEvent", "data"]
@@ -10734,7 +10847,7 @@ function InBrowserTestRunner({
10734
10847
  };
10735
10848
  }, []);
10736
10849
  const statusEmoji = {
10737
- pending: /* @__PURE__ */ jsx(AnimatedBars, { "aria-label": "Pending" }),
10850
+ pending: /* @__PURE__ */ jsx(AnimatedBars, { size: 14, "aria-label": "Pending" }),
10738
10851
  pass: /* @__PURE__ */ jsx(
10739
10852
  Icon,
10740
10853
  {
@@ -10809,8 +10922,8 @@ function InBrowserTestRunner({
10809
10922
  "button",
10810
10923
  {
10811
10924
  onClick: () => {
10812
- var _a2, _b;
10813
- return (_b = (_a2 = iframeRef.current) == null ? void 0 : _a2.contentWindow) == null ? void 0 : _b.location.reload();
10925
+ var _a, _b;
10926
+ return (_b = (_a = iframeRef.current) == null ? void 0 : _a.contentWindow) == null ? void 0 : _b.location.reload();
10814
10927
  },
10815
10928
  className: "border-r p-3",
10816
10929
  children: /* @__PURE__ */ jsx(Icon, { name: "Refresh", "aria-label": "Rerun Tests" })
@@ -10886,7 +10999,7 @@ async function loader$j({ request }) {
10886
10999
  return json$1({ error: "App is not running tests" }, { status: 404 });
10887
11000
  }
10888
11001
  return eventStream(request.signal, function setup(send) {
10889
- var _a2, _b;
11002
+ var _a, _b;
10890
11003
  let queue = [];
10891
11004
  function sendEvent(event) {
10892
11005
  queue.push(event);
@@ -10928,18 +11041,18 @@ async function loader$j({ request }) {
10928
11041
  });
10929
11042
  }
10930
11043
  function handleExit(code) {
10931
- var _a3, _b2;
10932
- (_a3 = testProcess == null ? void 0 : testProcess.stdout) == null ? void 0 : _a3.off("data", handleStdOutData);
11044
+ var _a2, _b2;
11045
+ (_a2 = testProcess == null ? void 0 : testProcess.stdout) == null ? void 0 : _a2.off("data", handleStdOutData);
10933
11046
  (_b2 = testProcess == null ? void 0 : testProcess.stderr) == null ? void 0 : _b2.off("data", handleStdErrData);
10934
11047
  testProcess == null ? void 0 : testProcess.off("exit", handleExit);
10935
11048
  sendEvent({ type: "exit", isRunning: false, code });
10936
11049
  }
10937
- (_a2 = testProcess.stdout) == null ? void 0 : _a2.on("data", handleStdOutData);
11050
+ (_a = testProcess.stdout) == null ? void 0 : _a.on("data", handleStdOutData);
10938
11051
  (_b = testProcess.stderr) == null ? void 0 : _b.on("data", handleStdErrData);
10939
11052
  testProcess.on("exit", handleExit);
10940
11053
  return function cleanup() {
10941
- var _a3, _b2;
10942
- (_a3 = testProcess.stdout) == null ? void 0 : _a3.off("data", handleStdOutData);
11054
+ var _a2, _b2;
11055
+ (_a2 = testProcess.stdout) == null ? void 0 : _a2.off("data", handleStdOutData);
10943
11056
  (_b2 = testProcess.stderr) == null ? void 0 : _b2.off("data", handleStdErrData);
10944
11057
  testProcess.off("exit", handleExit);
10945
11058
  clearInterval(interval);
@@ -10947,7 +11060,7 @@ async function loader$j({ request }) {
10947
11060
  });
10948
11061
  }
10949
11062
  async function action$3({ request }) {
10950
- var _a2;
11063
+ var _a;
10951
11064
  ensureUndeployed();
10952
11065
  const formData = await request.formData();
10953
11066
  const userHasAccess = await userHasAccessToWorkshop({
@@ -10996,7 +11109,7 @@ async function action$3({ request }) {
10996
11109
  case "stop": {
10997
11110
  const processEntry = getTestProcessEntry(app);
10998
11111
  if (processEntry) {
10999
- (_a2 = processEntry.process) == null ? void 0 : _a2.kill();
11112
+ (_a = processEntry.process) == null ? void 0 : _a.kill();
11000
11113
  }
11001
11114
  return jsonWithPE(formData, { success: true });
11002
11115
  }
@@ -11119,8 +11232,8 @@ function TestRunner({
11119
11232
  latestOnRun.current = onRun;
11120
11233
  }, [onRun]);
11121
11234
  useEffect(() => {
11122
- var _a2, _b;
11123
- if ((_a2 = fetcher.data) == null ? void 0 : _a2.success) {
11235
+ var _a, _b;
11236
+ if ((_a = fetcher.data) == null ? void 0 : _a.success) {
11124
11237
  (_b = latestOnRun.current) == null ? void 0 : _b.call(latestOnRun);
11125
11238
  }
11126
11239
  }, [fetcher.data]);
@@ -11156,8 +11269,8 @@ function ClearTest({
11156
11269
  latestOnClear.current = onClear;
11157
11270
  }, [onClear]);
11158
11271
  useEffect(() => {
11159
- var _a2, _b;
11160
- if ((_a2 = fetcher.data) == null ? void 0 : _a2.success) {
11272
+ var _a, _b;
11273
+ if ((_a = fetcher.data) == null ? void 0 : _a.success) {
11161
11274
  (_b = latestOnClear.current) == null ? void 0 : _b.call(latestOnClear);
11162
11275
  }
11163
11276
  }, [fetcher.data]);
@@ -11193,8 +11306,8 @@ function StopTest({
11193
11306
  latestOnStop.current = onStop;
11194
11307
  }, [onStop]);
11195
11308
  useEffect(() => {
11196
- var _a2, _b;
11197
- if ((_a2 = fetcher.data) == null ? void 0 : _a2.success) {
11309
+ var _a, _b;
11310
+ if ((_a = fetcher.data) == null ? void 0 : _a.success) {
11198
11311
  (_b = latestOnStop.current) == null ? void 0 : _b.call(latestOnStop);
11199
11312
  }
11200
11313
  }, [fetcher.data]);
@@ -11294,7 +11407,7 @@ function TestUI({
11294
11407
  return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-lg", children: /* @__PURE__ */ jsx("p", { children: "No tests here 😢 Sorry." }) });
11295
11408
  }
11296
11409
  async function loader$i({ request, params }) {
11297
- const timings = makeTimings("exerciseStepTypeLoader");
11410
+ const timings = makeTimings("exerciseStepTypeIndexLoader");
11298
11411
  const userHasAccess = await userHasAccessToWorkshop({
11299
11412
  request,
11300
11413
  timings
@@ -11456,7 +11569,7 @@ function withParam(searchParams, key, value) {
11456
11569
  return newSearchParams;
11457
11570
  }
11458
11571
  function ExercisePartRoute() {
11459
- var _a2, _b, _c, _d, _e, _f;
11572
+ var _a, _b, _c, _d, _e, _f;
11460
11573
  const data = useLoaderData();
11461
11574
  const [searchParams] = useSearchParams();
11462
11575
  const preview = searchParams.get("preview");
@@ -11464,12 +11577,12 @@ function ExercisePartRoute() {
11464
11577
  const altDown = useAltDown();
11465
11578
  const navigate = useNavigate();
11466
11579
  function shouldHideTab(tab) {
11467
- var _a3, _b2, _c2;
11580
+ var _a2, _b2, _c2;
11468
11581
  if (tab === "tests") {
11469
11582
  return ENV.EPICSHOP_DEPLOYED || !data.playground || data.playground.test.type === "none";
11470
11583
  }
11471
11584
  if (tab === "problem" || tab === "solution") {
11472
- if (((_a3 = data[tab]) == null ? void 0 : _a3.dev.type) === "none") return true;
11585
+ if (((_a2 = data[tab]) == null ? void 0 : _a2.dev.type) === "none") return true;
11473
11586
  if (ENV.EPICSHOP_DEPLOYED) {
11474
11587
  return ((_b2 = data[tab]) == null ? void 0 : _b2.dev.type) !== "browser" && !((_c2 = data[tab]) == null ? void 0 : _c2.stackBlitzUrl);
11475
11588
  }
@@ -11479,7 +11592,7 @@ function ExercisePartRoute() {
11479
11592
  }
11480
11593
  const activeTab = isValidPreview(preview) ? preview : tabs.find((t) => !shouldHideTab(t));
11481
11594
  const altDiffUrl = `/diff?${new URLSearchParams({
11482
- app1: ((_a2 = data.problem) == null ? void 0 : _a2.name) ?? "",
11595
+ app1: ((_a = data.problem) == null ? void 0 : _a.name) ?? "",
11483
11596
  app2: ((_b = data.solution) == null ? void 0 : _b.name) ?? ""
11484
11597
  })}`;
11485
11598
  function handleDiffTabClick(event) {
@@ -11671,9 +11784,9 @@ const meta$2 = ({
11671
11784
  data,
11672
11785
  matches
11673
11786
  }) => {
11674
- var _a2;
11787
+ var _a;
11675
11788
  const number = data == null ? void 0 : data.exercise.exerciseNumber.toString().padStart(2, "0");
11676
- const rootData = (_a2 = matches.find((m) => m.id === "root")) == null ? void 0 : _a2.data;
11789
+ const rootData = (_a = matches.find((m) => m.id === "root")) == null ? void 0 : _a.data;
11677
11790
  if (!data || !rootData) return [{ title: "🦉 | Error" }];
11678
11791
  return getSeoMetaTags({
11679
11792
  title: `🦉 | ${number}. ${data.exercise.title} | ${rootData == null ? void 0 : rootData.workshopTitle}`,
@@ -11721,10 +11834,10 @@ async function loader$f({ request, params }) {
11721
11834
  exercise.finishedEpicVideoEmbeds,
11722
11835
  { request }
11723
11836
  ),
11724
- exerciseFinished: exercise.finishedCode ? {
11837
+ exerciseFinished: {
11725
11838
  file: finishedFilepath,
11726
11839
  relativePath: `exercises/${exercise.dirName}/FINISHED.mdx`
11727
- } : null,
11840
+ },
11728
11841
  prevStepLink: prevApp ? {
11729
11842
  to: getAppPageRoute(prevApp),
11730
11843
  "aria-label": `${prevApp.title} (${prevApp.type})`
@@ -11755,6 +11868,9 @@ const mdxComponents$2 = { h1: () => null };
11755
11868
  function ExerciseFinished$1() {
11756
11869
  const data = useLoaderData();
11757
11870
  const exerciseNumber = data.exercise.exerciseNumber.toString().padStart(2, "0");
11871
+ useRevalidationWS({
11872
+ watchPaths: [`./exercises/${exerciseNumber}/FINISHED.mdx`]
11873
+ });
11758
11874
  return /* @__PURE__ */ jsx("div", { className: "flex max-w-full flex-grow flex-col", children: /* @__PURE__ */ jsxs("main", { className: "flex flex-grow flex-col sm:grid sm:h-full sm:min-h-[800px] sm:grid-cols-1 sm:grid-rows-2 md:min-h-[unset] lg:grid-cols-2 lg:grid-rows-1", children: [
11759
11875
  /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col sm:col-span-1 sm:row-span-1 sm:h-full lg:border-r", children: [
11760
11876
  /* @__PURE__ */ jsx("h1", { className: "h-14 border-b pl-10 pr-5 text-sm font-medium leading-tight", children: /* @__PURE__ */ jsx("div", { className: "flex h-14 flex-wrap items-center justify-between gap-x-2 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-start gap-x-2", children: [
@@ -11796,13 +11912,13 @@ function ExerciseFinished$1() {
11796
11912
  ),
11797
11913
  /* @__PURE__ */ jsxs("div", { className: "flex h-16 justify-between border-b-4 border-t lg:border-b-0", children: [
11798
11914
  /* @__PURE__ */ jsx("div", {}),
11799
- data.exerciseFinished ? /* @__PURE__ */ jsx(
11915
+ /* @__PURE__ */ jsx(
11800
11916
  EditFileOnGitHub,
11801
11917
  {
11802
11918
  file: data.exerciseFinished.file,
11803
11919
  relativePath: data.exerciseFinished.relativePath
11804
11920
  }
11805
- ) : null,
11921
+ ),
11806
11922
  /* @__PURE__ */ jsx(NavChevrons, { prev: data.prevStepLink, next: data.nextStepLink })
11807
11923
  ] })
11808
11924
  ] }),
@@ -11854,8 +11970,8 @@ const handle$4 = {
11854
11970
  const meta$1 = ({
11855
11971
  matches
11856
11972
  }) => {
11857
- var _a2;
11858
- const rootData = (_a2 = matches.find((m) => m.id === "root")) == null ? void 0 : _a2.data;
11973
+ var _a;
11974
+ const rootData = (_a = matches.find((m) => m.id === "root")) == null ? void 0 : _a.data;
11859
11975
  if (!rootData) return [];
11860
11976
  return getSeoMetaTags({
11861
11977
  title: `🎉 ${rootData == null ? void 0 : rootData.workshopTitle}`,
@@ -11917,6 +12033,7 @@ const headers$2 = ({ loaderHeaders, parentHeaders }) => {
11917
12033
  const mdxComponents$1 = { h1: () => null };
11918
12034
  function ExerciseFinished() {
11919
12035
  const data = useLoaderData();
12036
+ useRevalidationWS({ watchPaths: ["./exercises/FINISHED.mdx"] });
11920
12037
  return /* @__PURE__ */ jsx("div", { className: "flex h-full flex-grow flex-col", children: /* @__PURE__ */ jsxs("main", { className: "grid h-full flex-grow grid-cols-1 grid-rows-2 lg:grid-cols-2 lg:grid-rows-1", children: [
11921
12038
  /* @__PURE__ */ jsxs("div", { className: "relative col-span-1 row-span-1 flex h-full flex-col lg:border-r", children: [
11922
12039
  /* @__PURE__ */ jsx("h1", { className: "h-14 border-b pl-10 pr-5 text-sm font-medium uppercase leading-none", children: /* @__PURE__ */ jsx("div", { className: "flex h-14 flex-wrap items-center justify-between gap-x-2 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-start gap-x-2", children: [
@@ -12249,7 +12366,7 @@ async function action$2() {
12249
12366
  return json$1({ status: "pending" });
12250
12367
  }
12251
12368
  function Login() {
12252
- var _a2;
12369
+ var _a;
12253
12370
  const {
12254
12371
  product: { displayName }
12255
12372
  } = useWorkshopConfig();
@@ -12343,7 +12460,7 @@ function Login() {
12343
12460
  "."
12344
12461
  ] })
12345
12462
  ] }),
12346
- /* @__PURE__ */ jsx(loginFetcher.Form, { method: "POST", children: /* @__PURE__ */ jsx(Button, { varient: "primary", type: "submit", children: loginFetcher.state === "idle" && ((_a2 = loginFetcher.data) == null ? void 0 : _a2.status) !== "pending" ? `Retrieve Auth Code` : `Retrieving Auth Code...` }) })
12463
+ /* @__PURE__ */ jsx(loginFetcher.Form, { method: "POST", children: /* @__PURE__ */ jsx(Button, { varient: "primary", type: "submit", children: loginFetcher.state === "idle" && ((_a = loginFetcher.data) == null ? void 0 : _a.status) !== "pending" ? `Retrieve Auth Code` : `Retrieving Auth Code...` }) })
12347
12464
  ] }),
12348
12465
  authError ? /* @__PURE__ */ jsxs("div", { className: "mt-4 text-red-500", children: [
12349
12466
  "There was an error: ",
@@ -12363,10 +12480,10 @@ const handle$2 = {
12363
12480
  getSitemapEntries: () => null
12364
12481
  };
12365
12482
  function Support() {
12366
- var _a2;
12367
- const repoGroups = (_a2 = ENV.EPICSHOP_GITHUB_REPO.match(
12483
+ var _a;
12484
+ const repoGroups = (_a = ENV.EPICSHOP_GITHUB_REPO.match(
12368
12485
  /github\.com\/(?<org>[^/?]+)\/(?<repo>[^/?]+)/
12369
- )) == null ? void 0 : _a2.groups;
12486
+ )) == null ? void 0 : _a.groups;
12370
12487
  let repoUrl = ENV.EPICSHOP_GITHUB_REPO;
12371
12488
  let repoIssuesUrl = repoUrl;
12372
12489
  if ((repoGroups == null ? void 0 : repoGroups.org) && repoGroups.repo) {
@@ -12451,8 +12568,8 @@ const handle$1 = {
12451
12568
  const meta = ({
12452
12569
  matches
12453
12570
  }) => {
12454
- var _a2;
12455
- const rootData = (_a2 = matches.find((m) => m.id === "root")) == null ? void 0 : _a2.data;
12571
+ var _a;
12572
+ const rootData = (_a = matches.find((m) => m.id === "root")) == null ? void 0 : _a.data;
12456
12573
  return [{ title: `👷 | ${rootData == null ? void 0 : rootData.workshopTitle}` }];
12457
12574
  };
12458
12575
  async function loader$a({ request }) {
@@ -12535,11 +12652,11 @@ function linkProgress(progress) {
12535
12652
  }
12536
12653
  }
12537
12654
  function AdminLayout() {
12538
- var _a2, _b;
12655
+ var _a, _b;
12539
12656
  const data = useLoaderData();
12540
12657
  const navigation = useNavigation();
12541
12658
  const epicProgress = useEpicProgress();
12542
- const isStartingInspector = ((_a2 = navigation.formData) == null ? void 0 : _a2.get("intent")) === "inspect";
12659
+ const isStartingInspector = ((_a = navigation.formData) == null ? void 0 : _a.get("intent")) === "inspect";
12543
12660
  const isStoppingInspector = ((_b = navigation.formData) == null ? void 0 : _b.get("intent")) === "stop-inspect";
12544
12661
  const progressStatus = {
12545
12662
  completed: "bg-blue-500",
@@ -12698,7 +12815,7 @@ const route27 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
12698
12815
  loader: loader$7
12699
12816
  }, Symbol.toStringTag, { value: "Module" }));
12700
12817
  async function loader$6({ request }) {
12701
- var _a2, _b, _c, _d;
12818
+ var _a, _b, _c, _d;
12702
12819
  const reqUrl = new URL(request.url);
12703
12820
  const searchParams = reqUrl.searchParams;
12704
12821
  const timings = makeTimings("diffLoader");
@@ -12742,7 +12859,7 @@ async function loader$6({ request }) {
12742
12859
  const prevApp2Index = prevApp1Index + 1;
12743
12860
  const nextApp1Index = usingDefaultApp1 ? 0 : app1Index + 1 < allApps.length ? app1Index + 1 : -2;
12744
12861
  const nextApp2Index = nextApp1Index + 1;
12745
- const prevApp1 = (_a2 = allAppsFull[prevApp1Index]) == null ? void 0 : _a2.name;
12862
+ const prevApp1 = (_a = allAppsFull[prevApp1Index]) == null ? void 0 : _a.name;
12746
12863
  const prevApp2 = (_b = allAppsFull[prevApp2Index]) == null ? void 0 : _b.name;
12747
12864
  const nextApp1 = (_c = allAppsFull[nextApp1Index]) == null ? void 0 : _c.name;
12748
12865
  const nextApp2 = (_d = allAppsFull[nextApp2Index]) == null ? void 0 : _d.name;
@@ -13226,7 +13343,7 @@ const route39 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
13226
13343
  __proto__: null,
13227
13344
  loader
13228
13345
  }, Symbol.toStringTag, { value: "Module" }));
13229
- const serverManifest = { "entry": { "module": "/assets/entry.client-3M2p-8I3.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js"], "css": [] }, "routes": { "root": { "id": "root", "parentId": void 0, "path": "", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/root-9wVBEzOq.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/error-boundary-COkPRBOZ.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-B-hHvmeV.js", "/assets/index-YNIH4TH8.js", "/assets/presence-Cr--lRCr.js", "/assets/seo-pBpFCWsy.js"], "css": [] }, "routes/$": { "id": "routes/$", "parentId": "root", "path": "*", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_-D0Tgngwe.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/error-boundary-COkPRBOZ.js"], "css": [] }, "routes/_app+/_layout": { "id": "routes/_app+/_layout", "parentId": "root", "path": void 0, "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_layout-BPwIOXxN.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/workshop-config-CL4F08kr.js", "/assets/product-BAWG1Vut.js", "/assets/index-CXyf3Reb.js", "/assets/user-DvujSs-t.js", "/assets/presence-Cr--lRCr.js", "/assets/progress-BFm2U-l5.js"], "css": [] }, "routes/_app+/account": { "id": "routes/_app+/account", "parentId": "routes/_app+/_layout", "path": "account", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/account-BatJhmSV.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/button-EE0aPg10.js", "/assets/tooltip-kD4kSf1i.js", "/assets/user-DvujSs-t.js", "/assets/presence-Cr--lRCr.js"], "css": [] }, "routes/_app+/app.$appName+/$": { "id": "routes/_app+/app.$appName+/$", "parentId": "routes/_app+/_layout", "path": "app/:appName/*", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/api.$": { "id": "routes/_app+/app.$appName+/api.$", "parentId": "routes/_app+/_layout", "path": "app/:appName/api/*", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/api._-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/epic_ws[.js]": { "id": "routes/_app+/app.$appName+/epic_ws[.js]", "parentId": "routes/_app+/_layout", "path": "app/:appName/epic_ws.js", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/epic_ws_.js_-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/index": { "id": "routes/_app+/app.$appName+/index", "parentId": "routes/_app+/_layout", "path": "app/:appName/", "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/index-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/test.$testName": { "id": "routes/_app+/app.$appName+/test.$testName", "parentId": "routes/_app+/_layout", "path": "app/:appName/test/:testName", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test._testName-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/test.epic_ws[.js]": { "id": "routes/_app+/app.$appName+/test.epic_ws[.js]", "parentId": "routes/_app+/_layout", "path": "app/:appName/test/epic_ws.js", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test.epic_ws_.js_-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/discord": { "id": "routes/_app+/discord", "parentId": "routes/_app+/_layout", "path": "discord", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/discord-BhzUjmbI.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/user-DvujSs-t.js", "/assets/discord-BRTW4Rnh.js"], "css": [] }, "routes/_app+/exercise+/_layout": { "id": "routes/_app+/exercise+/_layout", "parentId": "routes/_app+/_layout", "path": "exercise", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_layout-BUs3av-e.js", "imports": ["/assets/index-1cKOJFpX.js"], "css": [] }, "routes/_app+/exercise+/$exerciseNumber": { "id": "routes/_app+/exercise+/$exerciseNumber", "parentId": "routes/_app+/exercise+/_layout", "path": ":exerciseNumber", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_exerciseNumber-j9COGU-R.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/mdx-DwC5Oacq.js", "/assets/progress-BFm2U-l5.js", "/assets/seo-pBpFCWsy.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber", "parentId": "routes/_app+/exercise+/_layout", "path": ":exerciseNumber/:stepNumber", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_exerciseNumber_._stepNumber-7kSd_6hH.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js"], "css": [] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber", "path": ":type", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_layout-DnttUdzs.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-CXyf3Reb.js", "/assets/index-BwhlO_gF.js", "/assets/index-Dx5GmdYq.js", "/assets/error-boundary-COkPRBOZ.js", "/assets/nav-chevrons-g-C0ilNz.js", "/assets/mdx-DwC5Oacq.js", "/assets/progress-BFm2U-l5.js", "/assets/set-playground-DW0yVaNn.js", "/assets/seo-pBpFCWsy.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/app": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/app", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "path": "app", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/app-CJ9ElQg6.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/button-EE0aPg10.js", "/assets/loading-sXkYDMsx.js", "/assets/progress-bar-D3kudPcr.js", "/assets/preview-C7dtR2VR.js"], "css": [] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/index": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/index", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "path": void 0, "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/index-ZZCxObNp.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/tooltip-kD4kSf1i.js", "/assets/index-CXyf3Reb.js", "/assets/index-BwhlO_gF.js", "/assets/request-info-CEhUGODY.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/accordion-OfO-5m5D.js", "/assets/mdx-DwC5Oacq.js", "/assets/use-event-source-A_0lEOPX.js", "/assets/set-playground-DW0yVaNn.js", "/assets/button-EE0aPg10.js", "/assets/diff-BXHLJqTK.js", "/assets/error-boundary-COkPRBOZ.js", "/assets/discord-BRTW4Rnh.js", "/assets/index-B-hHvmeV.js", "/assets/tests-DUg6VXec.js", "/assets/preview-C7dtR2VR.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/test": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/test", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "path": "test", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test-N2HloCnX.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/index-CXyf3Reb.js", "/assets/index-BwhlO_gF.js", "/assets/progress-bar-D3kudPcr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/accordion-OfO-5m5D.js", "/assets/use-event-source-A_0lEOPX.js", "/assets/set-playground-DW0yVaNn.js", "/assets/tests-DUg6VXec.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.index": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.index", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber", "path": void 0, "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_exerciseNumber_._stepNumber.index-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/exercise+/$exerciseNumber_.finished": { "id": "routes/_app+/exercise+/$exerciseNumber_.finished", "parentId": "routes/_app+/exercise+/_layout", "path": ":exerciseNumber/finished", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_exerciseNumber_.finished-DDNPeo8x.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/nav-chevrons-g-C0ilNz.js", "/assets/mdx-DwC5Oacq.js", "/assets/progress-BFm2U-l5.js", "/assets/seo-pBpFCWsy.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/finished": { "id": "routes/_app+/finished", "parentId": "routes/_app+/_layout", "path": "finished", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/finished-Dop_5v80.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/nav-chevrons-g-C0ilNz.js", "/assets/mdx-DwC5Oacq.js", "/assets/seo-pBpFCWsy.js", "/assets/progress-BFm2U-l5.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/index": { "id": "routes/_app+/index", "parentId": "routes/_app+/_layout", "path": void 0, "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/index-D-l_qaLC.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/error-boundary-COkPRBOZ.js", "/assets/mdx-DwC5Oacq.js", "/assets/progress-BFm2U-l5.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/login": { "id": "routes/_app+/login", "parentId": "routes/_app+/_layout", "path": "login", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/login-Cc73KLYm.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/workshop-config-CL4F08kr.js", "/assets/use-event-source-A_0lEOPX.js", "/assets/button-EE0aPg10.js", "/assets/loading-sXkYDMsx.js", "/assets/product-BAWG1Vut.js"], "css": [] }, "routes/_app+/support": { "id": "routes/_app+/support", "parentId": "routes/_app+/_layout", "path": "support", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/support-hcqGIpir.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js"], "css": [] }, "routes/admin+/_layout": { "id": "routes/admin+/_layout", "parentId": "root", "path": "admin", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_layout-BwzhY4NI.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/pe-CUZaIcdt.js", "/assets/tooltip-kD4kSf1i.js", "/assets/progress-BFm2U-l5.js"], "css": [] }, "routes/admin+/apps": { "id": "routes/admin+/apps", "parentId": "routes/admin+/_layout", "path": "apps", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/apps-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/admin+/cache": { "id": "routes/admin+/cache", "parentId": "routes/admin+/_layout", "path": "cache", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/cache-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/apps": { "id": "routes/apps", "parentId": "root", "path": "apps", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/apps-DP2rzg_V.js", "imports": [], "css": [] }, "routes/diff": { "id": "routes/diff", "parentId": "root", "path": "diff", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/diff-BEEJhGiC.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/tooltip-kD4kSf1i.js", "/assets/index-CXyf3Reb.js", "/assets/index-BwhlO_gF.js", "/assets/request-info-CEhUGODY.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/epic-video-DZzPuXR8.js", "/assets/progress-bar-D3kudPcr.js", "/assets/accordion-OfO-5m5D.js", "/assets/mdx-DwC5Oacq.js", "/assets/diff-BXHLJqTK.js", "/assets/nav-chevrons-g-C0ilNz.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/discord.callback": { "id": "routes/discord.callback", "parentId": "root", "path": "discord/callback", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/discord.callback-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/exercises": { "id": "routes/exercises", "parentId": "root", "path": "exercises", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/exercises-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/launch-editor": { "id": "routes/launch-editor", "parentId": "root", "path": "launch-editor", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/launch-editor-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/login-sse": { "id": "routes/login-sse", "parentId": "root", "path": "login-sse", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/login-sse-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/og": { "id": "routes/og", "parentId": "root", "path": "og", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/og-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/onboarding": { "id": "routes/onboarding", "parentId": "root", "path": "onboarding", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/onboarding-DE9gclYS.js", "imports": ["/assets/index-1cKOJFpX.js", "/assets/components-CME-nGId.js", "/assets/misc-CxCgA-_O.js", "/assets/request-info-CEhUGODY.js", "/assets/tooltip-kD4kSf1i.js", "/assets/pe-CUZaIcdt.js", "/assets/index-YNIH4TH8.js", "/assets/loading-sXkYDMsx.js", "/assets/user-DvujSs-t.js", "/assets/workshop-config-CL4F08kr.js", "/assets/button-EE0aPg10.js", "/assets/epic-video-DZzPuXR8.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/processes": { "id": "routes/processes", "parentId": "root", "path": "processes", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/processes-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/progress": { "id": "routes/progress", "parentId": "root", "path": "progress", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/progress-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/robots[.]txt": { "id": "routes/robots[.]txt", "parentId": "root", "path": "robots.txt", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/robots_._txt-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/set-playground": { "id": "routes/set-playground", "parentId": "root", "path": "set-playground", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/set-playground-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/sitemap[.]xml": { "id": "routes/sitemap[.]xml", "parentId": "root", "path": "sitemap.xml", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/sitemap_._xml-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/start": { "id": "routes/start", "parentId": "root", "path": "start", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/start-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/test": { "id": "routes/test", "parentId": "root", "path": "test", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/theme/index": { "id": "routes/theme/index", "parentId": "root", "path": "theme", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/index-K6Dvbx-E.js", "imports": [], "css": [] }, "routes/video-player/index": { "id": "routes/video-player/index", "parentId": "root", "path": "video-player", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/index-DP2rzg_V.js", "imports": [], "css": [] } }, "url": "/assets/manifest-e53d022a.js", "version": "e53d022a" };
13346
+ const serverManifest = { "entry": { "module": "/assets/entry.client-CW5CUf_W.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js"], "css": [] }, "routes": { "root": { "id": "root", "parentId": void 0, "path": "", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/root-Cl86OUog.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/error-boundary-BcGxKpte.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/index-YtpQLUzj.js", "/assets/index-BvihEwfB.js", "/assets/presence-Dd98AJ_5.js", "/assets/seo-pBpFCWsy.js"], "css": [] }, "routes/$": { "id": "routes/$", "parentId": "root", "path": "*", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_-ZHCWB__B.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/error-boundary-BcGxKpte.js"], "css": [] }, "routes/_app+/_layout": { "id": "routes/_app+/_layout", "parentId": "root", "path": void 0, "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_layout-frPHZWgR.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/product-mjsTrqXs.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/index-BjNhezSK.js", "/assets/user-Boua6jiU.js", "/assets/presence-Dd98AJ_5.js", "/assets/progress-Co-59mG2.js"], "css": [] }, "routes/_app+/account": { "id": "routes/_app+/account", "parentId": "routes/_app+/_layout", "path": "account", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/account-DDuV9rZX.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/button-CMkJ8p0a.js", "/assets/tooltip-6-WS-Xux.js", "/assets/user-Boua6jiU.js", "/assets/presence-Dd98AJ_5.js"], "css": [] }, "routes/_app+/app.$appName+/$": { "id": "routes/_app+/app.$appName+/$", "parentId": "routes/_app+/_layout", "path": "app/:appName/*", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/api.$": { "id": "routes/_app+/app.$appName+/api.$", "parentId": "routes/_app+/_layout", "path": "app/:appName/api/*", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/api._-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/epic_ws[.js]": { "id": "routes/_app+/app.$appName+/epic_ws[.js]", "parentId": "routes/_app+/_layout", "path": "app/:appName/epic_ws.js", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/epic_ws_.js_-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/index": { "id": "routes/_app+/app.$appName+/index", "parentId": "routes/_app+/_layout", "path": "app/:appName/", "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/index-K6Dvbx-E.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/test.$testName": { "id": "routes/_app+/app.$appName+/test.$testName", "parentId": "routes/_app+/_layout", "path": "app/:appName/test/:testName", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test._testName-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/app.$appName+/test.epic_ws[.js]": { "id": "routes/_app+/app.$appName+/test.epic_ws[.js]", "parentId": "routes/_app+/_layout", "path": "app/:appName/test/epic_ws.js", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test.epic_ws_.js_-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/discord": { "id": "routes/_app+/discord", "parentId": "routes/_app+/_layout", "path": "discord", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/discord-DYeU0QX6.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/user-Boua6jiU.js", "/assets/discord-C9bVfiZ6.js"], "css": [] }, "routes/_app+/exercise+/_layout": { "id": "routes/_app+/exercise+/_layout", "parentId": "routes/_app+/_layout", "path": "exercise", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_layout-Cfbi6StB.js", "imports": ["/assets/index-DF_XBInP.js"], "css": [] }, "routes/_app+/exercise+/$exerciseNumber": { "id": "routes/_app+/exercise+/$exerciseNumber", "parentId": "routes/_app+/exercise+/_layout", "path": ":exerciseNumber", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_exerciseNumber-BFTlBdr4.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/index-DBrRQJxF.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/mdx-CRxPouxB.js", "/assets/progress-Co-59mG2.js", "/assets/seo-pBpFCWsy.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber", "parentId": "routes/_app+/exercise+/_layout", "path": ":exerciseNumber/:stepNumber", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_exerciseNumber_._stepNumber-_687iGFh.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js"], "css": [] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber", "path": ":type", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/_layout-BriOqd2R.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/index-BjNhezSK.js", "/assets/index-BczhSZ3e.js", "/assets/index-DBrRQJxF.js", "/assets/error-boundary-BcGxKpte.js", "/assets/nav-chevrons-DYiI8EMU.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/mdx-CRxPouxB.js", "/assets/progress-Co-59mG2.js", "/assets/set-playground-pMKmtPtz.js", "/assets/seo-pBpFCWsy.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/app": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/app", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "path": "app", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/app-wbMCZEiv.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/button-CMkJ8p0a.js", "/assets/loading-Br41_Pbf.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/preview-DZcdG4kw.js"], "css": [] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/index": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/index", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "path": void 0, "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/index-C2yr7Uiu.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/tooltip-6-WS-Xux.js", "/assets/index-BjNhezSK.js", "/assets/index-BczhSZ3e.js", "/assets/request-info-DGnmXtfj.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/accordion-DuE9VejZ.js", "/assets/mdx-CRxPouxB.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/use-event-source-CCGBLG92.js", "/assets/set-playground-pMKmtPtz.js", "/assets/button-CMkJ8p0a.js", "/assets/diff-B6thd_Sf.js", "/assets/error-boundary-BcGxKpte.js", "/assets/discord-C9bVfiZ6.js", "/assets/index-YtpQLUzj.js", "/assets/tests-BeAEgPAw.js", "/assets/preview-DZcdG4kw.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/test": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/test", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout", "path": "test", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test-B6zIK2V6.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/index-BjNhezSK.js", "/assets/index-BczhSZ3e.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/epic-video-bs7WmhbC.js", "/assets/accordion-DuE9VejZ.js", "/assets/use-event-source-CCGBLG92.js", "/assets/set-playground-pMKmtPtz.js", "/assets/tests-BeAEgPAw.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.index": { "id": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber.index", "parentId": "routes/_app+/exercise+/$exerciseNumber_.$stepNumber", "path": void 0, "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_exerciseNumber_._stepNumber.index-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/_app+/exercise+/$exerciseNumber_.finished": { "id": "routes/_app+/exercise+/$exerciseNumber_.finished", "parentId": "routes/_app+/exercise+/_layout", "path": ":exerciseNumber/finished", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_exerciseNumber_.finished-BEfn-nJi.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/index-DBrRQJxF.js", "/assets/nav-chevrons-DYiI8EMU.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/mdx-CRxPouxB.js", "/assets/progress-Co-59mG2.js", "/assets/seo-pBpFCWsy.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/finished": { "id": "routes/_app+/finished", "parentId": "routes/_app+/_layout", "path": "finished", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/finished-C2dgX1d-.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/index-DBrRQJxF.js", "/assets/nav-chevrons-DYiI8EMU.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/mdx-CRxPouxB.js", "/assets/seo-pBpFCWsy.js", "/assets/progress-Co-59mG2.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/index": { "id": "routes/_app+/index", "parentId": "routes/_app+/_layout", "path": void 0, "index": true, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "/assets/index-CuV1bRbu.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/index-DBrRQJxF.js", "/assets/error-boundary-BcGxKpte.js", "/assets/mdx-CRxPouxB.js", "/assets/progress-Co-59mG2.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/_app+/login": { "id": "routes/_app+/login", "parentId": "routes/_app+/_layout", "path": "login", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/login-kjV7hrVt.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/use-event-source-CCGBLG92.js", "/assets/button-CMkJ8p0a.js", "/assets/loading-Br41_Pbf.js", "/assets/product-mjsTrqXs.js"], "css": [] }, "routes/_app+/support": { "id": "routes/_app+/support", "parentId": "routes/_app+/_layout", "path": "support", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/support-B0E_F4Zh.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js"], "css": [] }, "routes/admin+/_layout": { "id": "routes/admin+/_layout", "parentId": "root", "path": "admin", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/_layout-BLJr2x2F.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/pe-CvPIToj6.js", "/assets/tooltip-6-WS-Xux.js", "/assets/progress-Co-59mG2.js"], "css": [] }, "routes/admin+/apps": { "id": "routes/admin+/apps", "parentId": "routes/admin+/_layout", "path": "apps", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/apps-DP2rzg_V.js", "imports": [], "css": [] }, "routes/admin+/cache": { "id": "routes/admin+/cache", "parentId": "routes/admin+/_layout", "path": "cache", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/cache-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/apps": { "id": "routes/apps", "parentId": "root", "path": "apps", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/apps-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/diff": { "id": "routes/diff", "parentId": "root", "path": "diff", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/diff-BEk79KPK.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/tooltip-6-WS-Xux.js", "/assets/index-BjNhezSK.js", "/assets/index-BczhSZ3e.js", "/assets/request-info-DGnmXtfj.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/epic-video-bs7WmhbC.js", "/assets/progress-bar-F8_2mvYp.js", "/assets/accordion-DuE9VejZ.js", "/assets/mdx-CRxPouxB.js", "/assets/revalidation-ws-DcvYvzyj.js", "/assets/diff-B6thd_Sf.js", "/assets/nav-chevrons-DYiI8EMU.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/discord.callback": { "id": "routes/discord.callback", "parentId": "root", "path": "discord/callback", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/discord.callback-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/exercises": { "id": "routes/exercises", "parentId": "root", "path": "exercises", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/exercises-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/launch-editor": { "id": "routes/launch-editor", "parentId": "root", "path": "launch-editor", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/launch-editor-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/login-sse": { "id": "routes/login-sse", "parentId": "root", "path": "login-sse", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/login-sse-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/og": { "id": "routes/og", "parentId": "root", "path": "og", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/og-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/onboarding": { "id": "routes/onboarding", "parentId": "root", "path": "onboarding", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/onboarding-B4Z_yevk.js", "imports": ["/assets/index-DF_XBInP.js", "/assets/components-DZ8XIeZ3.js", "/assets/misc-BE75ioh8.js", "/assets/request-info-DGnmXtfj.js", "/assets/tooltip-6-WS-Xux.js", "/assets/pe-CvPIToj6.js", "/assets/index-BvihEwfB.js", "/assets/loading-Br41_Pbf.js", "/assets/user-Boua6jiU.js", "/assets/workshop-config-Ce9sSc3I.js", "/assets/button-CMkJ8p0a.js", "/assets/epic-video-bs7WmhbC.js"], "css": ["/assets/epic-video-DUnRvy1A.css"] }, "routes/processes": { "id": "routes/processes", "parentId": "root", "path": "processes", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/processes-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/progress": { "id": "routes/progress", "parentId": "root", "path": "progress", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/progress-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/robots[.]txt": { "id": "routes/robots[.]txt", "parentId": "root", "path": "robots.txt", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/robots_._txt-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/set-playground": { "id": "routes/set-playground", "parentId": "root", "path": "set-playground", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/set-playground-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/sitemap[.]xml": { "id": "routes/sitemap[.]xml", "parentId": "root", "path": "sitemap.xml", "index": void 0, "caseSensitive": void 0, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/sitemap_._xml-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/start": { "id": "routes/start", "parentId": "root", "path": "start", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/start-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/test": { "id": "routes/test", "parentId": "root", "path": "test", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/test-l0sNRNKZ.js", "imports": [], "css": [] }, "routes/theme/index": { "id": "routes/theme/index", "parentId": "root", "path": "theme", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/index-DP2rzg_V.js", "imports": [], "css": [] }, "routes/video-player/index": { "id": "routes/video-player/index", "parentId": "root", "path": "video-player", "index": void 0, "caseSensitive": void 0, "hasAction": true, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "/assets/index-l0sNRNKZ.js", "imports": [], "css": [] } }, "url": "/assets/manifest-32ad9742.js", "version": "32ad9742" };
13230
13347
  const mode = "production";
13231
13348
  const assetsBuildDirectory = "build/client";
13232
13349
  const basename = "/";