@epic-web/workshop-app 5.0.1 → 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 (131) 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-Cvtc-qzL.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-yrWoJVFy.js → epic-video-bs7WmhbC.js} +2 -2
  38. package/build/client/assets/{epic-video-yrWoJVFy.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-R-sUGQfT.js → index-CuV1bRbu.js} +2 -2
  52. package/build/client/assets/{index-R-sUGQfT.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-CpyquP9i.js → mdx-CRxPouxB.js} +2 -2
  65. package/build/client/assets/{mdx-CpyquP9i.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-B7TLVi2l.js → onboarding-B4Z_yevk.js} +2 -2
  71. package/build/client/assets/{onboarding-B7TLVi2l.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-DW0yVaNn.js.map → set-playground-pMKmtPtz.js.map} +1 -1
  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-DbZkv25C.js.map → test-B6zIK2V6.js.map} +1 -1
  96. package/build/client/assets/{tests-BR7RFlr5.js → tests-BeAEgPAw.js} +2 -2
  97. package/build/client/assets/{tests-BR7RFlr5.js.map → tests-BeAEgPAw.js.map} +1 -1
  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 +768 -692
  107. package/build/server/index.js.map +1 -1
  108. package/dist/server/index.js +83 -34
  109. package/package.json +4 -4
  110. package/build/client/assets/_exerciseNumber-1JworToC.js +0 -2
  111. package/build/client/assets/_exerciseNumber-1JworToC.js.map +0 -1
  112. package/build/client/assets/_exerciseNumber_.finished-B4D2ZAmj.js +0 -2
  113. package/build/client/assets/_exerciseNumber_.finished-B4D2ZAmj.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-CKnDy_9Z.js +0 -2
  117. package/build/client/assets/_layout-CKnDy_9Z.js.map +0 -1
  118. package/build/client/assets/app-CJ9ElQg6.js +0 -2
  119. package/build/client/assets/diff-Cvtc-qzL.js +0 -2
  120. package/build/client/assets/diff-jgZ_RGra.js +0 -2
  121. package/build/client/assets/diff-jgZ_RGra.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-Ct6I1SZV.js +0 -2
  125. package/build/client/assets/finished-Ct6I1SZV.js.map +0 -1
  126. package/build/client/assets/index-Ccssehd9.js +0 -2
  127. package/build/client/assets/index-Ccssehd9.js.map +0 -1
  128. package/build/client/assets/manifest-f5f44d87.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/test-DbZkv25C.js +0 -2
@@ -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";
@@ -15,14 +14,13 @@ import fsExtra from "fs-extra";
15
14
  import { LRUCache } from "lru-cache";
16
15
  import md5 from "md5-hex";
17
16
  import { z } from "zod";
18
- import fs from "node:fs";
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
- import fs$1 from "fs";
23
+ import fs from "fs";
26
24
  import { remarkCodeBlocksShiki } from "@kentcdodds/md-temp";
27
25
  import { bundleMDX } from "mdx-bundler";
28
26
  import PQueue from "p-queue";
@@ -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,7 +600,180 @@ function uniqueUsers(users) {
598
600
  return true;
599
601
  });
600
602
  }
603
+ function trimCodeBlocks() {
604
+ return async function transformer(tree) {
605
+ visit(tree, "element", (preNode) => {
606
+ if (preNode.tagName !== "pre" || !preNode.children.length) {
607
+ return;
608
+ }
609
+ const codeNode = preNode.children[0];
610
+ if (!codeNode || codeNode.type !== "element" || codeNode.tagName !== "code") {
611
+ return;
612
+ }
613
+ const [codeStringNode] = codeNode.children;
614
+ if (!codeStringNode) return;
615
+ if (codeStringNode.type !== "text") {
616
+ console.warn(
617
+ `trimCodeBlocks: Unexpected: codeStringNode type is not "text": ${codeStringNode.type}`
618
+ );
619
+ return;
620
+ }
621
+ codeStringNode.value = codeStringNode.value.trimEnd();
622
+ });
623
+ };
624
+ }
625
+ function removePreContainerDivs() {
626
+ return async function preContainerDivsTransformer(tree) {
627
+ visit(
628
+ tree,
629
+ { type: "element", tagName: "pre" },
630
+ function visitor(node, index, parent) {
631
+ if ((parent == null ? void 0 : parent.type) !== "element") return;
632
+ if (parent.tagName !== "div") return;
633
+ if (parent.children.length !== 1 && index === 0) return;
634
+ Object.assign(parent, node);
635
+ }
636
+ );
637
+ };
638
+ }
639
+ const rehypePlugins = [
640
+ trimCodeBlocks,
641
+ remarkCodeBlocksShiki,
642
+ removePreContainerDivs
643
+ ];
644
+ const verboseLog = process.env.EPICSHOP_VERBOSE_LOG === "true" ? console.log : () => {
645
+ };
646
+ async function compileMdx(file, {
647
+ request,
648
+ timings,
649
+ forceFresh
650
+ } = {}) {
651
+ const stat = await fs.promises.stat(file).catch((error) => ({ error }));
652
+ if ("error" in stat) {
653
+ throw new Error(`File stat cannot be read: ${stat.error}`);
654
+ }
655
+ const key = `file:${file}`;
656
+ forceFresh = await shouldForceFresh({ forceFresh, request, key });
657
+ const existingCacheEntry = await compiledInstructionMarkdownCache.get(key);
658
+ if (!forceFresh && existingCacheEntry) {
659
+ forceFresh = stat.mtimeMs > existingCacheEntry.metadata.createdTime;
660
+ }
661
+ return cachified({
662
+ key,
663
+ cache: compiledInstructionMarkdownCache,
664
+ request,
665
+ timings,
666
+ forceFresh,
667
+ getFreshValue: () => compileMdxImpl(file)
668
+ });
669
+ }
670
+ async function compileMdxImpl(file) {
671
+ let title = null;
672
+ const epicVideoEmbeds = [];
673
+ try {
674
+ verboseLog(`Compiling ${file}`);
675
+ const bundleResult = await queuedBundleMDX({
676
+ file,
677
+ cwd: path.dirname(file),
678
+ mdxOptions(options) {
679
+ options.remarkPlugins = [
680
+ ...options.remarkPlugins ?? [],
681
+ [remarkAutolinkHeadings, { behavior: "wrap" }],
682
+ gfm,
683
+ () => (tree) => {
684
+ visit(tree, "heading", (node) => {
685
+ if (title) return;
686
+ if (node.depth === 1) {
687
+ visit(node, "text", (textNode) => {
688
+ title = textNode.value.trim();
689
+ });
690
+ }
691
+ });
692
+ title = title ? title.replace(/^\d+\. /, "").trim() : null;
693
+ },
694
+ () => (tree) => {
695
+ visit(tree, "mdxJsxFlowElement", (jsxEl) => {
696
+ if (jsxEl.name !== "EpicVideo") return;
697
+ const urlAttr = jsxEl.attributes.find(
698
+ // @ts-expect-error no idea why this started being an issue suddenly 🤷‍♂️
699
+ (a) => a.type === "mdxJsxAttribute" && a.name === "url"
700
+ );
701
+ if (!urlAttr) return;
702
+ let url = urlAttr.value;
703
+ if (typeof url !== "string") return;
704
+ if (url.endsWith("/")) url = url.slice(0, -1);
705
+ epicVideoEmbeds.push(url);
706
+ });
707
+ },
708
+ emoji
709
+ ];
710
+ options.rehypePlugins = [
711
+ ...options.rehypePlugins ?? [],
712
+ ...rehypePlugins
713
+ ];
714
+ options.mdxExtensions = [".mdx", ".md"];
715
+ options.format = "mdx";
716
+ options.development = false;
717
+ return options;
718
+ }
719
+ });
720
+ if (!bundleResult) throw new Error(`Timeout for file: ${file}`);
721
+ const result = { code: bundleResult.code, title, epicVideoEmbeds };
722
+ return result;
723
+ } catch (error) {
724
+ console.error(`Compilation error for file: `, file, error);
725
+ throw error;
726
+ } finally {
727
+ verboseLog(`Successfully compiled ${file}`);
728
+ }
729
+ }
730
+ async function compileMarkdownString(markdownString) {
731
+ return cachified({
732
+ key: markdownString,
733
+ cache: compiledMarkdownCache,
734
+ ttl: 1e3 * 60 * 60 * 24,
735
+ getFreshValue: async () => {
736
+ try {
737
+ verboseLog(`Compiling string`, markdownString);
738
+ const result = await queuedBundleMDX({
739
+ source: markdownString,
740
+ mdxOptions(options) {
741
+ options.rehypePlugins = [
742
+ ...options.rehypePlugins ?? [],
743
+ ...rehypePlugins
744
+ ];
745
+ options.development = false;
746
+ return options;
747
+ }
748
+ });
749
+ if (!result) throw new Error(`Timed out compiling markdown string`);
750
+ return result.code;
751
+ } catch (error) {
752
+ console.error(`Compilation error for code: `, markdownString, error);
753
+ throw error;
754
+ } finally {
755
+ verboseLog(`Successfully compiled string`, markdownString);
756
+ }
757
+ }
758
+ });
759
+ }
760
+ let _queue$1 = null;
761
+ async function getQueue$1() {
762
+ if (_queue$1) return _queue$1;
763
+ _queue$1 = new PQueue({
764
+ concurrency: 1,
765
+ throwOnTimeout: true,
766
+ timeout: 1e3 * 60
767
+ });
768
+ return _queue$1;
769
+ }
770
+ async function queuedBundleMDX(...args) {
771
+ const queue = await getQueue$1();
772
+ const result = await queue.add(() => bundleMDX(...args));
773
+ return result;
774
+ }
601
775
  const workshopRoot$1 = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd();
776
+ const rootPkgJson = path$1.join(workshopRoot$1, "package.json");
602
777
  const StackBlitzConfigSchema = z.object({
603
778
  // we default this to `${exerciseTitle} (${type})`
604
779
  title: z.string().optional(),
@@ -663,16 +838,18 @@ const WorkshopConfigSchema = z.object({
663
838
  }
664
839
  };
665
840
  });
666
- let cachedConfig = null;
667
- function bustWorkshopConfigCache() {
668
- cachedConfig = null;
669
- }
841
+ const configCache = {
842
+ config: null,
843
+ modified: 0
844
+ };
670
845
  function getWorkshopConfig() {
671
- if (cachedConfig) return cachedConfig;
846
+ if (configCache.config && configCache.modified > fs$1.statSync(rootPkgJson).mtimeMs) {
847
+ return configCache.config;
848
+ }
672
849
  const packageJsonPath = path$1.join(workshopRoot$1, "package.json");
673
850
  let packageJson;
674
851
  try {
675
- const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
852
+ const packageJsonContent = fs$1.readFileSync(packageJsonPath, "utf8");
676
853
  packageJson = JSON.parse(packageJsonContent);
677
854
  } catch (error) {
678
855
  console.error(`Error reading or parsing package.json:`, error);
@@ -705,7 +882,8 @@ function getWorkshopConfig() {
705
882
  }
706
883
  try {
707
884
  const parsedConfig = WorkshopConfigSchema.parse(epicshopConfig);
708
- cachedConfig = parsedConfig;
885
+ configCache.config = parsedConfig;
886
+ configCache.modified = fs$1.statSync(rootPkgJson).mtimeMs;
709
887
  return parsedConfig;
710
888
  } catch (error) {
711
889
  if (error instanceof z.ZodError) {
@@ -724,7 +902,7 @@ async function getStackBlitzUrl({
724
902
  title,
725
903
  type
726
904
  }) {
727
- var _a2;
905
+ var _a;
728
906
  const workshopConfig = getWorkshopConfig();
729
907
  const appConfig = await getAppConfig(fullPath);
730
908
  if (appConfig.stackBlitzConfig === null) return null;
@@ -735,7 +913,7 @@ async function getStackBlitzUrl({
735
913
  const githubPart = githubRootUrl.pathname;
736
914
  const stackBlitzConfig = {
737
915
  ...appConfig.stackBlitzConfig,
738
- title: ((_a2 = appConfig.stackBlitzConfig) == null ? void 0 : _a2.title) ?? `${title} (${type})`
916
+ title: ((_a = appConfig.stackBlitzConfig) == null ? void 0 : _a.title) ?? `${title} (${type})`
739
917
  };
740
918
  const params = new URLSearchParams(stackBlitzConfig);
741
919
  const relativePath = fullPath.replace(`${workshopRoot$1}${path$1.sep}`, "");
@@ -746,15 +924,15 @@ async function getStackBlitzUrl({
746
924
  return stackBlitzUrl.toString();
747
925
  }
748
926
  async function getAppConfig(fullPath) {
749
- var _a2, _b;
927
+ var _a, _b;
750
928
  const workshopConfig = getWorkshopConfig();
751
929
  let epicshopConfig = {};
752
930
  let scripts = {};
753
931
  const packageJsonPath = path$1.join(fullPath, "package.json");
754
- const packageJsonExists = await fs.promises.access(packageJsonPath, fs.constants.F_OK).then(() => true).catch(() => false);
932
+ const packageJsonExists = await fs$1.promises.access(packageJsonPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
755
933
  if (packageJsonExists) {
756
934
  const pkg = JSON.parse(
757
- await fs.promises.readFile(path$1.join(fullPath, "package.json"), "utf8")
935
+ await fs$1.promises.readFile(path$1.join(fullPath, "package.json"), "utf8")
758
936
  );
759
937
  epicshopConfig = pkg.epicshop ?? {};
760
938
  scripts = pkg.scripts ?? {};
@@ -768,7 +946,7 @@ async function getAppConfig(fullPath) {
768
946
  };
769
947
  }),
770
948
  testTab: z.object({
771
- 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)
772
950
  }).default({}),
773
951
  scripts: z.object({
774
952
  test: z.string().optional(),
@@ -790,251 +968,16 @@ async function getAppConfig(fullPath) {
790
968
  try {
791
969
  return AppConfigSchema.parse(appConfig);
792
970
  } catch (error) {
793
- if (error instanceof z.ZodError) {
794
- const flattenedErrors = error.flatten();
795
- const errorMessages = Object.entries(flattenedErrors.fieldErrors).map(([field, errors]) => `${field}: ${errors == null ? void 0 : errors.join(", ")}`).concat(flattenedErrors.formErrors);
796
- throw new Error(
797
- `Invalid app configuration for ${fullPath}:
798
- ${errorMessages.join("\n")}`
799
- );
800
- }
801
- throw error;
802
- }
803
- }
804
- let watcher = global.__change_tracker_watcher__;
805
- const dirsToWatch = [
806
- path.join(workshopRoot$1, "playground"),
807
- path.join(workshopRoot$1, "exercises"),
808
- path.join(workshopRoot$1, "examples")
809
- ];
810
- const ignoredDirs = [
811
- "/.git",
812
- "/node_modules",
813
- "/build",
814
- "/server-build",
815
- "/public/build",
816
- "/playwright-report",
817
- "/dist",
818
- "/.cache"
819
- ];
820
- function getWatcher() {
821
- if (process.env.EPICSHOP_DEPLOYED ?? process.env.EPICSHOP_ENABLE_WATCHER !== "true") {
822
- return void 0;
823
- }
824
- if (watcher) return watcher;
825
- watcher = chokidar.watch(dirsToWatch, {
826
- ignoreInitial: true,
827
- ignored(path2, stat) {
828
- return (stat == null ? void 0 : stat.isDirectory()) ? ignoredDirs.some((dir) => path2.endsWith(dir)) : false;
829
- }
830
- });
831
- global.__change_tracker_watcher__ = watcher;
832
- return watcher;
833
- }
834
- let currentWithoutWatcher = null;
835
- async function withoutWatcher(fn) {
836
- if (!watcher) return fn();
837
- let thisWithoutWatcher = currentWithoutWatcher = Symbol("withoutWatcher");
838
- const eventNames = watcher.eventNames();
839
- const eventNamesToListenersMap = {};
840
- for (const eventName of eventNames) {
841
- if (typeof eventName === "string") {
842
- eventNamesToListenersMap[eventName] = watcher.listeners(eventName);
843
- }
844
- }
845
- watcher.removeAllListeners();
846
- try {
847
- const result = await fn();
848
- return result;
849
- } finally {
850
- if (currentWithoutWatcher === thisWithoutWatcher) {
851
- await new Promise((r) => setTimeout(r, 100));
852
- for (const eventName of eventNames) {
853
- if (typeof eventName === "string") {
854
- const listeners = eventNamesToListenersMap[eventName] || [];
855
- for (const listener of listeners) {
856
- watcher.on(eventName, listener);
857
- }
858
- }
859
- }
860
- }
861
- }
862
- }
863
- (_a = global.__change_tracker_close_with_grace_return__) == null ? void 0 : _a.uninstall();
864
- global.__change_tracker_close_with_grace_return__ = closeWithGrace(
865
- () => watcher == null ? void 0 : watcher.close()
866
- );
867
- function trimCodeBlocks() {
868
- return async function transformer(tree) {
869
- visit(tree, "element", (preNode) => {
870
- if (preNode.tagName !== "pre" || !preNode.children.length) {
871
- return;
872
- }
873
- const codeNode = preNode.children[0];
874
- if (!codeNode || codeNode.type !== "element" || codeNode.tagName !== "code") {
875
- return;
876
- }
877
- const [codeStringNode] = codeNode.children;
878
- if (!codeStringNode) return;
879
- if (codeStringNode.type !== "text") {
880
- console.warn(
881
- `trimCodeBlocks: Unexpected: codeStringNode type is not "text": ${codeStringNode.type}`
882
- );
883
- return;
884
- }
885
- codeStringNode.value = codeStringNode.value.trimEnd();
886
- });
887
- };
888
- }
889
- function removePreContainerDivs() {
890
- return async function preContainerDivsTransformer(tree) {
891
- visit(
892
- tree,
893
- { type: "element", tagName: "pre" },
894
- function visitor(node, index, parent) {
895
- if ((parent == null ? void 0 : parent.type) !== "element") return;
896
- if (parent.tagName !== "div") return;
897
- if (parent.children.length !== 1 && index === 0) return;
898
- Object.assign(parent, node);
899
- }
900
- );
901
- };
902
- }
903
- const rehypePlugins = [
904
- trimCodeBlocks,
905
- remarkCodeBlocksShiki,
906
- removePreContainerDivs
907
- ];
908
- const verboseLog = process.env.EPICSHOP_VERBOSE_LOG === "true" ? console.log : () => {
909
- };
910
- async function compileMdx(file, {
911
- request,
912
- timings,
913
- forceFresh
914
- } = {}) {
915
- const stat = await fs$1.promises.stat(file).catch((error) => ({ error }));
916
- if ("error" in stat) {
917
- throw new Error(`File stat cannot be read: ${stat.error}`);
918
- }
919
- const key = `file:${file}`;
920
- forceFresh = await shouldForceFresh({ forceFresh, request, key });
921
- const existingCacheEntry = await compiledInstructionMarkdownCache.get(key);
922
- if (!forceFresh && existingCacheEntry) {
923
- forceFresh = stat.mtimeMs > existingCacheEntry.metadata.createdTime;
924
- }
925
- return cachified({
926
- key,
927
- cache: compiledInstructionMarkdownCache,
928
- request,
929
- timings,
930
- forceFresh,
931
- getFreshValue: () => compileMdxImpl(file)
932
- });
933
- }
934
- async function compileMdxImpl(file) {
935
- let title = null;
936
- const epicVideoEmbeds = [];
937
- try {
938
- verboseLog(`Compiling ${file}`);
939
- const bundleResult = await queuedBundleMDX({
940
- file,
941
- cwd: path.dirname(file),
942
- mdxOptions(options) {
943
- options.remarkPlugins = [
944
- ...options.remarkPlugins ?? [],
945
- [remarkAutolinkHeadings, { behavior: "wrap" }],
946
- gfm,
947
- () => (tree) => {
948
- visit(tree, "heading", (node) => {
949
- if (title) return;
950
- if (node.depth === 1) {
951
- visit(node, "text", (textNode) => {
952
- title = textNode.value.trim();
953
- });
954
- }
955
- });
956
- title = title ? title.replace(/^\d+\. /, "").trim() : null;
957
- },
958
- () => (tree) => {
959
- visit(tree, "mdxJsxFlowElement", (jsxEl) => {
960
- if (jsxEl.name !== "EpicVideo") return;
961
- const urlAttr = jsxEl.attributes.find(
962
- // @ts-expect-error no idea why this started being an issue suddenly 🤷‍♂️
963
- (a) => a.type === "mdxJsxAttribute" && a.name === "url"
964
- );
965
- if (!urlAttr) return;
966
- let url = urlAttr.value;
967
- if (typeof url !== "string") return;
968
- if (url.endsWith("/")) url = url.slice(0, -1);
969
- epicVideoEmbeds.push(url);
970
- });
971
- },
972
- emoji
973
- ];
974
- options.rehypePlugins = [
975
- ...options.rehypePlugins ?? [],
976
- ...rehypePlugins
977
- ];
978
- options.mdxExtensions = [".mdx", ".md"];
979
- options.format = "mdx";
980
- options.development = false;
981
- return options;
982
- }
983
- });
984
- if (!bundleResult) throw new Error(`Timeout for file: ${file}`);
985
- const result = { code: bundleResult.code, title, epicVideoEmbeds };
986
- return result;
987
- } catch (error) {
988
- console.error(`Compilation error for file: `, file, error);
989
- throw error;
990
- } finally {
991
- verboseLog(`Successfully compiled ${file}`);
992
- }
993
- }
994
- async function compileMarkdownString(markdownString) {
995
- return cachified({
996
- key: markdownString,
997
- cache: compiledMarkdownCache,
998
- ttl: 1e3 * 60 * 60 * 24,
999
- getFreshValue: async () => {
1000
- try {
1001
- verboseLog(`Compiling string`, markdownString);
1002
- const result = await queuedBundleMDX({
1003
- source: markdownString,
1004
- mdxOptions(options) {
1005
- options.rehypePlugins = [
1006
- ...options.rehypePlugins ?? [],
1007
- ...rehypePlugins
1008
- ];
1009
- options.development = false;
1010
- return options;
1011
- }
1012
- });
1013
- if (!result) throw new Error(`Timed out compiling markdown string`);
1014
- return result.code;
1015
- } catch (error) {
1016
- console.error(`Compilation error for code: `, markdownString, error);
1017
- throw error;
1018
- } finally {
1019
- verboseLog(`Successfully compiled string`, markdownString);
1020
- }
971
+ if (error instanceof z.ZodError) {
972
+ const flattenedErrors = error.flatten();
973
+ const errorMessages = Object.entries(flattenedErrors.fieldErrors).map(([field, errors]) => `${field}: ${errors == null ? void 0 : errors.join(", ")}`).concat(flattenedErrors.formErrors);
974
+ throw new Error(
975
+ `Invalid app configuration for ${fullPath}:
976
+ ${errorMessages.join("\n")}`
977
+ );
1021
978
  }
1022
- });
1023
- }
1024
- let _queue = null;
1025
- async function getQueue() {
1026
- if (_queue) return _queue;
1027
- _queue = new PQueue({
1028
- concurrency: 1,
1029
- throwOnTimeout: true,
1030
- timeout: 1e3 * 60
1031
- });
1032
- return _queue;
1033
- }
1034
- async function queuedBundleMDX(...args) {
1035
- const queue = await getQueue();
1036
- const result = await queue.add(() => bundleMDX(...args));
1037
- return result;
979
+ throw error;
980
+ }
1038
981
  }
1039
982
  const schema = z.object({
1040
983
  NODE_ENV: z.enum(["production", "development", "test"]).default("development"),
@@ -1061,6 +1004,64 @@ function getEnv() {
1061
1004
  EPICSHOP_DEPLOYED: process.env.EPICSHOP_DEPLOYED === "true" || process.env.EPICSHOP_DEPLOYED === "1"
1062
1005
  };
1063
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
+ }
1064
1065
  function getErrorMessage$1(error) {
1065
1066
  if (typeof error === "string") return error;
1066
1067
  if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
@@ -1073,9 +1074,9 @@ const isDeployed$1 = process.env.EPICSHOP_DEPLOYED === "true" || process.env.EPI
1073
1074
  const devProcesses = remember("dev_processes", getDevProcessesMap);
1074
1075
  const testProcesses = remember("test_processes", getTestProcessesMap);
1075
1076
  function getDevProcessesMap() {
1076
- var _a2;
1077
+ var _a;
1077
1078
  const procs = /* @__PURE__ */ new Map();
1078
- (_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();
1079
1080
  global.__process_dev_close_with_grace_return__ = closeWithGrace(async () => {
1080
1081
  for (const [name, proc] of procs.entries()) {
1081
1082
  console.log("closing", name);
@@ -1085,9 +1086,9 @@ function getDevProcessesMap() {
1085
1086
  return procs;
1086
1087
  }
1087
1088
  function getTestProcessesMap() {
1088
- var _a2;
1089
+ var _a;
1089
1090
  const procs = /* @__PURE__ */ new Map();
1090
- (_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();
1091
1092
  global.__process_test_close_with_grace_return__ = closeWithGrace(async () => {
1092
1093
  for (const [id, proc] of procs.entries()) {
1093
1094
  if (proc.process) {
@@ -1316,7 +1317,7 @@ async function waitForPortToBeAvailable(port2) {
1316
1317
  console.error("Timed out waiting for the port to become available");
1317
1318
  }
1318
1319
  }
1319
- let initialized$1 = false;
1320
+ global.__epicshop_apps_initialized__ ?? (global.__epicshop_apps_initialized__ = false);
1320
1321
  const workshopRoot = process.env.EPICSHOP_CONTEXT_CWD = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd();
1321
1322
  const playgroundAppNameInfoPath = path$1.join(
1322
1323
  workshopRoot,
@@ -1434,7 +1435,7 @@ function isExerciseStepApp(app) {
1434
1435
  return isProblemApp(app) || isSolutionApp(app);
1435
1436
  }
1436
1437
  function exists(file) {
1437
- return fs.promises.access(file, fs.constants.F_OK).then(
1438
+ return fs$1.promises.access(file, fs$1.constants.F_OK).then(
1438
1439
  () => true,
1439
1440
  () => false
1440
1441
  );
@@ -1448,27 +1449,39 @@ const modifiedTimes = remember(
1448
1449
  "modified_times",
1449
1450
  () => /* @__PURE__ */ new Map()
1450
1451
  );
1451
- function init() {
1452
- var _a2;
1453
- if (initialized$1) return;
1454
- initialized$1 = true;
1452
+ async function init() {
1453
+ if (global.__epicshop_apps_initialized__) return;
1454
+ global.__epicshop_apps_initialized__ = true;
1455
1455
  const config = getWorkshopConfig();
1456
1456
  process.env.EPICSHOP_GITHUB_REPO = config.githubRepo;
1457
1457
  process.env.EPICSHOP_GITHUB_ROOT = config.githubRoot;
1458
1458
  init$1();
1459
1459
  global.ENV = getEnv();
1460
- (_a2 = getWatcher()) == null ? void 0 : _a2.on("all", handleFileChanges);
1461
- }
1462
- async function handleFileChanges(event, filePath) {
1463
- if (filePath === path$1.join(workshopRoot, "package.json")) {
1464
- bustWorkshopConfigCache();
1465
- }
1466
- const apps = await getApps();
1467
- for (const app of apps) {
1468
- if (filePath.startsWith(app.fullPath)) {
1469
- modifiedTimes.set(app.fullPath, Date.now());
1470
- break;
1471
- }
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;
1479
+ }
1480
+ });
1481
+ chok.on("all", (_event, filePath) => {
1482
+ setModifiedTimesForAppDirs(path$1.join(workshopRoot, filePath));
1483
+ });
1484
+ closeWithGrace(() => chok.close());
1472
1485
  }
1473
1486
  }
1474
1487
  function getForceFresh$1(cacheEntry) {
@@ -1477,6 +1490,17 @@ function getForceFresh$1(cacheEntry) {
1477
1490
  if (!latestModifiedTime) return void 0;
1478
1491
  return latestModifiedTime > cacheEntry.metadata.createdTime ? true : void 0;
1479
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
+ }
1480
1504
  function getForceFreshForDir(cacheEntry, ...dirs) {
1481
1505
  const truthyDirs = dirs.filter(Boolean);
1482
1506
  for (const d of truthyDirs) {
@@ -1494,7 +1518,7 @@ function getForceFreshForDir(cacheEntry, ...dirs) {
1494
1518
  }
1495
1519
  async function readDir(dir) {
1496
1520
  if (await exists(dir)) {
1497
- return fs.promises.readdir(dir);
1521
+ return fs$1.promises.readdir(dir);
1498
1522
  }
1499
1523
  return [];
1500
1524
  }
@@ -1530,9 +1554,9 @@ function getAppDirInfo(appDir2) {
1530
1554
  return { stepNumber, type, subtitle };
1531
1555
  }
1532
1556
  function extractExerciseNumber(dir) {
1533
- var _a2, _b;
1557
+ var _a, _b;
1534
1558
  const regex = /^(?<number>\d+)\./;
1535
- 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;
1536
1560
  if (!number) {
1537
1561
  return null;
1538
1562
  }
@@ -1586,7 +1610,7 @@ async function getApps({
1586
1610
  request,
1587
1611
  forceFresh
1588
1612
  } = {}) {
1589
- if (!initialized$1) init();
1613
+ await init();
1590
1614
  const key = "apps";
1591
1615
  const apps = await cachified({
1592
1616
  key,
@@ -1599,10 +1623,24 @@ async function getApps({
1599
1623
  ttl: 1e3 * 60 * 60 * 24,
1600
1624
  forceFresh: forceFresh ?? getForceFresh$1(appsCache.get(key)),
1601
1625
  getFreshValue: async () => {
1602
- const playgroundApp = await getPlaygroundApp({ request, timings });
1603
- const problemApps = await getProblemApps({ request, timings });
1604
- const solutionApps = await getSolutionApps({ request, timings });
1605
- 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
+ ]);
1606
1644
  const sortedApps = [
1607
1645
  playgroundApp,
1608
1646
  ...problemApps,
@@ -1654,7 +1692,7 @@ const AppIdInfoSchema = z.object({
1654
1692
  type: z.union([z.literal("problem"), z.literal("solution")])
1655
1693
  });
1656
1694
  function extractNumbersAndTypeFromAppNameOrPath(fullPathOrAppName) {
1657
- var _a2;
1695
+ var _a;
1658
1696
  const info = {};
1659
1697
  if (fullPathOrAppName.includes(path$1.sep)) {
1660
1698
  const relativePath = fullPathOrAppName.replace(
@@ -1665,7 +1703,7 @@ function extractNumbersAndTypeFromAppNameOrPath(fullPathOrAppName) {
1665
1703
  if (!exerciseNumberPart || !stepNumberPart) return null;
1666
1704
  const exerciseNumber = exerciseNumberPart.split(".")[0];
1667
1705
  const stepNumber = stepNumberPart.split(".")[0];
1668
- 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];
1669
1707
  info.exerciseNumber = exerciseNumber;
1670
1708
  info.stepNumber = stepNumber;
1671
1709
  info.type = type;
@@ -1681,18 +1719,28 @@ function extractNumbersAndTypeFromAppNameOrPath(fullPathOrAppName) {
1681
1719
  }
1682
1720
  async function getProblemDirs() {
1683
1721
  const exercisesDir = path$1.join(workshopRoot, "exercises");
1684
- const problemDirs = (await glob("**/*.problem*", {
1685
- cwd: exercisesDir,
1686
- ignore: "node_modules/**"
1687
- })).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
+ }
1688
1731
  return problemDirs;
1689
1732
  }
1690
1733
  async function getSolutionDirs() {
1691
1734
  const exercisesDir = path$1.join(workshopRoot, "exercises");
1692
- const solutionDirs = (await glob("**/*.solution*", {
1693
- cwd: exercisesDir,
1694
- ignore: "node_modules/**"
1695
- })).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
+ }
1696
1744
  return solutionDirs;
1697
1745
  }
1698
1746
  function getPathname(fullPath) {
@@ -1746,7 +1794,7 @@ async function findSolutionDir({
1746
1794
  const { stepNumber } = info;
1747
1795
  const paddedStepNumber = stepNumber.toString().padStart(2, "0");
1748
1796
  const parentDir = path$1.dirname(fullPath);
1749
- const siblingDirs = await fs.promises.readdir(parentDir);
1797
+ const siblingDirs = await fs$1.promises.readdir(parentDir);
1750
1798
  const solutionDir = siblingDirs.find(
1751
1799
  (dir) => dir.startsWith(`${paddedStepNumber}.solution`)
1752
1800
  );
@@ -1773,7 +1821,7 @@ async function findProblemDir({
1773
1821
  const { stepNumber } = info;
1774
1822
  const paddedStepNumber = stepNumber.toString().padStart(2, "0");
1775
1823
  const parentDir = path$1.dirname(fullPath);
1776
- const siblingDirs = await fs.promises.readdir(parentDir);
1824
+ const siblingDirs = await fs$1.promises.readdir(parentDir);
1777
1825
  const problemDir = siblingDirs.find(
1778
1826
  (dir) => dir.endsWith("problem") && dir.includes(paddedStepNumber)
1779
1827
  );
@@ -1800,7 +1848,7 @@ async function getTestInfo({
1800
1848
  return { type: "script", script: testScript };
1801
1849
  }
1802
1850
  const testAppFullPath = await findSolutionDir({ fullPath }) ?? fullPath;
1803
- const dirList = await fs.promises.readdir(testAppFullPath);
1851
+ const dirList = await fs$1.promises.readdir(testAppFullPath);
1804
1852
  const testFiles = dirList.filter((item) => item.includes(".test."));
1805
1853
  if (testFiles.length) {
1806
1854
  return {
@@ -1864,10 +1912,10 @@ async function getPlaygroundApp({
1864
1912
  getTestInfo({ fullPath: playgroundDir }),
1865
1913
  getDevInfo({ fullPath: playgroundDir, portNumber })
1866
1914
  ]);
1867
- const appModifiedTime = await getDirModifiedTime(
1915
+ const appModifiedTime = await queuedGetDirModifiedTime(
1868
1916
  await getFullPathFromAppName(baseAppName)
1869
1917
  );
1870
- const playgroundAppModifiedTime = await getDirModifiedTime(playgroundDir);
1918
+ const playgroundAppModifiedTime = await queuedGetDirModifiedTime(playgroundDir);
1871
1919
  const type = "playground";
1872
1920
  const title = (compiledReadme == null ? void 0 : compiledReadme.title) ?? name;
1873
1921
  return {
@@ -1928,7 +1976,9 @@ async function getExampleApps({
1928
1976
  request
1929
1977
  } = {}) {
1930
1978
  const examplesDir = path$1.join(workshopRoot, "examples");
1931
- 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
+ );
1932
1982
  const exampleApps = [];
1933
1983
  for (const exampleDir of exampleDirs) {
1934
1984
  const index = exampleDirs.indexOf(exampleDir);
@@ -1941,10 +1991,14 @@ async function getExampleApps({
1941
1991
  timingKey: exampleDir.replace(`${examplesDir}${path$1.sep}`, ""),
1942
1992
  request,
1943
1993
  forceFresh: getForceFreshForDir(exampleAppCache.get(key), exampleDir),
1944
- getFreshValue: () => getExampleAppFromPath(exampleDir, index, request).catch((error) => {
1945
- console.error(error);
1946
- return null;
1947
- })
1994
+ getFreshValue: async () => {
1995
+ return getExampleAppFromPath(exampleDir, index, request).catch(
1996
+ (error) => {
1997
+ console.error(error);
1998
+ return null;
1999
+ }
2000
+ );
2001
+ }
1948
2002
  });
1949
2003
  if (exampleApp) exampleApps.push(exampleApp);
1950
2004
  }
@@ -2013,10 +2067,12 @@ async function getSolutionApps({
2013
2067
  solutionAppCache.get(solutionDir),
2014
2068
  solutionDir
2015
2069
  ),
2016
- getFreshValue: () => getSolutionAppFromPath(solutionDir, request).catch((error) => {
2017
- console.error(error);
2018
- return null;
2019
- })
2070
+ getFreshValue: async () => {
2071
+ return getSolutionAppFromPath(solutionDir, request).catch((error) => {
2072
+ console.error(error);
2073
+ return null;
2074
+ });
2075
+ }
2020
2076
  });
2021
2077
  if (solutionApp) solutionApps.push(solutionApp);
2022
2078
  }
@@ -2087,10 +2143,12 @@ async function getProblemApps({
2087
2143
  problemDir,
2088
2144
  solutionDir
2089
2145
  ),
2090
- getFreshValue: () => getProblemAppFromPath(problemDir).catch((error) => {
2091
- console.error(error);
2092
- return null;
2093
- })
2146
+ getFreshValue: async () => {
2147
+ return getProblemAppFromPath(problemDir).catch((error) => {
2148
+ console.error(error);
2149
+ return null;
2150
+ });
2151
+ }
2094
2152
  });
2095
2153
  if (problemApp) problemApps.push(problemApp);
2096
2154
  }
@@ -2180,111 +2238,107 @@ async function getAppFromFile(filePath) {
2180
2238
  return apps.find((app) => filePath.startsWith(app.fullPath));
2181
2239
  }
2182
2240
  async function setPlayground(srcDir, { reset } = {}) {
2183
- var _a2;
2184
2241
  const destDir = path$1.join(workshopRoot, "playground");
2185
- await withoutWatcher(async () => {
2186
- const isIgnored = await isGitIgnored({ cwd: srcDir });
2187
- const playgroundApp = await getAppByName("playground");
2188
- const playgroundWasRunning = playgroundApp ? isAppRunning(playgroundApp) : false;
2189
- if (playgroundApp && reset) {
2190
- await closeProcess(playgroundApp.name);
2191
- await fsExtra.remove(destDir);
2192
- }
2193
- const setPlaygroundTimestamp = Date.now();
2194
- const preSetPlaygroundPath = await firstToExist(
2195
- path$1.join(srcDir, "epicshop", "pre-set-playground.js"),
2196
- path$1.join(workshopRoot, "epicshop", "pre-set-playground.js")
2197
- );
2198
- if (preSetPlaygroundPath) {
2199
- await execa("node", [preSetPlaygroundPath], {
2200
- cwd: workshopRoot,
2201
- stdio: "inherit",
2202
- env: {
2203
- EPICSHOP_PLAYGROUND_TIMESTAMP: setPlaygroundTimestamp.toString(),
2204
- EPICSHOP_PLAYGROUND_DEST_DIR: destDir,
2205
- EPICSHOP_PLAYGROUND_SRC_DIR: srcDir,
2206
- EPICSHOP_PLAYGROUND_WAS_RUNNING: playgroundWasRunning.toString()
2207
- }
2208
- });
2209
- }
2210
- const basename2 = path$1.basename(srcDir);
2211
- await fsExtra.remove(path$1.join(destDir, "node_modules"));
2212
- await fsExtra.copy(srcDir, destDir, {
2213
- filter: async (srcFile, destFile) => {
2214
- if (srcFile.includes(`${basename2}${path$1.sep}build`) || srcFile.includes(`${basename2}${path$1.sep}public${path$1.sep}build`)) {
2215
- return false;
2216
- }
2217
- if (srcFile === srcDir) return true;
2218
- if (srcFile.includes("node_modules")) return true;
2219
- if (srcFile.endsWith(".env")) return true;
2220
- if (isIgnored(srcFile)) return false;
2221
- try {
2222
- const isDir = (await fsExtra.stat(srcFile)).isDirectory();
2223
- if (isDir) return true;
2224
- const destIsDir = (await fsExtra.stat(destFile)).isDirectory();
2225
- if (destIsDir) return true;
2226
- const currentContents = await fsExtra.readFile(destFile);
2227
- const newContents = await fsExtra.readFile(srcFile);
2228
- if (currentContents.equals(newContents)) return false;
2229
- return true;
2230
- } catch {
2231
- return true;
2232
- }
2242
+ const isIgnored = await isGitIgnored({ cwd: srcDir });
2243
+ const playgroundApp = await getAppByName("playground");
2244
+ const playgroundWasRunning = playgroundApp ? isAppRunning(playgroundApp) : false;
2245
+ if (playgroundApp && reset) {
2246
+ await closeProcess(playgroundApp.name);
2247
+ await fsExtra.remove(destDir);
2248
+ }
2249
+ const setPlaygroundTimestamp = Date.now();
2250
+ const preSetPlaygroundPath = await firstToExist(
2251
+ path$1.join(srcDir, "epicshop", "pre-set-playground.js"),
2252
+ path$1.join(workshopRoot, "epicshop", "pre-set-playground.js")
2253
+ );
2254
+ if (preSetPlaygroundPath) {
2255
+ await execa("node", [preSetPlaygroundPath], {
2256
+ cwd: workshopRoot,
2257
+ stdio: "inherit",
2258
+ env: {
2259
+ EPICSHOP_PLAYGROUND_TIMESTAMP: setPlaygroundTimestamp.toString(),
2260
+ EPICSHOP_PLAYGROUND_DEST_DIR: destDir,
2261
+ EPICSHOP_PLAYGROUND_SRC_DIR: srcDir,
2262
+ EPICSHOP_PLAYGROUND_WAS_RUNNING: playgroundWasRunning.toString()
2233
2263
  }
2234
2264
  });
2235
- async function getFiles(dir) {
2236
- const dirPath = dir.replace(/\\/g, "/");
2237
- const files = await globby([`${dirPath}/**/*`, "!**/build/**/*"], {
2238
- onlyFiles: false,
2239
- dot: true
2240
- });
2241
- return files.map((f) => f.replace(dirPath, ""));
2242
- }
2243
- const srcFiles = await getFiles(srcDir);
2244
- const destFiles = await getFiles(destDir);
2245
- const filesToDelete = destFiles.filter(
2246
- (fileName) => !srcFiles.includes(fileName)
2247
- );
2248
- for (const fileToDelete of filesToDelete) {
2249
- await fsExtra.remove(path$1.join(destDir, fileToDelete));
2250
- }
2251
- const appName = getAppName(srcDir);
2252
- await fsExtra.ensureDir(path$1.dirname(playgroundAppNameInfoPath));
2253
- await fsExtra.writeJSON(playgroundAppNameInfoPath, { appName });
2254
- const playgroundIsStillRunning = playgroundApp ? isAppRunning(playgroundApp) : false;
2255
- const restartPlayground = playgroundWasRunning && !playgroundIsStillRunning;
2256
- const postSetPlaygroundPath = await firstToExist(
2257
- path$1.join(srcDir, "epicshop", "post-set-playground.js"),
2258
- path$1.join(workshopRoot, "epicshop", "post-set-playground.js")
2259
- );
2260
- if (postSetPlaygroundPath) {
2261
- await execa("node", [postSetPlaygroundPath], {
2262
- cwd: workshopRoot,
2263
- stdio: "inherit",
2264
- env: {
2265
- EPICSHOP_PLAYGROUND_TIMESTAMP: setPlaygroundTimestamp.toString(),
2266
- EPICSHOP_PLAYGROUND_SRC_DIR: srcDir,
2267
- EPICSHOP_PLAYGROUND_DEST_DIR: destDir,
2268
- EPICSHOP_PLAYGROUND_WAS_RUNNING: playgroundWasRunning.toString(),
2269
- EPICSHOP_PLAYGROUND_IS_STILL_RUNNING: playgroundIsStillRunning.toString(),
2270
- EPICSHOP_PLAYGROUND_RESTART_PLAYGROUND: restartPlayground.toString()
2271
- }
2272
- });
2273
- }
2274
- modifiedTimes.set(destDir, Date.now());
2275
- if (playgroundApp && restartPlayground) {
2276
- await runAppDev(playgroundApp);
2277
- await waitOnApp(playgroundApp);
2265
+ }
2266
+ const basename2 = path$1.basename(srcDir);
2267
+ await fsExtra.remove(path$1.join(destDir, "node_modules"));
2268
+ await fsExtra.copy(srcDir, destDir, {
2269
+ filter: async (srcFile, destFile) => {
2270
+ if (srcFile.includes(`${basename2}${path$1.sep}build`) || srcFile.includes(`${basename2}${path$1.sep}public${path$1.sep}build`)) {
2271
+ return false;
2272
+ }
2273
+ if (srcFile === srcDir) return true;
2274
+ if (srcFile.includes("node_modules")) return true;
2275
+ if (srcFile.endsWith(".env")) return true;
2276
+ if (isIgnored(srcFile)) return false;
2277
+ try {
2278
+ const isDir = (await fsExtra.stat(srcFile)).isDirectory();
2279
+ if (isDir) return true;
2280
+ const destIsDir = (await fsExtra.stat(destFile)).isDirectory();
2281
+ if (destIsDir) return true;
2282
+ const currentContents = await fsExtra.readFile(destFile);
2283
+ const newContents = await fsExtra.readFile(srcFile);
2284
+ if (currentContents.equals(newContents)) return false;
2285
+ return true;
2286
+ } catch {
2287
+ return true;
2288
+ }
2278
2289
  }
2279
2290
  });
2280
- (_a2 = getWatcher()) == null ? void 0 : _a2.emit("all", "playground", destDir);
2291
+ async function getFiles(dir) {
2292
+ const dirPath = dir.replace(/\\/g, "/");
2293
+ const files = await globby([`${dirPath}/**/*`, "!**/build/**/*"], {
2294
+ onlyFiles: false,
2295
+ dot: true
2296
+ });
2297
+ return files.map((f) => f.replace(dirPath, ""));
2298
+ }
2299
+ const srcFiles = await getFiles(srcDir);
2300
+ const destFiles = await getFiles(destDir);
2301
+ const filesToDelete = destFiles.filter(
2302
+ (fileName) => !srcFiles.includes(fileName)
2303
+ );
2304
+ for (const fileToDelete of filesToDelete) {
2305
+ await fsExtra.remove(path$1.join(destDir, fileToDelete));
2306
+ }
2307
+ const appName = getAppName(srcDir);
2308
+ await fsExtra.ensureDir(path$1.dirname(playgroundAppNameInfoPath));
2309
+ await fsExtra.writeJSON(playgroundAppNameInfoPath, { appName });
2310
+ const playgroundIsStillRunning = playgroundApp ? isAppRunning(playgroundApp) : false;
2311
+ const restartPlayground = playgroundWasRunning && !playgroundIsStillRunning;
2312
+ const postSetPlaygroundPath = await firstToExist(
2313
+ path$1.join(srcDir, "epicshop", "post-set-playground.js"),
2314
+ path$1.join(workshopRoot, "epicshop", "post-set-playground.js")
2315
+ );
2316
+ if (postSetPlaygroundPath) {
2317
+ await execa("node", [postSetPlaygroundPath], {
2318
+ cwd: workshopRoot,
2319
+ stdio: "inherit",
2320
+ env: {
2321
+ EPICSHOP_PLAYGROUND_TIMESTAMP: setPlaygroundTimestamp.toString(),
2322
+ EPICSHOP_PLAYGROUND_SRC_DIR: srcDir,
2323
+ EPICSHOP_PLAYGROUND_DEST_DIR: destDir,
2324
+ EPICSHOP_PLAYGROUND_WAS_RUNNING: playgroundWasRunning.toString(),
2325
+ EPICSHOP_PLAYGROUND_IS_STILL_RUNNING: playgroundIsStillRunning.toString(),
2326
+ EPICSHOP_PLAYGROUND_RESTART_PLAYGROUND: restartPlayground.toString()
2327
+ }
2328
+ });
2329
+ }
2330
+ modifiedTimes.set(destDir, Date.now());
2331
+ if (playgroundApp && restartPlayground) {
2332
+ await runAppDev(playgroundApp);
2333
+ await waitOnApp(playgroundApp);
2334
+ }
2281
2335
  }
2282
2336
  async function getPlaygroundAppName() {
2283
2337
  if (!await exists(playgroundAppNameInfoPath)) {
2284
2338
  return null;
2285
2339
  }
2286
2340
  try {
2287
- const jsonString = await fs.promises.readFile(
2341
+ const jsonString = await fs$1.promises.readFile(
2288
2342
  playgroundAppNameInfoPath,
2289
2343
  "utf8"
2290
2344
  );
@@ -2295,27 +2349,6 @@ async function getPlaygroundAppName() {
2295
2349
  return null;
2296
2350
  }
2297
2351
  }
2298
- async function getDirModifiedTime(dir) {
2299
- const isIgnored = await isGitIgnored({ cwd: dir });
2300
- const files = await fs.promises.readdir(dir, { withFileTypes: true });
2301
- const modifiedTimes2 = await Promise.all(
2302
- files.map(async (file) => {
2303
- if (isIgnored(file.name)) return 0;
2304
- const filePath = path$1.join(dir, file.name);
2305
- if (file.isDirectory()) {
2306
- return getDirModifiedTime(filePath);
2307
- } else {
2308
- try {
2309
- const { mtimeMs } = await fs.promises.stat(filePath);
2310
- return mtimeMs;
2311
- } catch {
2312
- return 0;
2313
- }
2314
- }
2315
- })
2316
- );
2317
- return Math.max(0, ...modifiedTimes2);
2318
- }
2319
2352
  function getAppDisplayName(a, allApps) {
2320
2353
  let displayName = `${a.title} (${a.type})`;
2321
2354
  if (isExerciseStepApp(a)) {
@@ -2379,6 +2412,23 @@ const playgroundPath = path$1.join(workshopRoot, "playground/");
2379
2412
  function getRelativePath$1(filePath) {
2380
2413
  return path$1.normalize(filePath).replace(playgroundPath, `playground${path$1.sep}`).replace(exercisesPath, "");
2381
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
+ }
2382
2432
  function Confetti({ id }) {
2383
2433
  if (!id) return null;
2384
2434
  return /* @__PURE__ */ jsx(ClientOnly, { children: () => /* @__PURE__ */ jsx(
@@ -2614,7 +2664,7 @@ const extendedTheme = {
2614
2664
  }
2615
2665
  };
2616
2666
  const AnchorOrLink = React.forwardRef(function AnchorOrLink2(props, ref) {
2617
- var _a2;
2667
+ var _a;
2618
2668
  const {
2619
2669
  to,
2620
2670
  href,
@@ -2635,7 +2685,7 @@ const AnchorOrLink = React.forwardRef(function AnchorOrLink2(props, ref) {
2635
2685
  }
2636
2686
  if (!shouldUserRegularAnchor && typeof to === "object") {
2637
2687
  toUrl = `${to.pathname ?? ""}${to.hash ? `#${to.hash}` : ""}${to.search ? `?${to.search}` : ""}`;
2638
- shouldUserRegularAnchor = Boolean((_a2 = to.pathname) == null ? void 0 : _a2.includes(":"));
2688
+ shouldUserRegularAnchor = Boolean((_a = to.pathname) == null ? void 0 : _a.includes(":"));
2639
2689
  }
2640
2690
  if (shouldUserRegularAnchor) {
2641
2691
  return /* @__PURE__ */ jsx("a", { ...rest, download, href: href ?? toUrl, ref, children });
@@ -2799,8 +2849,8 @@ function EpicProgress() {
2799
2849
  const transition = useNavigation();
2800
2850
  const fetchers = useFetchers().filter(
2801
2851
  (fetcher) => {
2802
- var _a2;
2803
- 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";
2804
2854
  }
2805
2855
  );
2806
2856
  const states = [transition.state, ...fetchers.map((f) => f.state)];
@@ -3040,17 +3090,17 @@ function ThemeSwitch() {
3040
3090
  ] });
3041
3091
  }
3042
3092
  function useTheme() {
3043
- var _a2;
3093
+ var _a;
3044
3094
  const hints = useHints();
3045
3095
  const requestInfo = useRequestInfo();
3046
3096
  const fetchers = useFetchers();
3047
3097
  const fetcher = fetchers.find(
3048
3098
  (f) => {
3049
- var _a3;
3050
- 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";
3051
3101
  }
3052
3102
  );
3053
- 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");
3054
3104
  if (optimisticTheme === "system") return hints.theme;
3055
3105
  if (optimisticTheme === "light" || optimisticTheme === "dark") {
3056
3106
  return optimisticTheme;
@@ -3324,7 +3374,7 @@ function getProgressForLesson(epicLessonSlug, {
3324
3374
  workshopFinished,
3325
3375
  exercises
3326
3376
  }) {
3327
- var _a2;
3377
+ var _a;
3328
3378
  const hasEmbed = (embed) => embed == null ? void 0 : embed.some((e) => e.split("/").at(-1) === epicLessonSlug);
3329
3379
  if (workshopInstructions.compiled.status === "success" && hasEmbed(workshopInstructions.compiled.epicVideoEmbeds)) {
3330
3380
  return { type: "workshop-instructions" };
@@ -3346,7 +3396,7 @@ function getProgressForLesson(epicLessonSlug, {
3346
3396
  };
3347
3397
  }
3348
3398
  for (const step of exercise.steps.filter(Boolean)) {
3349
- if (hasEmbed((_a2 = step.problem) == null ? void 0 : _a2.epicVideoEmbeds)) {
3399
+ if (hasEmbed((_a = step.problem) == null ? void 0 : _a.epicVideoEmbeds)) {
3350
3400
  return {
3351
3401
  type: "step",
3352
3402
  exerciseNumber: exercise.exerciseNumber,
@@ -3444,7 +3494,7 @@ async function userHasAccessToWorkshop({
3444
3494
  request,
3445
3495
  forceFresh
3446
3496
  }) {
3447
- var _a2;
3497
+ var _a;
3448
3498
  const config = getWorkshopConfig();
3449
3499
  const {
3450
3500
  product: { host, slug }
@@ -3454,7 +3504,7 @@ async function userHasAccessToWorkshop({
3454
3504
  const cookieHeader = request.headers.get("Cookie");
3455
3505
  if (!cookieHeader) return false;
3456
3506
  const cookies = cookie__default.parse(cookieHeader);
3457
- 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;
3458
3508
  }
3459
3509
  const authInfo = await getAuthInfo();
3460
3510
  if (!authInfo) return false;
@@ -3486,9 +3536,9 @@ async function userHasAccessToWorkshop({
3486
3536
  }
3487
3537
  const PresenceContext = createContext(null);
3488
3538
  function usePresencePreferences() {
3489
- var _a2;
3539
+ var _a;
3490
3540
  const data = useRouteLoaderData("root");
3491
- 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;
3492
3542
  }
3493
3543
  function useOptionalWorkshopTitle() {
3494
3544
  const data = useRouteLoaderData("root");
@@ -3567,10 +3617,10 @@ function usePresenceSocket(user) {
3567
3617
  }
3568
3618
  function scoreUsers(location, users) {
3569
3619
  const scoredUsers = users.map((user) => {
3570
- var _a2, _b, _c, _d;
3620
+ var _a, _b, _c, _d;
3571
3621
  let score = 0;
3572
3622
  const available = 4;
3573
- 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)) {
3574
3624
  score += 1;
3575
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)) {
3576
3626
  score += 1;
@@ -3722,33 +3772,42 @@ async function loader$y({ request }) {
3722
3772
  throw redirect("/onboarding");
3723
3773
  }
3724
3774
  }
3725
- const preferences = await getPreferences();
3726
- const progress = await getProgress({ timings }).catch((e) => {
3727
- console.error("Failed to get progress", e);
3728
- const emptyProgress = [];
3729
- return emptyProgress;
3730
- });
3731
- const { toast: toast2, headers: toastHeaders } = await getToast(request);
3732
- const { confettiId, headers: confettiHeaders } = getConfetti(request);
3733
- const discordMember = await getDiscordMember();
3734
3775
  const theme = getTheme(request);
3735
- const user = await getUserInfo();
3736
- const userHasAccess = await userHasAccessToWorkshop({ request, timings });
3737
- const apps = await getApps({ request, timings });
3738
- 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
+ });
3739
3794
  return json$1(
3740
3795
  {
3796
+ ...asyncStuff,
3741
3797
  workshopConfig,
3742
3798
  workshopTitle,
3743
3799
  workshopSubtitle,
3744
3800
  instructor,
3745
- apps: apps.map(({ name, fullPath, relativePath }) => ({
3801
+ apps: asyncStuff.apps.map(({ name, fullPath, relativePath }) => ({
3746
3802
  name,
3747
3803
  fullPath,
3748
3804
  relativePath
3749
3805
  })),
3750
3806
  ENV: getEnv(),
3751
3807
  requestInfo: {
3808
+ protocol: new URL(request.url).protocol,
3809
+ hostname: new URL(request.url).hostname,
3810
+ port: new URL(request.url).port,
3752
3811
  origin: new URL(request.url).origin,
3753
3812
  domain: getDomainUrl(request),
3754
3813
  hints: getHints(request),
@@ -3756,11 +3815,6 @@ async function loader$y({ request }) {
3756
3815
  session: { theme },
3757
3816
  separator: path$1.sep
3758
3817
  },
3759
- progress,
3760
- preferences,
3761
- discordMember,
3762
- user,
3763
- userHasAccess,
3764
3818
  toast: toast2,
3765
3819
  confettiId,
3766
3820
  presence: {
@@ -3786,11 +3840,6 @@ function Document({
3786
3840
  env = {},
3787
3841
  className
3788
3842
  }) {
3789
- const revalidator = useRevalidator();
3790
- useEffect(() => {
3791
- window.__epicshop ?? (window.__epicshop = {});
3792
- window.__epicshop.handleFileChange = revalidator.revalidate;
3793
- }, [revalidator]);
3794
3843
  return /* @__PURE__ */ jsxs("html", { lang: "en", className, children: [
3795
3844
  /* @__PURE__ */ jsxs("head", { children: [
3796
3845
  /* @__PURE__ */ jsx(ClientHintCheck, {}),
@@ -3810,8 +3859,7 @@ function Document({
3810
3859
  /* @__PURE__ */ jsxs("body", { className: "bg-background text-foreground scrollbar-thin scrollbar-thumb-scrollbar h-screen-safe", children: [
3811
3860
  children,
3812
3861
  /* @__PURE__ */ jsx(ScrollRestoration, {}),
3813
- /* @__PURE__ */ jsx(Scripts, {}),
3814
- ENV.EPICSHOP_DEPLOYED ? null : /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: getWebsocketJS() } })
3862
+ /* @__PURE__ */ jsx(Scripts, {})
3815
3863
  ] })
3816
3864
  ] });
3817
3865
  }
@@ -3850,70 +3898,6 @@ function AppWithProviders() {
3850
3898
  function ErrorBoundary$6() {
3851
3899
  return /* @__PURE__ */ jsx(Document, { className: "h-screen-safe", children: /* @__PURE__ */ jsx(GeneralErrorBoundary, {}) });
3852
3900
  }
3853
- function getWebsocketJS() {
3854
- const js = (
3855
- /* javascript */
3856
- `
3857
- function epicLiveReloadConnect(config) {
3858
- const protocol = location.protocol === "https:" ? "wss:" : "ws:";
3859
- const host = location.hostname;
3860
- const port = location.port;
3861
- const socketPath = protocol + "//" + host + ":" + port + "/__ws";
3862
- const ws = new WebSocket(socketPath);
3863
- function handleFileChange(changedFiles) {
3864
- console.log(
3865
- ['🐨 Reloading', window.frameElement?.getAttribute('title')]
3866
- .filter(Boolean)
3867
- .join(' '),
3868
- changedFiles
3869
- );
3870
- if (typeof window.__epicshop?.handleFileChange === "function") {
3871
- window.__epicshop?.handleFileChange();
3872
- } else {
3873
- setTimeout(() => window.location.reload(), 200);
3874
- }
3875
- }
3876
- function debounce(fn, ms) {
3877
- let timeout;
3878
- return function debouncedFn(...args) {
3879
- clearTimeout(timeout);
3880
- timeout = setTimeout(() => fn(...args), ms);
3881
- };
3882
- }
3883
- const debouncedHandleFileChange = debounce(handleFileChange, 50);
3884
- ws.onmessage = (message) => {
3885
- const event = JSON.parse(message.data);
3886
- if (event.type !== 'epicshop:file-change') return;
3887
- const { filePaths } = event.data;
3888
- debouncedHandleFileChange(filePaths);
3889
- };
3890
- ws.onopen = () => {
3891
- if (config && typeof config.onOpen === "function") {
3892
- config.onOpen();
3893
- }
3894
- };
3895
- ws.onclose = (event) => {
3896
- if (event.code === 1006) {
3897
- console.log("EpicShop dev server web socket closed. Reconnecting...");
3898
- setTimeout(
3899
- () =>
3900
- epicLiveReloadConnect({
3901
- onOpen: () => window.location.reload(),
3902
- }),
3903
- 1000
3904
- );
3905
- }
3906
- };
3907
- ws.onerror = (error) => {
3908
- console.log("EpicShop dev server web socket error:");
3909
- console.error(error);
3910
- };
3911
- }
3912
- epicLiveReloadConnect();
3913
- `
3914
- );
3915
- return js;
3916
- }
3917
3901
  const route0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
3918
3902
  __proto__: null,
3919
3903
  ErrorBoundary: ErrorBoundary$6,
@@ -4050,6 +4034,63 @@ function Logo({
4050
4034
  }
4051
4035
  return logoElement;
4052
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;
4053
4094
  const Dialog = DialogPrimitive.Root;
4054
4095
  const DialogTrigger = DialogPrimitive.Trigger;
4055
4096
  const DialogPortal = DialogPrimitive.Portal;
@@ -4157,14 +4198,14 @@ function useEpicProgress() {
4157
4198
  const data = useRouteLoaderData("root");
4158
4199
  const progressFetcher = useFetchers().find(
4159
4200
  (f) => {
4160
- var _a2;
4161
- 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"));
4162
4203
  }
4163
4204
  );
4164
4205
  if (!progressFetcher || !(data == null ? void 0 : data.progress)) return (data == null ? void 0 : data.progress) ?? null;
4165
4206
  return data.progress.map((p) => {
4166
- var _a2, _b;
4167
- 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";
4168
4209
  const optimisticLessonSlug = (_b = progressFetcher.formData) == null ? void 0 : _b.get("lessonSlug");
4169
4210
  if (optimisticLessonSlug === p.epicLessonSlug) {
4170
4211
  return {
@@ -4324,13 +4365,13 @@ function ProgressToggle({
4324
4365
  className,
4325
4366
  ...progressItemSearch
4326
4367
  }) {
4327
- var _a2, _b, _c, _d;
4368
+ var _a, _b, _c, _d;
4328
4369
  const progressFetcher = useFetcher();
4329
4370
  const peRedirectInput = usePERedirectInput();
4330
4371
  const progressItem = useProgressItem(progressItemSearch);
4331
4372
  const animationRef = React.useRef(null);
4332
4373
  const buttonRef = React.useRef(null);
4333
- 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);
4334
4375
  const [startAnimation, setStartAnimation] = React.useState(false);
4335
4376
  const location = useLocation();
4336
4377
  const navigation = useNavigation();
@@ -4338,11 +4379,11 @@ function ProgressToggle({
4338
4379
  const navigationLocationPathname = (_d = navigation.location) == null ? void 0 : _d.pathname;
4339
4380
  const locationPathname = location.pathname;
4340
4381
  React.useEffect(() => {
4341
- var _a3;
4382
+ var _a2;
4342
4383
  if (navigationLocationStateFrom === "continue next lesson button") {
4343
4384
  if (locationPathname === navigationLocationPathname) {
4344
4385
  setStartAnimation(true);
4345
- (_a3 = buttonRef.current) == null ? void 0 : _a3.focus();
4386
+ (_a2 = buttonRef.current) == null ? void 0 : _a2.focus();
4346
4387
  }
4347
4388
  }
4348
4389
  }, [
@@ -4450,7 +4491,7 @@ const route36 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
4450
4491
  useRequireEpicProgress
4451
4492
  }, Symbol.toStringTag, { value: "Module" }));
4452
4493
  async function loader$w({ request }) {
4453
- var _a2;
4494
+ var _a;
4454
4495
  const timings = makeTimings("appLayoutLoader");
4455
4496
  const { title: workshopTitle } = getWorkshopConfig();
4456
4497
  const [exercises, playgroundAppName] = await Promise.all([
@@ -4460,7 +4501,7 @@ async function loader$w({ request }) {
4460
4501
  const playground = {
4461
4502
  appName: playgroundAppName,
4462
4503
  exerciseNumber: Number(
4463
- (_a2 = extractNumbersAndTypeFromAppNameOrPath(playgroundAppName ?? "")) == null ? void 0 : _a2.exerciseNumber
4504
+ (_a = extractNumbersAndTypeFromAppNameOrPath(playgroundAppName ?? "")) == null ? void 0 : _a.exerciseNumber
4464
4505
  )
4465
4506
  };
4466
4507
  const result = json$1(
@@ -4554,7 +4595,7 @@ function FacePile({ isMenuOpened }) {
4554
4595
  return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [
4555
4596
  (shouldShowNumberOverLimit ? users.slice(0, limit) : users).map(
4556
4597
  ({ user, score }) => {
4557
- var _a2, _b;
4598
+ var _a, _b;
4558
4599
  const scoreClassNames = getScoreClassNames(score);
4559
4600
  const locationLabel = getLocationLabel(user.location);
4560
4601
  return /* @__PURE__ */ jsxs(Tooltip, { children: [
@@ -4585,7 +4626,7 @@ function FacePile({ isMenuOpened }) {
4585
4626
  /* @__PURE__ */ jsxs("span", { children: [
4586
4627
  user.name || `${displayNameShort} Dev`,
4587
4628
  " ",
4588
- 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
4589
4630
  ] }),
4590
4631
  (locationLabel == null ? void 0 : locationLabel.line1) ? /* @__PURE__ */ jsx("span", { children: locationLabel.line1 }) : null,
4591
4632
  (locationLabel == null ? void 0 : locationLabel.line2) ? /* @__PURE__ */ jsx("span", { children: locationLabel.line2 }) : null
@@ -4626,6 +4667,7 @@ function App() {
4626
4667
  const isWide = useIsWide();
4627
4668
  const isHydrated = useHydrated();
4628
4669
  const [isMenuOpened, setMenuOpened] = React.useState(false);
4670
+ useRevalidationWS({ watchPaths: ["./exercises/README.mdx"] });
4629
4671
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
4630
4672
  user ? null : /* @__PURE__ */ jsx(NoUserBanner, {}),
4631
4673
  isHydrated && isWide ? null : /* @__PURE__ */ jsx(
@@ -5462,9 +5504,9 @@ function NavToggle({
5462
5504
  React.useEffect(() => {
5463
5505
  if (!isMenuOpened) return;
5464
5506
  function handleKeyUp(event) {
5465
- var _a2;
5507
+ var _a;
5466
5508
  if (event.key === "Escape") {
5467
- (_a2 = menuButtonRef.current) == null ? void 0 : _a2.click();
5509
+ (_a = menuButtonRef.current) == null ? void 0 : _a.click();
5468
5510
  }
5469
5511
  }
5470
5512
  document.addEventListener("keyup", handleKeyUp);
@@ -5816,7 +5858,7 @@ async function getForceFresh(filePath, cacheEntry) {
5816
5858
  if (!cacheEntry) return true;
5817
5859
  const app = await getAppFromFile(filePath);
5818
5860
  if (!app) return true;
5819
- const appModified = modifiedTimes.get(app.fullPath) ?? 0;
5861
+ const appModified = await queuedGetDirModifiedTime(app.fullPath);
5820
5862
  const cacheModified = cacheEntry.metadata.createdTime;
5821
5863
  return !cacheModified || appModified > cacheModified || void 0;
5822
5864
  }
@@ -5836,7 +5878,7 @@ async function compileTs(filePath, fullPath, {
5836
5878
  getFreshValue: async () => {
5837
5879
  const result = await esbuild.build({
5838
5880
  stdin: {
5839
- contents: await fs$1.promises.readFile(filePath, "utf-8"),
5881
+ contents: await fs.promises.readFile(filePath, "utf-8"),
5840
5882
  // NOTE: if the fileAppName is specified, then we're resolving to a different
5841
5883
  // app than the one we're serving the file from. We do this so the tests
5842
5884
  // can live in the solution directory, but be run against the problem
@@ -6070,6 +6112,10 @@ async function loader$r({ request, params }) {
6070
6112
  return redirect(getBaseUrl({ request, port: app.dev.portNumber }));
6071
6113
  }
6072
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
+ }
6073
6119
  const js = (
6074
6120
  /* javascript */
6075
6121
  `
@@ -6077,20 +6123,19 @@ async function loader$r({ request, params }) {
6077
6123
  const protocol = location.protocol === "https:" ? "wss:" : "ws:";
6078
6124
  const host = location.hostname;
6079
6125
  const port = location.port;
6080
- const socketPath = protocol + "//" + host + ":" + port + "/__ws";
6126
+ const socketPath = protocol + "//" + host + ":" + port + "/__ws?" + ${JSON.stringify(watchParams.toString())};
6081
6127
  const ws = new WebSocket(socketPath);
6082
6128
  ws.onmessage = (message) => {
6083
6129
  const event = JSON.parse(message.data);
6084
6130
  if (event.type !== 'epicshop:file-change') return;
6085
6131
  const { filePaths } = event.data;
6086
- if (${JSON.stringify(relevantPaths)}.some(p => filePaths.some(filePath => filePath.startsWith(p)))) {
6087
- console.log(
6088
- ['🐨 Reloading', window.frameElement?.getAttribute('title')]
6089
- .filter(Boolean)
6090
- .join(' '),
6091
- );
6092
- window.location.reload();
6093
- }
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();
6094
6139
  };
6095
6140
  ws.onopen = () => {
6096
6141
  if (config && typeof config.onOpen === "function") {
@@ -6129,7 +6174,7 @@ const route6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
6129
6174
  loader: loader$r
6130
6175
  }, Symbol.toStringTag, { value: "Module" }));
6131
6176
  async function loader$q({ request, params }) {
6132
- var _a2;
6177
+ var _a;
6133
6178
  const timings = makeTimings("app");
6134
6179
  const { fileApp, app } = await resolveApps({ request, params, timings });
6135
6180
  const baseApp = isPlaygroundApp(app) ? await getAppByName(app.appName) : app;
@@ -6177,7 +6222,7 @@ async function loader$q({ request, params }) {
6177
6222
  const { title: workshopTitle } = getWorkshopConfig();
6178
6223
  const baseAppTitle = isExerciseStepApp(baseApp) ? [
6179
6224
  `${baseApp.stepNumber.toString().padStart(2, "0")}. ${baseApp.title}`,
6180
- `${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"}`,
6181
6226
  workshopTitle
6182
6227
  ] : [(baseApp == null ? void 0 : baseApp.title) ?? "N/A"];
6183
6228
  const title = (isExerciseStepApp(app) ? [
@@ -6217,7 +6262,7 @@ const route7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
6217
6262
  loader: loader$q
6218
6263
  }, Symbol.toStringTag, { value: "Module" }));
6219
6264
  async function loader$p({ request, params }) {
6220
- var _a2;
6265
+ var _a;
6221
6266
  const timings = makeTimings("app_test_loader");
6222
6267
  const userHasAccess = await userHasAccessToWorkshop({
6223
6268
  request
@@ -6320,7 +6365,7 @@ ${testScriptTag}`;
6320
6365
  const title = (isExerciseStepApp(app) ? [
6321
6366
  isProblemApp(app) ? "🧪💪" : isSolutionApp(app) ? "🧪🏁" : null,
6322
6367
  `${app.stepNumber.toString().padStart(2, "0")}. ${app.title}`,
6323
- `${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"}`,
6324
6369
  workshopTitle
6325
6370
  ] : ["🧪", appTitle]).filter(Boolean).join(" | ");
6326
6371
  const html = (
@@ -6501,9 +6546,9 @@ const PlaybackTimeSchema = z.object({
6501
6546
  return { time: Number(data.time), expiresAt: new Date(data.expiresAt) };
6502
6547
  });
6503
6548
  function usePlayerPreferences() {
6504
- var _a2;
6549
+ var _a;
6505
6550
  const data = useRouteLoaderData("root");
6506
- 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;
6507
6552
  }
6508
6553
  const ignoredInputs = [
6509
6554
  "INPUT",
@@ -6611,9 +6656,9 @@ function MuxPlayer({
6611
6656
  });
6612
6657
  }, 300);
6613
6658
  React.useEffect(() => {
6614
- var _a2, _b;
6659
+ var _a, _b;
6615
6660
  if (!metadataLoaded) return;
6616
- const textTracks = (_a2 = muxPlayerRef.current) == null ? void 0 : _a2.textTracks;
6661
+ const textTracks = (_a = muxPlayerRef.current) == null ? void 0 : _a.textTracks;
6617
6662
  if (!textTracks) return;
6618
6663
  const subtitlePref = (_b = playerPreferencesRef.current) == null ? void 0 : _b.subtitle;
6619
6664
  if (subtitlePref == null ? void 0 : subtitlePref.id) {
@@ -6655,11 +6700,11 @@ function MuxPlayer({
6655
6700
  defaultHiddenCaptions: true,
6656
6701
  currentTime,
6657
6702
  onTimeUpdate: () => {
6658
- var _a2;
6703
+ var _a;
6659
6704
  return sessionStorage.setItem(
6660
6705
  currentTimeSessionKey,
6661
6706
  JSON.stringify({
6662
- time: (_a2 = muxPlayerRef.current) == null ? void 0 : _a2.currentTime,
6707
+ time: (_a = muxPlayerRef.current) == null ? void 0 : _a.currentTime,
6663
6708
  expiresAt: new Date(Date.now() + 1e3 * 60 * 30).toISOString()
6664
6709
  })
6665
6710
  );
@@ -6743,8 +6788,8 @@ function useInterval(callback, delay = 1e3) {
6743
6788
  }, [callback]);
6744
6789
  useEffect(() => {
6745
6790
  function tick() {
6746
- var _a2;
6747
- (_a2 = savedCallback.current) == null ? void 0 : _a2.call(savedCallback);
6791
+ var _a;
6792
+ (_a = savedCallback.current) == null ? void 0 : _a.call(savedCallback);
6748
6793
  }
6749
6794
  if (delay !== null) {
6750
6795
  const id = setInterval(tick, delay);
@@ -6872,7 +6917,7 @@ function extractEpicTitle(urlString) {
6872
6917
  "useFetcherType"
6873
6918
  ];
6874
6919
  const title = titleWords.filter(Boolean).map((word, index) => {
6875
- var _a2;
6920
+ var _a;
6876
6921
  const lowerWord = word.toLowerCase();
6877
6922
  const literalWord = literalWords.find(
6878
6923
  (w) => w.toLowerCase() === lowerWord
@@ -6881,7 +6926,7 @@ function extractEpicTitle(urlString) {
6881
6926
  if (lowerCaseWords.includes(lowerWord) && index > 0) {
6882
6927
  return lowerWord;
6883
6928
  }
6884
- 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);
6885
6930
  }).join(" ");
6886
6931
  if (isSolution) {
6887
6932
  return `${title} (🏁 solution)`;
@@ -7351,7 +7396,7 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber, colNumber, work
7351
7396
  return [fileName];
7352
7397
  }
7353
7398
  function guessEditor() {
7354
- var _a2;
7399
+ var _a;
7355
7400
  if (process.env.EPICSHOP_EDITOR) {
7356
7401
  return shellQuote.parse(process.env.EPICSHOP_EDITOR).map((a) => String(a));
7357
7402
  }
@@ -7372,7 +7417,7 @@ function guessEditor() {
7372
7417
  ).toString();
7373
7418
  const runningProcesses = output.split("\r\n");
7374
7419
  for (let i = 0; i < runningProcesses.length; i++) {
7375
- const processPath = (_a2 = runningProcesses[i]) == null ? void 0 : _a2.trim();
7420
+ const processPath = (_a = runningProcesses[i]) == null ? void 0 : _a.trim();
7376
7421
  if (!processPath) continue;
7377
7422
  const processName = path.basename(processPath);
7378
7423
  if (COMMON_EDITORS_WIN.includes(processName)) {
@@ -7431,7 +7476,7 @@ async function launchEditor(pathList, lineNumber = 1, colNumber = 1) {
7431
7476
  )) {
7432
7477
  acc.errorsList.push(fileName);
7433
7478
  } else {
7434
- if (!fs$1.existsSync(fileName)) {
7479
+ if (!fs.existsSync(fileName)) {
7435
7480
  fsExtra.ensureDirSync(path.dirname(fileName));
7436
7481
  fsExtra.writeFileSync(fileName, "", "utf8");
7437
7482
  }
@@ -7481,7 +7526,7 @@ File names may consist only of alphanumeric characters (all languages), periods,
7481
7526
  _childProcess.kill("SIGKILL");
7482
7527
  }
7483
7528
  return new Promise((res) => {
7484
- var _a2;
7529
+ var _a;
7485
7530
  if (process.platform === "win32") {
7486
7531
  _childProcess = child_process.spawn(
7487
7532
  "cmd.exe",
@@ -7493,7 +7538,7 @@ File names may consist only of alphanumeric characters (all languages), periods,
7493
7538
  stdio: ["inherit", "inherit", "pipe"]
7494
7539
  });
7495
7540
  }
7496
- (_a2 = _childProcess.stderr) == null ? void 0 : _a2.on("data", (data) => {
7541
+ (_a = _childProcess.stderr) == null ? void 0 : _a.on("data", (data) => {
7497
7542
  const message = String(data);
7498
7543
  if (!message.includes("Node.js environment variables are disabled")) {
7499
7544
  process.stderr.write(data);
@@ -7663,7 +7708,7 @@ function LaunchEditorImpl({
7663
7708
  children,
7664
7709
  onUpdate
7665
7710
  }) {
7666
- var _a2;
7711
+ var _a;
7667
7712
  const fetcher = useLaunchFetcher(onUpdate);
7668
7713
  const peRedirectInput = usePERedirectInput();
7669
7714
  const fileList = typeof appFile === "string" ? [appFile] : appFile;
@@ -7697,7 +7742,7 @@ function LaunchEditorImpl({
7697
7742
  className: clsx(
7698
7743
  "launch_button",
7699
7744
  fetcher.state === "idle" ? null : "cursor-progress",
7700
- ((_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
7701
7746
  ),
7702
7747
  children
7703
7748
  }
@@ -7808,19 +7853,19 @@ function OpenInEditor({
7808
7853
  "data-start": start,
7809
7854
  "data-type": type
7810
7855
  }) {
7811
- var _a2;
7856
+ var _a;
7812
7857
  const data = useLoaderData();
7813
7858
  if (type === "other" || !buttons || !filename || !fullPath) return null;
7814
- 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) ?? "");
7815
7860
  const isFileFromDifferentApp = !fullPath.startsWith(currentAppFullPath);
7816
7861
  const validButtons = ENV.EPICSHOP_DEPLOYED ? ["problem", "solution"] : ["problem", "solution", "playground"];
7817
7862
  const buttonList = buttons.split(",");
7818
7863
  const apps = validButtons.filter((button) => buttonList.includes(button));
7819
7864
  return /* @__PURE__ */ jsx(Fragment, { children: apps.map((type2) => {
7820
- var _a3;
7865
+ var _a2;
7821
7866
  const app = data[type2];
7822
7867
  if (type2 === "playground") {
7823
- 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);
7824
7869
  if (!app || isDifferentApp || isFileFromDifferentApp) {
7825
7870
  return /* @__PURE__ */ jsxs(
7826
7871
  "button",
@@ -7864,10 +7909,10 @@ function CopyButton() {
7864
7909
  {
7865
7910
  className: cn(buttonClassName, "w-12 uppercase"),
7866
7911
  onClick: (event) => {
7867
- var _a2, _b, _c;
7912
+ var _a, _b, _c;
7868
7913
  setCopied(true);
7869
7914
  const button = event.currentTarget;
7870
- 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) || "";
7871
7916
  void navigator.clipboard.writeText(code);
7872
7917
  },
7873
7918
  children: copied ? "copied" : "copy"
@@ -7976,9 +8021,9 @@ const meta$4 = ({
7976
8021
  data,
7977
8022
  matches
7978
8023
  }) => {
7979
- var _a2;
8024
+ var _a;
7980
8025
  const number = data == null ? void 0 : data.exercise.exerciseNumber.toString().padStart(2, "0");
7981
- 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;
7982
8027
  if (!data || !rootData) return [{ title: "🦉 | Error" }];
7983
8028
  return getSeoMetaTags({
7984
8029
  title: `📝 | ${number}. ${data.exercise.title} | ${rootData == null ? void 0 : rootData.workshopTitle}`,
@@ -8046,9 +8091,12 @@ const headers$9 = ({ loaderHeaders, parentHeaders }) => {
8046
8091
  };
8047
8092
  const mdxComponents$4 = { h1: () => null };
8048
8093
  function ExerciseNumberRoute() {
8049
- var _a2;
8094
+ var _a;
8050
8095
  const data = useLoaderData();
8051
- 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(
8052
8100
  2,
8053
8101
  "0"
8054
8102
  );
@@ -8259,11 +8307,11 @@ function diffPathToRelative(filePath) {
8259
8307
  return relativePath.join(path.sep);
8260
8308
  }
8261
8309
  function getLanguage(ext) {
8262
- var _a2;
8263
- return ((_a2 = bundledLanguagesInfo.find((l) => {
8264
- var _a3;
8265
- return l.id === ext || ((_a3 = l.aliases) == null ? void 0 : _a3.includes(ext));
8266
- })) == 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";
8267
8315
  }
8268
8316
  function getFileCodeblocks(file, filePathApp1, filePathApp2, type) {
8269
8317
  if (!file.chunks.length) {
@@ -8444,15 +8492,23 @@ async function getDiffIgnore(filePath) {
8444
8492
  (content) => content.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("#")).filter(Boolean)
8445
8493
  ) : [];
8446
8494
  }
8447
- function getForceFreshForDiff(app1, app2, cacheEntry) {
8448
- 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;
8449
8498
  const app1Modified = modifiedTimes.get(app1.fullPath) ?? 0;
8499
+ if (app1Modified > cacheModified) return true;
8450
8500
  const app2Modified = modifiedTimes.get(app2.fullPath) ?? 0;
8451
- const cacheModified = cacheEntry.metadata.createdTime;
8452
- 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;
8453
8509
  }
8454
8510
  async function getDiffFiles(app1, app2, {
8455
- forceFresh = false,
8511
+ forceFresh,
8456
8512
  timings,
8457
8513
  request
8458
8514
  } = {}) {
@@ -8461,7 +8517,7 @@ async function getDiffFiles(app1, app2, {
8461
8517
  const result = await cachified({
8462
8518
  key,
8463
8519
  cache: diffFilesCache,
8464
- forceFresh: forceFresh || getForceFreshForDiff(app1, app2, cacheEntry),
8520
+ forceFresh: forceFresh || await getForceFreshForDiff(app1, app2, cacheEntry),
8465
8521
  timings,
8466
8522
  request,
8467
8523
  getFreshValue: () => getDiffFilesImpl(app1, app2)
@@ -8511,7 +8567,7 @@ async function getDiffFilesImpl(app1, app2) {
8511
8567
  })).filter((file) => !testFiles.includes(file.path));
8512
8568
  }
8513
8569
  async function getDiffCode(app1, app2, {
8514
- forceFresh = false,
8570
+ forceFresh,
8515
8571
  timings,
8516
8572
  request
8517
8573
  } = {}) {
@@ -8520,7 +8576,7 @@ async function getDiffCode(app1, app2, {
8520
8576
  const result = await cachified({
8521
8577
  key,
8522
8578
  cache: diffCodeCache,
8523
- forceFresh: forceFresh || getForceFreshForDiff(app1, app2, cacheEntry),
8579
+ forceFresh: forceFresh || await getForceFreshForDiff(app1, app2, cacheEntry),
8524
8580
  timings,
8525
8581
  request,
8526
8582
  getFreshValue: () => getDiffCodeImpl(app1, app2)
@@ -8682,7 +8738,7 @@ function SetPlayground({
8682
8738
  tooltipText,
8683
8739
  ...buttonProps
8684
8740
  }) {
8685
- var _a2;
8741
+ var _a;
8686
8742
  const fetcher = useFetcher();
8687
8743
  const peRedirectInput = usePERedirectInput();
8688
8744
  const submitButton = /* @__PURE__ */ jsx(
@@ -8693,7 +8749,7 @@ function SetPlayground({
8693
8749
  className: clsx(
8694
8750
  buttonProps.className,
8695
8751
  fetcher.state !== "idle" ? "cursor-progress" : null,
8696
- ((_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
8697
8753
  )
8698
8754
  }
8699
8755
  );
@@ -8717,7 +8773,7 @@ function PlaygroundChooser({
8717
8773
  playgroundAppName,
8718
8774
  allApps
8719
8775
  }) {
8720
- var _a2;
8776
+ var _a;
8721
8777
  const fetcher = useFetcher();
8722
8778
  return /* @__PURE__ */ jsxs(
8723
8779
  Select.Root,
@@ -8738,7 +8794,7 @@ function PlaygroundChooser({
8738
8794
  className: clsx(
8739
8795
  "flex h-full w-full items-center justify-between text-left radix-placeholder:text-gray-500 focus-visible:outline-none",
8740
8796
  fetcher.state !== "idle" ? "cursor-progress" : null,
8741
- ((_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
8742
8798
  ),
8743
8799
  children: [
8744
8800
  /* @__PURE__ */ jsx("span", { className: "w-80 flex-1 overflow-hidden text-ellipsis whitespace-nowrap scrollbar-thin scrollbar-thumb-scrollbar", children: /* @__PURE__ */ jsx(
@@ -8884,10 +8940,10 @@ function DiffLink({
8884
8940
  );
8885
8941
  }
8886
8942
  function getAppName2(input) {
8887
- var _a2;
8943
+ var _a;
8888
8944
  if (typeof input === "number") {
8889
8945
  const stepIndex = data.exerciseIndex + input;
8890
- return (_a2 = data.allApps[stepIndex]) == null ? void 0 : _a2.name;
8946
+ return (_a = data.allApps[stepIndex]) == null ? void 0 : _a.name;
8891
8947
  }
8892
8948
  if (!input) return null;
8893
8949
  for (const { name, stepName } of data.allApps) {
@@ -8969,7 +9025,7 @@ function LinkToApp({
8969
9025
  children = /* @__PURE__ */ jsx("code", { children: appTo.toString() }),
8970
9026
  ...props
8971
9027
  }) {
8972
- var _a2;
9028
+ var _a;
8973
9029
  const [searchParams] = useSearchParams();
8974
9030
  const to = `?${withParam$1(
8975
9031
  searchParams,
@@ -8983,7 +9039,7 @@ function LinkToApp({
8983
9039
  const previewAppUrl = (app == null ? void 0 : app.dev.type) === "script" ? getBaseUrl({
8984
9040
  domain: requestInfo.domain,
8985
9041
  port: app.dev.portNumber
8986
- }) : ((_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;
8987
9043
  const { inBrowserBrowserRef } = useStepContext();
8988
9044
  const href = previewAppUrl ? previewAppUrl.slice(0, -1) + appTo.toString() : null;
8989
9045
  return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center justify-between gap-1", children: [
@@ -8997,9 +9053,9 @@ function LinkToApp({
8997
9053
  }),
8998
9054
  title: ENV.EPICSHOP_DEPLOYED ? "Cannot link to app in deployed version" : void 0,
8999
9055
  onClick: (event) => {
9000
- var _a3, _b;
9056
+ var _a2, _b;
9001
9057
  if (ENV.EPICSHOP_DEPLOYED) event.preventDefault();
9002
- (_a3 = props.onClick) == null ? void 0 : _a3.call(props, event);
9058
+ (_a2 = props.onClick) == null ? void 0 : _a2.call(props, event);
9003
9059
  (_b = inBrowserBrowserRef.current) == null ? void 0 : _b.handleExtrnalNavigation(appTo.toString());
9004
9060
  },
9005
9061
  children
@@ -9026,14 +9082,14 @@ function LinkToApp({
9026
9082
  function TouchedFiles({
9027
9083
  diffFilesPromise
9028
9084
  }) {
9029
- var _a2, _b;
9085
+ var _a, _b;
9030
9086
  const data = useLoaderData();
9031
9087
  const [open, setOpen] = React.useState(false);
9032
9088
  const contentRef = React.useRef(null);
9033
9089
  function handleLaunchUpdate() {
9034
9090
  setOpen(false);
9035
9091
  }
9036
- const appName = (_a2 = data.playground) == null ? void 0 : _a2.appName;
9092
+ const appName = (_a = data.playground) == null ? void 0 : _a.appName;
9037
9093
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
9038
9094
  /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
9039
9095
  "button",
@@ -9092,12 +9148,12 @@ function TouchedFiles({
9092
9148
  }
9093
9149
  ) }) : null,
9094
9150
  diffFiles.map((file) => {
9095
- var _a3;
9151
+ var _a2;
9096
9152
  return /* @__PURE__ */ jsx("li", { "data-state": file.status, children: /* @__PURE__ */ jsx(
9097
9153
  LaunchEditor,
9098
9154
  {
9099
9155
  appFile: `${file.path},${file.line},1`,
9100
- 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",
9101
9157
  onUpdate: handleLaunchUpdate,
9102
9158
  children: /* @__PURE__ */ jsx("code", { children: file.path })
9103
9159
  }
@@ -9115,14 +9171,14 @@ function TouchedFiles({
9115
9171
  ] }) });
9116
9172
  }
9117
9173
  function pageTitle(data, workshopTitle) {
9118
- var _a2;
9174
+ var _a;
9119
9175
  const exerciseNumber = (data == null ? void 0 : data.exerciseStepApp.exerciseNumber.toString().padStart(2, "0")) ?? "00";
9120
9176
  const stepNumber = (data == null ? void 0 : data.exerciseStepApp.stepNumber.toString().padStart(2, "0")) ?? "00";
9121
9177
  const emoji2 = {
9122
9178
  problem: "💪",
9123
9179
  solution: "🏁"
9124
9180
  }[(data == null ? void 0 : data.type) ?? "problem"];
9125
- 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";
9126
9182
  return {
9127
9183
  emoji: emoji2,
9128
9184
  stepNumber,
@@ -9138,8 +9194,8 @@ const meta$3 = ({
9138
9194
  matches,
9139
9195
  params
9140
9196
  }) => {
9141
- var _a2;
9142
- 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;
9143
9199
  if (!data || !rootData) return [{ title: "🦉 | Error" }];
9144
9200
  const { emoji: emoji2, stepNumber, title, exerciseNumber, exerciseTitle } = pageTitle(data);
9145
9201
  return getSeoMetaTags({
@@ -9152,8 +9208,8 @@ const meta$3 = ({
9152
9208
  });
9153
9209
  };
9154
9210
  async function loader$l({ request, params }) {
9155
- var _a2, _b;
9156
- const timings = makeTimings("exerciseStepTypeLoader");
9211
+ var _a, _b;
9212
+ const timings = makeTimings("exerciseStepTypeLayoutLoader");
9157
9213
  const url = new URL(request.url);
9158
9214
  const { title: workshopTitle } = getWorkshopConfig();
9159
9215
  const cacheOptions = { request, timings };
@@ -9211,7 +9267,7 @@ async function loader$l({ request, params }) {
9211
9267
  const exerciseId = getStepId(exerciseStepApp);
9212
9268
  const exerciseIndex = allApps.findIndex((step) => step.stepId === exerciseId);
9213
9269
  const exerciseApps = allAppsFull.filter(isExerciseStepApp).filter((app) => app.exerciseNumber === exerciseStepApp.exerciseNumber);
9214
- 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;
9215
9271
  const isFirstStep = ((_b = exerciseApps[0]) == null ? void 0 : _b.name) === exerciseStepApp.name;
9216
9272
  const nextApp = await getNextExerciseApp(exerciseStepApp, cacheOptions);
9217
9273
  const prevApp = await getPrevExerciseApp(exerciseStepApp, cacheOptions);
@@ -9287,10 +9343,13 @@ const headers$7 = ({ loaderHeaders, parentHeaders }) => {
9287
9343
  return headers2;
9288
9344
  };
9289
9345
  function ExercisePartRoute$1() {
9290
- var _a2;
9346
+ var _a;
9291
9347
  const data = useLoaderData();
9292
9348
  const inBrowserBrowserRef = useRef(null);
9293
9349
  const titleBits = pageTitle(data);
9350
+ useRevalidationWS({
9351
+ watchPaths: [`${data.exerciseStepApp.relativePath}/README.mdx`]
9352
+ });
9294
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: [
9295
9354
  /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col sm:col-span-1 sm:row-span-1 sm:h-full lg:border-r", children: [
9296
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: [
@@ -9319,7 +9378,7 @@ function ExercisePartRoute$1() {
9319
9378
  ")"
9320
9379
  ] })
9321
9380
  ] }),
9322
- 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
9323
9382
  ] }) }),
9324
9383
  /* @__PURE__ */ jsxs(
9325
9384
  "article",
@@ -9497,10 +9556,10 @@ async function action$4({ request }) {
9497
9556
  throw new Error(`Unknown intent: ${intent}`);
9498
9557
  }
9499
9558
  function AppStopper({ name }) {
9500
- var _a2;
9559
+ var _a;
9501
9560
  const fetcher = useFetcher();
9502
9561
  const peRedirectInput = usePERedirectInput();
9503
- const inFlightIntent = (_a2 = fetcher.formData) == null ? void 0 : _a2.get("intent");
9562
+ const inFlightIntent = (_a = fetcher.formData) == null ? void 0 : _a.get("intent");
9504
9563
  const inFlightState = inFlightIntent === "stop" ? "Stopping App" : inFlightIntent === "restart" ? "Restarting App" : null;
9505
9564
  const altDown = useAltDown();
9506
9565
  return /* @__PURE__ */ jsxs(fetcher.Form, { method: "POST", action: "/start", children: [
@@ -9530,10 +9589,10 @@ function PortStopper({ port: port2 }) {
9530
9589
  ] });
9531
9590
  }
9532
9591
  function AppStarter({ name }) {
9533
- var _a2;
9592
+ var _a;
9534
9593
  const fetcher = useFetcher();
9535
9594
  const peRedirectInput = usePERedirectInput();
9536
- 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") {
9537
9596
  if (fetcher.data.error === "port-unavailable") {
9538
9597
  return /* @__PURE__ */ jsxs("div", { children: [
9539
9598
  "The port is unavailable. Would you like to stop whatever is running on that port and try again?",
@@ -9659,8 +9718,8 @@ function InBrowserBrowserForRealzImpl({ baseUrl, id, name, initialRoute }, ref)
9659
9718
  });
9660
9719
  useEffect(() => {
9661
9720
  function handleMessage(messageEvent) {
9662
- var _a2;
9663
- 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;
9664
9723
  const result = messageSchema.safeParse(messageEvent.data, {
9665
9724
  path: ["messageEvent", "data"]
9666
9725
  });
@@ -9761,7 +9820,7 @@ function InBrowserBrowserForRealzImpl({ baseUrl, id, name, initialRoute }, ref)
9761
9820
  }
9762
9821
  }, [iframePathname]);
9763
9822
  const navigateChild = (...params) => {
9764
- var _a2, _b;
9823
+ var _a, _b;
9765
9824
  const to = params[0];
9766
9825
  if (typeof to === "number") {
9767
9826
  lastDirectionRef.current = to > 0 ? "forward" : "back";
@@ -9774,7 +9833,7 @@ function InBrowserBrowserForRealzImpl({ baseUrl, id, name, initialRoute }, ref)
9774
9833
  lastDirectionTimeout.current = setTimeout(() => {
9775
9834
  lastDirectionRef.current = "new";
9776
9835
  }, 100);
9777
- (_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(
9778
9837
  { type: "epicshop:navigate-call", params },
9779
9838
  "*"
9780
9839
  );
@@ -10172,6 +10231,18 @@ const mdxComponents$3 = {
10172
10231
  // override the pre-with-buttons
10173
10232
  pre
10174
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
+ }
10175
10246
  function Diff({
10176
10247
  diff,
10177
10248
  allApps
@@ -10260,7 +10331,8 @@ function Diff({
10260
10331
  `${diff2.app1}${diff2.app2}`
10261
10332
  )
10262
10333
  ] }),
10263
- /* @__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 })
10264
10336
  ] })
10265
10337
  }
10266
10338
  )
@@ -10400,13 +10472,13 @@ const EpicForumResponseSchema = z.object({
10400
10472
  })
10401
10473
  );
10402
10474
  async function fetchDiscordPosts({ request }) {
10403
- var _a2;
10475
+ var _a;
10404
10476
  const config = getWorkshopConfig();
10405
10477
  const forceFresh = await shouldForceFresh({ request });
10406
10478
  const searchParams = new URLSearchParams({
10407
10479
  channelId: config.product.discordChannelId
10408
10480
  });
10409
- if ((_a2 = config.product.discordTags) == null ? void 0 : _a2.length) {
10481
+ if ((_a = config.product.discordTags) == null ? void 0 : _a.length) {
10410
10482
  for (const tag of config.product.discordTags) {
10411
10483
  searchParams.append("tagId", tag);
10412
10484
  }
@@ -10744,8 +10816,8 @@ function InBrowserTestRunner({
10744
10816
  const [testSteps, setTestSteps] = useState([]);
10745
10817
  useEffect(() => {
10746
10818
  function handleMessage(messageEvent) {
10747
- var _a2;
10748
- 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;
10749
10821
  if ("request" in messageEvent.data) return;
10750
10822
  const result = testRunnerDataSchema.safeParse(messageEvent.data, {
10751
10823
  path: ["messageEvent", "data"]
@@ -10850,8 +10922,8 @@ function InBrowserTestRunner({
10850
10922
  "button",
10851
10923
  {
10852
10924
  onClick: () => {
10853
- var _a2, _b;
10854
- 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();
10855
10927
  },
10856
10928
  className: "border-r p-3",
10857
10929
  children: /* @__PURE__ */ jsx(Icon, { name: "Refresh", "aria-label": "Rerun Tests" })
@@ -10927,7 +10999,7 @@ async function loader$j({ request }) {
10927
10999
  return json$1({ error: "App is not running tests" }, { status: 404 });
10928
11000
  }
10929
11001
  return eventStream(request.signal, function setup(send) {
10930
- var _a2, _b;
11002
+ var _a, _b;
10931
11003
  let queue = [];
10932
11004
  function sendEvent(event) {
10933
11005
  queue.push(event);
@@ -10969,18 +11041,18 @@ async function loader$j({ request }) {
10969
11041
  });
10970
11042
  }
10971
11043
  function handleExit(code) {
10972
- var _a3, _b2;
10973
- (_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);
10974
11046
  (_b2 = testProcess == null ? void 0 : testProcess.stderr) == null ? void 0 : _b2.off("data", handleStdErrData);
10975
11047
  testProcess == null ? void 0 : testProcess.off("exit", handleExit);
10976
11048
  sendEvent({ type: "exit", isRunning: false, code });
10977
11049
  }
10978
- (_a2 = testProcess.stdout) == null ? void 0 : _a2.on("data", handleStdOutData);
11050
+ (_a = testProcess.stdout) == null ? void 0 : _a.on("data", handleStdOutData);
10979
11051
  (_b = testProcess.stderr) == null ? void 0 : _b.on("data", handleStdErrData);
10980
11052
  testProcess.on("exit", handleExit);
10981
11053
  return function cleanup() {
10982
- var _a3, _b2;
10983
- (_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);
10984
11056
  (_b2 = testProcess.stderr) == null ? void 0 : _b2.off("data", handleStdErrData);
10985
11057
  testProcess.off("exit", handleExit);
10986
11058
  clearInterval(interval);
@@ -10988,7 +11060,7 @@ async function loader$j({ request }) {
10988
11060
  });
10989
11061
  }
10990
11062
  async function action$3({ request }) {
10991
- var _a2;
11063
+ var _a;
10992
11064
  ensureUndeployed();
10993
11065
  const formData = await request.formData();
10994
11066
  const userHasAccess = await userHasAccessToWorkshop({
@@ -11037,7 +11109,7 @@ async function action$3({ request }) {
11037
11109
  case "stop": {
11038
11110
  const processEntry = getTestProcessEntry(app);
11039
11111
  if (processEntry) {
11040
- (_a2 = processEntry.process) == null ? void 0 : _a2.kill();
11112
+ (_a = processEntry.process) == null ? void 0 : _a.kill();
11041
11113
  }
11042
11114
  return jsonWithPE(formData, { success: true });
11043
11115
  }
@@ -11160,8 +11232,8 @@ function TestRunner({
11160
11232
  latestOnRun.current = onRun;
11161
11233
  }, [onRun]);
11162
11234
  useEffect(() => {
11163
- var _a2, _b;
11164
- if ((_a2 = fetcher.data) == null ? void 0 : _a2.success) {
11235
+ var _a, _b;
11236
+ if ((_a = fetcher.data) == null ? void 0 : _a.success) {
11165
11237
  (_b = latestOnRun.current) == null ? void 0 : _b.call(latestOnRun);
11166
11238
  }
11167
11239
  }, [fetcher.data]);
@@ -11197,8 +11269,8 @@ function ClearTest({
11197
11269
  latestOnClear.current = onClear;
11198
11270
  }, [onClear]);
11199
11271
  useEffect(() => {
11200
- var _a2, _b;
11201
- if ((_a2 = fetcher.data) == null ? void 0 : _a2.success) {
11272
+ var _a, _b;
11273
+ if ((_a = fetcher.data) == null ? void 0 : _a.success) {
11202
11274
  (_b = latestOnClear.current) == null ? void 0 : _b.call(latestOnClear);
11203
11275
  }
11204
11276
  }, [fetcher.data]);
@@ -11234,8 +11306,8 @@ function StopTest({
11234
11306
  latestOnStop.current = onStop;
11235
11307
  }, [onStop]);
11236
11308
  useEffect(() => {
11237
- var _a2, _b;
11238
- if ((_a2 = fetcher.data) == null ? void 0 : _a2.success) {
11309
+ var _a, _b;
11310
+ if ((_a = fetcher.data) == null ? void 0 : _a.success) {
11239
11311
  (_b = latestOnStop.current) == null ? void 0 : _b.call(latestOnStop);
11240
11312
  }
11241
11313
  }, [fetcher.data]);
@@ -11335,7 +11407,7 @@ function TestUI({
11335
11407
  return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-lg", children: /* @__PURE__ */ jsx("p", { children: "No tests here 😢 Sorry." }) });
11336
11408
  }
11337
11409
  async function loader$i({ request, params }) {
11338
- const timings = makeTimings("exerciseStepTypeLoader");
11410
+ const timings = makeTimings("exerciseStepTypeIndexLoader");
11339
11411
  const userHasAccess = await userHasAccessToWorkshop({
11340
11412
  request,
11341
11413
  timings
@@ -11497,7 +11569,7 @@ function withParam(searchParams, key, value) {
11497
11569
  return newSearchParams;
11498
11570
  }
11499
11571
  function ExercisePartRoute() {
11500
- var _a2, _b, _c, _d, _e, _f;
11572
+ var _a, _b, _c, _d, _e, _f;
11501
11573
  const data = useLoaderData();
11502
11574
  const [searchParams] = useSearchParams();
11503
11575
  const preview = searchParams.get("preview");
@@ -11505,12 +11577,12 @@ function ExercisePartRoute() {
11505
11577
  const altDown = useAltDown();
11506
11578
  const navigate = useNavigate();
11507
11579
  function shouldHideTab(tab) {
11508
- var _a3, _b2, _c2;
11580
+ var _a2, _b2, _c2;
11509
11581
  if (tab === "tests") {
11510
11582
  return ENV.EPICSHOP_DEPLOYED || !data.playground || data.playground.test.type === "none";
11511
11583
  }
11512
11584
  if (tab === "problem" || tab === "solution") {
11513
- 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;
11514
11586
  if (ENV.EPICSHOP_DEPLOYED) {
11515
11587
  return ((_b2 = data[tab]) == null ? void 0 : _b2.dev.type) !== "browser" && !((_c2 = data[tab]) == null ? void 0 : _c2.stackBlitzUrl);
11516
11588
  }
@@ -11520,7 +11592,7 @@ function ExercisePartRoute() {
11520
11592
  }
11521
11593
  const activeTab = isValidPreview(preview) ? preview : tabs.find((t) => !shouldHideTab(t));
11522
11594
  const altDiffUrl = `/diff?${new URLSearchParams({
11523
- app1: ((_a2 = data.problem) == null ? void 0 : _a2.name) ?? "",
11595
+ app1: ((_a = data.problem) == null ? void 0 : _a.name) ?? "",
11524
11596
  app2: ((_b = data.solution) == null ? void 0 : _b.name) ?? ""
11525
11597
  })}`;
11526
11598
  function handleDiffTabClick(event) {
@@ -11712,9 +11784,9 @@ const meta$2 = ({
11712
11784
  data,
11713
11785
  matches
11714
11786
  }) => {
11715
- var _a2;
11787
+ var _a;
11716
11788
  const number = data == null ? void 0 : data.exercise.exerciseNumber.toString().padStart(2, "0");
11717
- 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;
11718
11790
  if (!data || !rootData) return [{ title: "🦉 | Error" }];
11719
11791
  return getSeoMetaTags({
11720
11792
  title: `🦉 | ${number}. ${data.exercise.title} | ${rootData == null ? void 0 : rootData.workshopTitle}`,
@@ -11762,10 +11834,10 @@ async function loader$f({ request, params }) {
11762
11834
  exercise.finishedEpicVideoEmbeds,
11763
11835
  { request }
11764
11836
  ),
11765
- exerciseFinished: exercise.finishedCode ? {
11837
+ exerciseFinished: {
11766
11838
  file: finishedFilepath,
11767
11839
  relativePath: `exercises/${exercise.dirName}/FINISHED.mdx`
11768
- } : null,
11840
+ },
11769
11841
  prevStepLink: prevApp ? {
11770
11842
  to: getAppPageRoute(prevApp),
11771
11843
  "aria-label": `${prevApp.title} (${prevApp.type})`
@@ -11796,6 +11868,9 @@ const mdxComponents$2 = { h1: () => null };
11796
11868
  function ExerciseFinished$1() {
11797
11869
  const data = useLoaderData();
11798
11870
  const exerciseNumber = data.exercise.exerciseNumber.toString().padStart(2, "0");
11871
+ useRevalidationWS({
11872
+ watchPaths: [`./exercises/${exerciseNumber}/FINISHED.mdx`]
11873
+ });
11799
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: [
11800
11875
  /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col sm:col-span-1 sm:row-span-1 sm:h-full lg:border-r", children: [
11801
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: [
@@ -11837,13 +11912,13 @@ function ExerciseFinished$1() {
11837
11912
  ),
11838
11913
  /* @__PURE__ */ jsxs("div", { className: "flex h-16 justify-between border-b-4 border-t lg:border-b-0", children: [
11839
11914
  /* @__PURE__ */ jsx("div", {}),
11840
- data.exerciseFinished ? /* @__PURE__ */ jsx(
11915
+ /* @__PURE__ */ jsx(
11841
11916
  EditFileOnGitHub,
11842
11917
  {
11843
11918
  file: data.exerciseFinished.file,
11844
11919
  relativePath: data.exerciseFinished.relativePath
11845
11920
  }
11846
- ) : null,
11921
+ ),
11847
11922
  /* @__PURE__ */ jsx(NavChevrons, { prev: data.prevStepLink, next: data.nextStepLink })
11848
11923
  ] })
11849
11924
  ] }),
@@ -11895,8 +11970,8 @@ const handle$4 = {
11895
11970
  const meta$1 = ({
11896
11971
  matches
11897
11972
  }) => {
11898
- var _a2;
11899
- 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;
11900
11975
  if (!rootData) return [];
11901
11976
  return getSeoMetaTags({
11902
11977
  title: `🎉 ${rootData == null ? void 0 : rootData.workshopTitle}`,
@@ -11958,6 +12033,7 @@ const headers$2 = ({ loaderHeaders, parentHeaders }) => {
11958
12033
  const mdxComponents$1 = { h1: () => null };
11959
12034
  function ExerciseFinished() {
11960
12035
  const data = useLoaderData();
12036
+ useRevalidationWS({ watchPaths: ["./exercises/FINISHED.mdx"] });
11961
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: [
11962
12038
  /* @__PURE__ */ jsxs("div", { className: "relative col-span-1 row-span-1 flex h-full flex-col lg:border-r", children: [
11963
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: [
@@ -12290,7 +12366,7 @@ async function action$2() {
12290
12366
  return json$1({ status: "pending" });
12291
12367
  }
12292
12368
  function Login() {
12293
- var _a2;
12369
+ var _a;
12294
12370
  const {
12295
12371
  product: { displayName }
12296
12372
  } = useWorkshopConfig();
@@ -12384,7 +12460,7 @@ function Login() {
12384
12460
  "."
12385
12461
  ] })
12386
12462
  ] }),
12387
- /* @__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...` }) })
12388
12464
  ] }),
12389
12465
  authError ? /* @__PURE__ */ jsxs("div", { className: "mt-4 text-red-500", children: [
12390
12466
  "There was an error: ",
@@ -12404,10 +12480,10 @@ const handle$2 = {
12404
12480
  getSitemapEntries: () => null
12405
12481
  };
12406
12482
  function Support() {
12407
- var _a2;
12408
- const repoGroups = (_a2 = ENV.EPICSHOP_GITHUB_REPO.match(
12483
+ var _a;
12484
+ const repoGroups = (_a = ENV.EPICSHOP_GITHUB_REPO.match(
12409
12485
  /github\.com\/(?<org>[^/?]+)\/(?<repo>[^/?]+)/
12410
- )) == null ? void 0 : _a2.groups;
12486
+ )) == null ? void 0 : _a.groups;
12411
12487
  let repoUrl = ENV.EPICSHOP_GITHUB_REPO;
12412
12488
  let repoIssuesUrl = repoUrl;
12413
12489
  if ((repoGroups == null ? void 0 : repoGroups.org) && repoGroups.repo) {
@@ -12492,8 +12568,8 @@ const handle$1 = {
12492
12568
  const meta = ({
12493
12569
  matches
12494
12570
  }) => {
12495
- var _a2;
12496
- 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;
12497
12573
  return [{ title: `👷 | ${rootData == null ? void 0 : rootData.workshopTitle}` }];
12498
12574
  };
12499
12575
  async function loader$a({ request }) {
@@ -12576,11 +12652,11 @@ function linkProgress(progress) {
12576
12652
  }
12577
12653
  }
12578
12654
  function AdminLayout() {
12579
- var _a2, _b;
12655
+ var _a, _b;
12580
12656
  const data = useLoaderData();
12581
12657
  const navigation = useNavigation();
12582
12658
  const epicProgress = useEpicProgress();
12583
- 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";
12584
12660
  const isStoppingInspector = ((_b = navigation.formData) == null ? void 0 : _b.get("intent")) === "stop-inspect";
12585
12661
  const progressStatus = {
12586
12662
  completed: "bg-blue-500",
@@ -12739,7 +12815,7 @@ const route27 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
12739
12815
  loader: loader$7
12740
12816
  }, Symbol.toStringTag, { value: "Module" }));
12741
12817
  async function loader$6({ request }) {
12742
- var _a2, _b, _c, _d;
12818
+ var _a, _b, _c, _d;
12743
12819
  const reqUrl = new URL(request.url);
12744
12820
  const searchParams = reqUrl.searchParams;
12745
12821
  const timings = makeTimings("diffLoader");
@@ -12783,7 +12859,7 @@ async function loader$6({ request }) {
12783
12859
  const prevApp2Index = prevApp1Index + 1;
12784
12860
  const nextApp1Index = usingDefaultApp1 ? 0 : app1Index + 1 < allApps.length ? app1Index + 1 : -2;
12785
12861
  const nextApp2Index = nextApp1Index + 1;
12786
- const prevApp1 = (_a2 = allAppsFull[prevApp1Index]) == null ? void 0 : _a2.name;
12862
+ const prevApp1 = (_a = allAppsFull[prevApp1Index]) == null ? void 0 : _a.name;
12787
12863
  const prevApp2 = (_b = allAppsFull[prevApp2Index]) == null ? void 0 : _b.name;
12788
12864
  const nextApp1 = (_c = allAppsFull[nextApp1Index]) == null ? void 0 : _c.name;
12789
12865
  const nextApp2 = (_d = allAppsFull[nextApp2Index]) == null ? void 0 : _d.name;
@@ -13267,7 +13343,7 @@ const route39 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
13267
13343
  __proto__: null,
13268
13344
  loader
13269
13345
  }, Symbol.toStringTag, { value: "Module" }));
13270
- 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-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-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-1JworToC.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-yrWoJVFy.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/mdx-CpyquP9i.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-CKnDy_9Z.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-yrWoJVFy.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-CpyquP9i.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-Ccssehd9.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-yrWoJVFy.js", "/assets/progress-bar-D3kudPcr.js", "/assets/accordion-OfO-5m5D.js", "/assets/mdx-CpyquP9i.js", "/assets/use-event-source-A_0lEOPX.js", "/assets/set-playground-DW0yVaNn.js", "/assets/button-EE0aPg10.js", "/assets/diff-jgZ_RGra.js", "/assets/error-boundary-COkPRBOZ.js", "/assets/discord-BRTW4Rnh.js", "/assets/index-B-hHvmeV.js", "/assets/tests-BR7RFlr5.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-DbZkv25C.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-yrWoJVFy.js", "/assets/accordion-OfO-5m5D.js", "/assets/use-event-source-A_0lEOPX.js", "/assets/set-playground-DW0yVaNn.js", "/assets/tests-BR7RFlr5.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-B4D2ZAmj.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-yrWoJVFy.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/nav-chevrons-g-C0ilNz.js", "/assets/mdx-CpyquP9i.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-Ct6I1SZV.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-yrWoJVFy.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/nav-chevrons-g-C0ilNz.js", "/assets/mdx-CpyquP9i.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-R-sUGQfT.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-yrWoJVFy.js", "/assets/progress-bar-D3kudPcr.js", "/assets/index-Dx5GmdYq.js", "/assets/error-boundary-COkPRBOZ.js", "/assets/mdx-CpyquP9i.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-Cvtc-qzL.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-yrWoJVFy.js", "/assets/progress-bar-D3kudPcr.js", "/assets/accordion-OfO-5m5D.js", "/assets/mdx-CpyquP9i.js", "/assets/diff-jgZ_RGra.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-B7TLVi2l.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-yrWoJVFy.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-f5f44d87.js", "version": "f5f44d87" };
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" };
13271
13347
  const mode = "production";
13272
13348
  const assetsBuildDirectory = "build/client";
13273
13349
  const basename = "/";