[
  {
    "path": ".gitignore",
    "content": "node_modules/\ndist/\n.DS_Store\n"
  },
  {
    "path": ".oxfmtrc.json",
    "content": "{\n  \"$schema\": \"./node_modules/oxfmt/configuration_schema.json\",\n  \"arrowParens\": \"always\",\n  \"htmlWhitespaceSensitivity\": \"ignore\",\n  \"printWidth\": 120,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"all\",\n  \"useTabs\": false,\n  \"quoteProps\": \"consistent\",\n  \"ignorePatterns\": [\"node_modules/\", \"dist/\", \"pnpm-lock.yaml\"],\n  \"sortTailwindcss\": {\n    \"config\": \"uno.config.js\"\n  }\n}\n"
  },
  {
    "path": ".oxlintrc.json",
    "content": "{\n  \"jsPlugins\": [\"./scripts/oxlint-plugin-unocss.ts\"],\n  \"rules\": {\n    \"unocss/valid-class\": \"error\",\n    \"unocss/no-conflicting-classes\": \"error\",\n    \"eslint/no-unassigned-vars\": \"off\"\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 SolidJS Core Team\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p>\n  <img width=\"100%\" src=\"https://assets.solidjs.com/banner?project=Playground&type=core\" alt=\"Solid Playground\">\n</p>\n\n# Solid Template Explorer\n\nThis is the source code of the [solid playground](https://playground.solidjs.com) website.\nThrough it you can quickly discover what the solid compiler will generate from your JSX templates.\n\nThere are 3 modes available:\n\n- DOM: The classic SPA generation mechanism\n- SSR: The server side generation mechanism\n- HYDRATION: The client side generation for hydration\n- UNIVERSAL: The client side generation for universal (custom renderer)\n\n## Getting up and running\n\nThis project is built using the [pnpm](https://pnpm.js.org/) package manager.\n\nOnce you got it up and running you can follow these steps the have a fully working environement:\n\n```bash\n# Clone the project\n$ git clone https://github.com/solidjs/solid-playground\n\n# cd into the project and install the dependencies\n$ cd solid-playground && pnpm i\n\n# Start the dev server, the address is available at http://localhost:5173\n$ pnpm run dev\n\n# Build the project\n$ pnpm run build\n```\n\n## Credits / Technologies used\n\n- [solid-js](https://github.com/solidjs/solid/): The view library\n- [@babel/standalone](https://babeljs.io/docs/en/babel-standalone): The in-browser compiler. Solid compiler relies on babel\n- [monaco](https://microsoft.github.io/monaco-editor/): The in-browser code editor. This is the code editor that powers VS Code\n- [Windi CSS](https://windicss.org/): The CSS framework\n- [vite](https://vitejs.dev/): The module bundler\n- [workbox](https://developers.google.com/web/tools/workbox): The service worker generator\n- [pnpm](https://pnpm.js.org/): The package manager\n- [lz-string](https://github.com/pieroxy/lz-string): The string compression algorithm used to share REPL\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"solid-playground-restructured\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"keywords\": [],\n  \"author\": \"\",\n  \"workspaces\": [\n    \"./packages/*\"\n  ],\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"cd packages/playground && pnpm start\",\n    \"build\": \"cd packages/playground && pnpm build\",\n    \"dev\": \"cd packages/playground && pnpm dev\",\n    \"format\": \"oxfmt .\",\n    \"lint\": \"oxlint .\"\n  },\n  \"devDependencies\": {\n    \"@changesets/cli\": \"2.31.0\",\n    \"@oxlint/plugins\": \"^1.61.0\",\n    \"@unocss/preset-wind3\": \"^66.6.8\",\n    \"jiti\": \"^2.6.1\",\n    \"oxfmt\": \"^0.46.0\",\n    \"oxlint\": \"^1.61.0\",\n    \"synckit\": \"^0.11.12\",\n    \"unocss\": \"^66.6.8\"\n  },\n  \"packageManager\": \"pnpm@10.33.2\",\n  \"pnpm\": {\n    \"patchedDependencies\": {\n      \"monaco-editor\": \"patches/monaco-editor.patch\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/playground/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n    <link rel=\"shortcut icon\" href=\"/logo.png\" type=\"image/png\" />\n    <title>Solid Playground</title>\n    <meta name=\"description\" content=\"Quickly discover what the solid compiler will generate from your JSX template\" />\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"update\" class=\"bottom-10 left-10 z-12 fixed\"></div>\n    <script src=\"./src/index.tsx\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/playground/package.json",
    "content": "{\n  \"name\": \"solid-playground\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"start\": \"vite preview\",\n    \"dev\": \"vite\",\n    \"tsc\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@solid-primitives/scheduled\": \"^1.5.3\",\n    \"@solidjs/router\": \"^0.16.1\",\n    \"dedent\": \"^1.7.2\",\n    \"solid-dismiss\": \"^1.8.2\",\n    \"solid-heroicons\": \"^3.2.4\",\n    \"solid-js\": \"1.9.12\",\n    \"solid-repl\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@amoutonbrady/lz-string\": \"^0.1.0\",\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/plugin-syntax-jsx\": \"^7.28.6\",\n    \"@babel/preset-typescript\": \"^7.28.5\",\n    \"@babel/types\": \"^7.29.0\",\n    \"@solidjs/router\": \"^0.16.1\",\n    \"@types/babel__standalone\": \"^7.1.9\",\n    \"@types/dedent\": \"^0.7.2\",\n    \"assert\": \"^2.1.0\",\n    \"csstype\": \"^3.2.3\",\n    \"jszip\": \"^3.10.1\",\n    \"monaco-editor\": \"^0.55.1\",\n    \"register-service-worker\": \"^1.7.2\",\n    \"typescript\": \"^6.0.3\",\n    \"unocss\": \"^66.6.8\",\n    \"vite\": \"^8.0.10\",\n    \"vite-plugin-solid\": \"^2.11.12\"\n  }\n}\n"
  },
  {
    "path": "packages/playground/public/_redirects",
    "content": "/*    /index.html   200\n"
  },
  {
    "path": "packages/playground/public/manifest.webmanifest",
    "content": "{\n  \"dir\": \"ltr\",\n  \"lang\": \"en\",\n  \"name\": \"Solid REPL\",\n  \"short_name\": \"Solid REPL\",\n  \"scope\": \"/\",\n  \"display\": \"standalone\",\n  \"start_url\": \"/\",\n  \"background_color\": \"transparent\",\n  \"theme_color\": \"transparent\",\n  \"description\": \"Solid REPL\",\n  \"orientation\": \"any\",\n  \"prefer_related_applications\": false,\n  \"icons\": [\n    {\n      \"src\": \"/square_logo.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/playground/public/robots.txt",
    "content": "User-agent: *\nAllow: /"
  },
  {
    "path": "packages/playground/public/sw.js",
    "content": "const cacheName = 'my-cache';\n\nasync function notifyClient(event) {\n  const client = event.clientId ? await clients.get(event.clientId) : null;\n  if (client) {\n    client.postMessage({ type: 'cache' });\n    return;\n  }\n  const all = await clients.matchAll();\n  for (const c of all) c.postMessage({ type: 'cache' });\n}\n\nfunction responsesDiffer(cached, fresh) {\n  const cachedTag = cached.headers.get('etag') || cached.headers.get('last-modified');\n  const freshTag = fresh.headers.get('etag') || fresh.headers.get('last-modified');\n  if (cachedTag && freshTag) return cachedTag !== freshTag;\n  return false;\n}\n\nasync function fetchAndCache(cache, event) {\n  try {\n    const response = await fetch(event.request);\n    if (response.ok) {\n      await cache.put(event.request, response.clone());\n    }\n    return response;\n  } catch (e) {\n    console.error(e);\n    if (event.request.mode === 'navigate') {\n      return await cache.match('/index.html');\n    }\n    throw e;\n  }\n}\n\nasync function fetchWithCache(event) {\n  const cache = await caches.open(cacheName);\n  const cached = await cache.match(event.request);\n  const fresh = fetchAndCache(cache, event);\n  if (cached) {\n    fresh.then((response) => {\n      if (response && responsesDiffer(cached, response)) {\n        notifyClient(event);\n      }\n    });\n    return cached;\n  }\n  return fresh;\n}\n\nfunction handleFetch(event) {\n  if (\n    event.request.headers.get('cache-control') !== 'no-cache' &&\n    event.request.method === 'GET' &&\n    event.request.url.startsWith(self.location.origin)\n  ) {\n    event.respondWith(fetchWithCache(event));\n  }\n}\n\nself.addEventListener('fetch', handleFetch);\n\nself.addEventListener('install', () => {\n  self.skipWaiting();\n});\n\nself.addEventListener('activate', (event) => {\n  event.waitUntil(\n    (async () => {\n      const keys = await caches.keys();\n      await Promise.all(keys.filter((k) => k !== cacheName).map((k) => caches.delete(k)));\n      await clients.claim();\n    })(),\n  );\n});\n"
  },
  {
    "path": "packages/playground/src/app.tsx",
    "content": "import { Show, JSX, Suspense } from 'solid-js';\nimport { Route, Router } from '@solidjs/router';\nimport { eventBus, setEventBus } from './utils/serviceWorker';\nimport { Update } from './components/update';\nimport { useZoom } from 'solid-repl/src/hooks/useZoom';\nimport { Edit } from './pages/edit';\nimport { Home } from './pages/home';\nimport { Login } from './pages/login';\nimport { AppContextProvider } from './context';\n\nexport const App = (): JSX.Element => {\n  /**\n   * Those next three lines are useful to display a popup\n   * if the client code has been updated. This trigger a signal\n   * via an EventBus initiated in the service worker and\n   * the couple line above.\n   */\n  const { zoomState, updateZoom } = useZoom();\n  document.addEventListener('keydown', (e) => {\n    if (!zoomState.overrideNative) return;\n    if (!(e.ctrlKey || e.metaKey)) return;\n\n    if (e.key === '=') {\n      updateZoom('increase');\n      e.preventDefault();\n    } else if (e.key == '-') {\n      updateZoom('decrease');\n      e.preventDefault();\n    }\n  });\n\n  return (\n    <div class=\"text-neutral-900 dark:text-neutral-50 bg-neutral-100 dark:bg-neutral-950 relative flex h-screen flex-col overflow-auto font-sans\">\n      <Router\n        root={(props) => (\n          <AppContextProvider>\n            <Suspense>{props.children}</Suspense>\n          </AppContextProvider>\n        )}\n      >\n        <Route path={['/', '/:user/:repl']} component={Edit} />\n        <Route path=\"/:user\" component={Home} />\n        <Route path=\"/login\" component={Login} />\n      </Router>\n\n      <Show when={eventBus()} children={<Update onDismiss={() => setEventBus(false)} />} />\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/playground/src/components/header.tsx",
    "content": "import Dismiss from 'solid-dismiss';\nimport { A } from '@solidjs/router';\nimport { Icon } from 'solid-heroicons';\nimport { unwrap } from 'solid-js/store';\nimport { onCleanup, createSignal, Show, ParentComponent, children } from 'solid-js';\nimport { share, link, arrowDownTray, xCircle, bars_3, moon, sun } from 'solid-heroicons/outline';\nimport { exportToZip } from '../utils/exportFiles';\nimport { ZoomDropdown } from './zoomDropdown';\nimport { API, useAppContext } from '../context';\nimport { Button, LinkButton } from 'solid-repl/src/components/ui/Button';\n\nimport logo from '../assets/logo.svg?url';\n\nexport const Header: ParentComponent<{\n  compiler?: Worker;\n  fork?: () => void;\n  share: () => Promise<string>;\n}> = (props) => {\n  const [copy, setCopy] = createSignal(false);\n  const context = useAppContext()!;\n  const [showMenu, setShowMenu] = createSignal(false);\n  const [showProfile, setShowProfile] = createSignal(false);\n  const resolved = children(() => props.children);\n  let menuBtnEl!: HTMLButtonElement;\n  let profileBtn!: HTMLButtonElement;\n\n  function shareLink() {\n    props.share().then((url) => {\n      navigator.clipboard.writeText(url).then(() => {\n        setCopy(true);\n        setTimeout(setCopy, 750, false);\n      });\n    });\n  }\n\n  window.addEventListener('resize', closeMobileMenu);\n  onCleanup(() => {\n    window.removeEventListener('resize', closeMobileMenu);\n  });\n\n  function closeMobileMenu() {\n    setShowMenu(false);\n  }\n\n  const menuButtonClasses = (show: boolean) => ({\n    'rounded-none active:bg-gray-300 hover:bg-gray-300 dark:hover:text-black': show,\n  });\n\n  return (\n    <header class=\"top-0 z-12 gap-x-4 p-1 px-2 bg-neutral-100 dark:bg-neutral-950 sticky flex items-center text-sm\">\n      <A href={`/${context.profile()}`}>\n        <img src={logo} alt=\"solid-js logo\" class=\"w-8\" />\n      </A>\n      {resolved() || (\n        <h1 class=\"leading-0 uppercase tracking-widest\">\n          Solid<b>JS</b> Playground\n        </h1>\n      )}\n      <div class=\"space-x-2 ml-auto flex items-center\">\n        <Dismiss\n          classList={{\n            'absolute top-[53px] right-[10px] z-10 w-fit': showMenu(),\n            'flex flex-col justify-center border-neutral-200 bg-white dark:border-neutral-700 rounded-lg border shadow-lg dark:bg-neutral-900':\n              showMenu(),\n            'hidden': !showMenu(),\n          }}\n          class=\"md:space-x-2 md:flex md:flex-row md:items-center\"\n          menuButton={() => menuBtnEl}\n          open={showMenu}\n          setOpen={setShowMenu}\n          show\n        >\n          <Button onClick={context.toggleDark} classList={menuButtonClasses(showMenu())} title=\"Toggle dark mode\">\n            <Show when={context.dark()} fallback={<Icon path={moon} class=\"h-6\" />}>\n              <Icon path={sun} class=\"h-6\" />\n            </Show>\n            <span class=\"text-sm md:sr-only\">{context.dark() ? 'Light' : 'Dark'} mode</span>\n          </Button>\n\n          <Show when={context.tabs()}>\n            <Button\n              onClick={() => exportToZip(unwrap(context.tabs())!)}\n              classList={menuButtonClasses(showMenu())}\n              title=\"Export to Zip\"\n            >\n              <Icon path={arrowDownTray} class=\"h-6\" style={{ margin: '0' }} />\n              <span class=\"text-sm md:sr-only\">Export to Zip</span>\n            </Button>\n          </Show>\n\n          <ZoomDropdown showMenu={showMenu()} />\n\n          <Button\n            onClick={shareLink}\n            classList={{\n              ...menuButtonClasses(showMenu()),\n              'opacity-80 hover:opacity-100': !copy(),\n              'text-green-100': copy() && !showMenu(),\n            }}\n            title=\"Share with a minified link\"\n          >\n            <Icon class=\"h-6\" path={copy() ? link : share} />\n            <span class=\"text-sm md:sr-only\">{copy() ? 'Copied to clipboard' : 'Share'}</span>\n          </Button>\n\n          <LinkButton\n            href=\"https://github.com/solidjs/solid-playground\"\n            target=\"_blank\"\n            classList={menuButtonClasses(showMenu())}\n            title=\"Github\"\n            class=\"cursor-alias\"\n          >\n            <Icon\n              viewBox=\"0 0 96 96\"\n              class=\"h-6\"\n              path={{\n                path: (\n                  <path\n                    fill-rule=\"evenodd\"\n                    clip-rule=\"evenodd\"\n                    d=\"M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z\"\n                  />\n                ),\n                outline: false,\n                mini: false,\n              }}\n            />\n            <span class=\"text-sm md:sr-only\">Github</span>\n          </LinkButton>\n        </Dismiss>\n        <Button\n          type=\"button\"\n          classList={{\n            'border-white border': showMenu(),\n          }}\n          class=\"md:hidden\"\n          variant=\"ghost\"\n          title=\"Mobile Menu Button\"\n          ref={menuBtnEl}\n        >\n          <Show when={showMenu()} fallback={<Icon path={bars_3} class=\"h-6 w-6\" />}>\n            <Icon path={xCircle} class=\"h-[22px] w-[22px]\" />\n          </Show>\n          <span class=\"sr-only\">Show menu</span>\n        </Button>\n        <div class=\"relative flex shrink-0 cursor-pointer items-center leading-snug\">\n          <Show\n            when={context.user()?.avatar}\n            fallback={\n              <a\n                class=\"mx-1 px-3 py-1.5 text-white rounded-md bg-solidc text-sm hover:bg-solidc/90\"\n                href={`${API}/auth/login?redirect=${window.location.origin}/login?auth=success`}\n                rel=\"external\"\n              >\n                Login\n              </a>\n            }\n          >\n            <button ref={profileBtn}>\n              <img crossOrigin=\"anonymous\" src={context.user()?.avatar} class=\"h-8 w-8 rounded-full\" />\n            </button>\n            <Dismiss menuButton={() => profileBtn} open={showProfile} setOpen={setShowProfile}>\n              <div class=\"right-0 mt-2 bg-white border-neutral-200 dark:border-neutral-700 dark:bg-neutral-900 absolute flex flex-col items-center justify-center overflow-hidden rounded-lg border shadow-lg\">\n                <a\n                  class=\"w-full px-4 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 border-neutral-200 dark:border-neutral-700 whitespace-nowrap border-b text-center text-sm\"\n                  href={`/${context.profile()}`}\n                >\n                  {context.user()?.display}\n                </a>\n                <button\n                  onClick={() => (context.token = '')}\n                  class=\"w-full px-4 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 whitespace-nowrap text-center text-sm\"\n                >\n                  Sign Out\n                </button>\n              </div>\n            </Dismiss>\n          </Show>\n        </div>\n      </div>\n    </header>\n  );\n};\n"
  },
  {
    "path": "packages/playground/src/components/setupSolid.ts",
    "content": "import { typescript } from 'monaco-editor';\n\nconst solidTypes: Record<string, string> = import.meta.glob('/node_modules/{solid-js,csstype}/**/*.{d.ts,json}', {\n  eager: true,\n  query: '?raw',\n  import: 'default',\n});\n\nfor (const path in solidTypes) {\n  typescript.typescriptDefaults.addExtraLib(solidTypes[path], `file://${path}`);\n  typescript.javascriptDefaults.addExtraLib(solidTypes[path], `file://${path}`);\n}\n\nimport repl from 'solid-repl/src/repl';\nexport default repl;\n"
  },
  {
    "path": "packages/playground/src/components/update.tsx",
    "content": "import type { Component } from 'solid-js';\nimport { Portal } from 'solid-js/web';\n\nimport { Icon } from 'solid-heroicons';\nimport { xMark } from 'solid-heroicons/outline';\n\nexport const Update: Component<{\n  onDismiss: (...args: unknown[]) => unknown;\n}> = (props) => {\n  const mount = document.getElementById('update');\n\n  return (\n    <Portal mount={mount!}>\n      <div class=\"z-10 border-neutral-200 bg-white px-6 py-4 dark:border-neutral-700 dark:text-white dark:bg-neutral-900 relative max-w-sm rounded-lg border text-solidc shadow-lg\">\n        <button\n          title=\"close\"\n          onClick={props.onDismiss}\n          class=\"top-2 right-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 p-1 absolute rounded-md\"\n        >\n          <Icon path={xMark} class=\"h-5\" />\n        </button>\n        <p class=\"pr-6 font-semibold\">There's a new update available.</p>\n        <p class=\"mt-2 text-sm opacity-80\">\n          Refresh your browser or click the button below to get the latest update of the REPL.\n        </p>\n        <button\n          onClick={() => location.reload()}\n          class=\"mt-4 px-3 py-1.5 text-white rounded-md bg-solidc text-sm hover:bg-solidc/90\"\n        >\n          Refresh\n        </button>\n      </div>\n    </Portal>\n  );\n};\n"
  },
  {
    "path": "packages/playground/src/components/zoomDropdown.tsx",
    "content": "import { Icon } from 'solid-heroicons';\nimport { magnifyingGlassPlus, minus, plus } from 'solid-heroicons/outline';\nimport Dismiss from 'solid-dismiss';\nimport { Component, createSignal, createEffect } from 'solid-js';\nimport { useZoom } from 'solid-repl/src/hooks/useZoom';\nimport { Button } from 'solid-repl/src/components/ui/Button';\nimport { Checkbox } from 'solid-repl/src/components/ui/Checkbox';\n\nexport const ZoomDropdown: Component<{ showMenu: boolean }> = (props) => {\n  const [open, setOpen] = createSignal(false);\n  const { zoomState, updateZoom, setZoomState } = useZoom();\n  const popupDuration = 1250;\n  let containerEl!: HTMLDivElement;\n  let prevZoom = zoomState.zoom;\n  let timeoutId: number | null = null;\n  let btnEl!: HTMLButtonElement;\n  let prevFocusedEl: HTMLElement | null;\n  let stealFocus = true;\n\n  const onMouseMove = () => {\n    stealFocus = true;\n    window.clearTimeout(timeoutId!);\n  };\n\n  const onKeyDownContainer = (e: KeyboardEvent) => {\n    if (!open()) return;\n\n    if (e.key === 'Escape' && !stealFocus) {\n      if (prevFocusedEl) {\n        setOpen(false);\n        prevFocusedEl.focus();\n        stealFocus = true;\n      }\n      window.clearTimeout(timeoutId!);\n    }\n\n    if (!['Tab', 'Enter', 'Space'].includes(e.key)) return;\n    stealFocus = false;\n    prevFocusedEl = null;\n    window.clearTimeout(timeoutId!);\n  };\n\n  createEffect(() => {\n    if (prevZoom === zoomState.zoom) return;\n    prevZoom = zoomState.zoom;\n\n    if (stealFocus) {\n      prevFocusedEl = document.activeElement as HTMLElement;\n      btnEl.focus();\n      stealFocus = false;\n    }\n\n    setOpen(true);\n\n    window.clearTimeout(timeoutId!);\n\n    timeoutId = window.setTimeout(() => {\n      setOpen(false);\n\n      stealFocus = true;\n      if (prevFocusedEl) {\n        prevFocusedEl.focus();\n      }\n    }, popupDuration);\n  });\n\n  createEffect(() => {\n    if (!open()) {\n      if (containerEl) {\n        containerEl.removeEventListener('mouseenter', onMouseMove);\n      }\n      stealFocus = true;\n    } else {\n      if (containerEl) {\n        containerEl.addEventListener('mouseenter', onMouseMove, { once: true });\n      }\n    }\n  });\n\n  return (\n    <div\n      class=\"relative\"\n      onKeyDown={onKeyDownContainer}\n      onClick={() => {\n        window.clearTimeout(timeoutId!);\n      }}\n      ref={containerEl}\n      tabindex=\"-1\"\n    >\n      <Button\n        type=\"button\"\n        classList={{\n          'rounded-none active:bg-gray-300 hover:bg-gray-300 dark:hover:text-black': props.showMenu,\n          'bg-gray-300 dark:text-black': open() && props.showMenu,\n        }}\n        title=\"Scale editor to make text larger or smaller\"\n        ref={btnEl}\n      >\n        <Icon class=\"h-6\" path={magnifyingGlassPlus} />\n        <span class=\"text-sm md:sr-only\">Scale Editor</span>\n      </Button>\n      <Dismiss menuButton={btnEl} open={open} setOpen={setOpen}>\n        <div\n          class=\"z-10 w-min border-neutral-200 bg-white p-4 dark:border-neutral-700 dark:bg-neutral-900 absolute rounded-lg border shadow-lg\"\n          classList={{\n            'left-1/4': props.showMenu,\n          }}\n          style={{\n            transform: 'translateX(calc(2rem - 100%))',\n          }}\n        >\n          <div class=\"flex items-center\">\n            <button\n              class=\"border-neutral-200 p-1.5 hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-800 inline-flex items-center justify-center rounded-l-md border\"\n              aria-label=\"decrease font size\"\n              onClick={() => updateZoom('decrease')}\n            >\n              <Icon path={minus} class=\"h-4 w-4\" />\n            </button>\n            <div class=\"w-20 border-neutral-200 px-3 py-1 dark:border-neutral-700 border-y text-center text-sm tabular-nums\">\n              {zoomState.zoom}%\n            </div>\n            <button\n              class=\"mr-4 border-neutral-200 p-1.5 hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-800 inline-flex items-center justify-center rounded-r-md border\"\n              aria-label=\"increase font size\"\n              onClick={() => updateZoom('increase')}\n            >\n              <Icon path={plus} class=\"h-4 w-4\" />\n            </button>\n            <button\n              class=\"border-neutral-200 px-3 py-1 hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-800 rounded-md border text-sm\"\n              aria-label=\"reset font size\"\n              onClick={() => updateZoom('reset')}\n            >\n              Reset\n            </button>\n          </div>\n          <div class=\"mt-3 space-y-2\">\n            <Checkbox\n              label=\"Override browser zoom keyboard shortcut\"\n              checked={zoomState.overrideNative}\n              onChange={(e) => setZoomState('overrideNative', e.currentTarget.checked)}\n            />\n            <Checkbox\n              label=\"Scale iframe result\"\n              checked={zoomState.scaleIframe}\n              onChange={(e) => setZoomState('scaleIframe', e.currentTarget.checked)}\n            />\n          </div>\n        </div>\n      </Dismiss>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/playground/src/context.tsx",
    "content": "import { Accessor, createContext, createResource, createSignal, ParentComponent, Resource, useContext } from 'solid-js';\nimport type { Tab } from 'solid-repl';\nimport { isDarkTheme } from './utils/isDarkTheme';\n\ninterface AppContextType {\n  token: string;\n  user: Resource<{ display: string; avatar: string } | undefined>;\n  profile: Accessor<string>;\n  tabs: Accessor<Tab[] | undefined>;\n  setTabs: (x: Accessor<Tab[] | undefined> | undefined) => void;\n  dark: Accessor<boolean>;\n  toggleDark: () => void;\n}\n\nconst AppContext = createContext<AppContextType>();\n\n// export const API = 'http://localhost:8787';\n// export const API = '/api';\nexport const API = 'https://api.solidjs.com';\n\nexport const AppContextProvider: ParentComponent = (props) => {\n  const [token, setToken] = createSignal(localStorage.getItem('token') || '');\n  const [user] = createResource(token, async (token) => {\n    if (!token)\n      return {\n        display: '',\n        avatar: '',\n      };\n    const result = await fetch(`${API}/profile`, {\n      headers: {\n        authorization: `Bearer ${token}`,\n      },\n    });\n    const body = await result.json();\n    return {\n      display: body.display,\n      avatar: body.avatar,\n    };\n  });\n\n  const [dark, setDark] = createSignal(isDarkTheme());\n  document.body.classList.toggle('dark', dark());\n\n  let [tabsGetter, setTabs] = createSignal<Accessor<Tab[] | undefined>>();\n  return (\n    <AppContext.Provider\n      value={{\n        get token() {\n          return token();\n        },\n        set token(x) {\n          setToken(x);\n          localStorage.setItem('token', x);\n        },\n        user,\n        profile: () => user()?.display || 'anonymous',\n        tabs() {\n          const tabs = tabsGetter();\n          if (!tabs) return undefined;\n          return tabs();\n        },\n        setTabs(x) {\n          setTabs(() => x);\n        },\n        dark,\n        toggleDark() {\n          let x = !dark();\n          document.body.classList.toggle('dark', x);\n          setDark(x);\n          localStorage.setItem('dark', String(x));\n        },\n      }}\n    >\n      {props.children}\n    </AppContext.Provider>\n  );\n};\n\nexport const useAppContext = () => useContext(AppContext);\n"
  },
  {
    "path": "packages/playground/src/index.tsx",
    "content": "import { render } from 'solid-js/web';\nimport { App } from './app';\nimport { registerServiceWorker } from './utils/serviceWorker';\nimport 'solid-repl/repl/main.css';\nimport 'virtual:uno.css';\n\nrender(() => <App />, document.querySelector('#app')!);\n\nregisterServiceWorker();\n"
  },
  {
    "path": "packages/playground/src/pages/edit.tsx",
    "content": "import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';\nimport tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';\nimport cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';\nimport jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';\nimport CompilerWorker from 'solid-repl/repl/compiler?worker';\nimport FormatterWorker from 'solid-repl/repl/formatter?worker';\nimport LinterWorker from 'solid-repl/repl/linter?worker';\nimport { batch, createEffect, createResource, createSignal, lazy, onCleanup, Show, Suspense } from 'solid-js';\nimport { useLocation, useMatch, useNavigate, useParams, useSearchParams } from '@solidjs/router';\nimport { API, useAppContext } from '../context';\nimport { debounce } from '@solid-primitives/scheduled';\nimport { decompressFromURL } from '@amoutonbrady/lz-string';\nimport { defaultTabs } from 'solid-repl/src';\nimport type { Tab } from 'solid-repl';\nimport type { APIRepl } from './home';\nimport { Header } from '../components/header';\nimport { Button } from 'solid-repl/src/components/ui/Button';\n\nfunction parseHash<T>(hash: string, fallback: T): T {\n  try {\n    return JSON.parse(decompressFromURL(hash) || '');\n  } catch {\n    return fallback;\n  }\n}\n\nconst Repl = lazy(() => import('../components/setupSolid'));\n\nwindow.MonacoEnvironment = {\n  getWorker(_moduleId: unknown, label: string) {\n    switch (label) {\n      case 'css':\n        return new cssWorker();\n      case 'json':\n        return new jsonWorker();\n      case 'typescript':\n      case 'javascript':\n        return new tsWorker();\n      default:\n        return new editorWorker();\n    }\n  },\n};\n\ninterface InternalTab extends Tab {\n  _source: string;\n  _name: string;\n}\nexport const Edit = () => {\n  const [searchParams] = useSearchParams();\n  const scratchpad = useMatch(() => '/');\n  const compiler = new CompilerWorker();\n  const formatter = new FormatterWorker();\n  const linter = new LinterWorker();\n\n  const params = useParams<{ user: string; repl: string }>();\n  const context = useAppContext()!;\n  const navigate = useNavigate();\n  const location = useLocation();\n\n  let disableFetch: true | undefined;\n\n  let readonly = () => !scratchpad() && context.profile() != params.user && !localStorage.getItem(params.repl);\n\n  createEffect(() => {\n    if (!scratchpad()) return;\n    if (location.query.hash) {\n      navigate(`/anonymous/${location.query.hash}`);\n    } else if (location.hash) {\n      const initialTabs = parseHash(location.hash.slice(1), defaultTabs);\n      localStorage.setItem(\n        'scratchpad',\n        JSON.stringify({\n          files: initialTabs.map((x) => ({ name: x.name, content: x.source })),\n        }),\n      );\n      navigate('/', { replace: true });\n    }\n  });\n\n  const mapTabs = (toMap: (Tab | InternalTab)[]): InternalTab[] =>\n    toMap.map((tab) => {\n      if ('_source' in tab) return tab;\n      return {\n        _name: tab.name,\n        get name() {\n          return this._name;\n        },\n        set name(name: string) {\n          this._name = name;\n          updateRepl();\n        },\n        _source: tab.source,\n        get source() {\n          return this._source;\n        },\n        set source(source: string) {\n          this._source = source;\n          updateRepl();\n        },\n      };\n    });\n\n  const [tabs, trueSetTabs] = createSignal<InternalTab[]>([]);\n  const setTabs = (tabs: (Tab | InternalTab)[]) => trueSetTabs(mapTabs(tabs));\n  context.setTabs(tabs);\n  onCleanup(() => context.setTabs(undefined));\n\n  const [current, setCurrent] = createSignal<string | undefined>(undefined, { equals: false });\n  const [resource, { mutate }] = createResource<APIRepl, { repl: string | undefined; scratchpad: boolean }>(\n    () => ({ repl: params.repl, scratchpad: !!scratchpad() }),\n    async ({ repl, scratchpad }): Promise<APIRepl> => {\n      if (disableFetch) {\n        disableFetch = undefined;\n        if (resource.latest) return resource.latest;\n      }\n\n      let output: APIRepl;\n      if (scratchpad) {\n        const myScratchpad = localStorage.getItem('scratchpad');\n        if (!myScratchpad) {\n          output = {\n            files: defaultTabs.map((x) => ({\n              name: x.name,\n              content: x.source,\n            })),\n          } as APIRepl;\n          localStorage.setItem('scratchpad', JSON.stringify(output));\n        } else {\n          output = JSON.parse(myScratchpad);\n        }\n      } else {\n        output = await fetch(`${API}/repl/${repl}`, {\n          headers: { authorization: context.token ? `Bearer ${context.token}` : '' },\n        }).then((r) => r.json());\n      }\n\n      batch(() => {\n        setTabs(\n          output.files.map((x) => {\n            return { name: x.name, source: x.content };\n          }),\n        );\n        setCurrent(output.files[0].name);\n      });\n\n      return output;\n    },\n  );\n\n  const reset = () => {\n    batch(() => {\n      setTabs(mapTabs(defaultTabs));\n      setCurrent(defaultTabs[0].name);\n    });\n  };\n\n  const publishScratchpad = async (title: string) => {\n    const newRepl = {\n      title,\n      public: true,\n      labels: [] as string[],\n      version: '1.0',\n      files: tabs().map((x) => ({ name: x.name, content: x.source })),\n    };\n    const response = await fetch(`${API}/repl`, {\n      method: 'POST',\n      headers: {\n        'authorization': context.token ? `Bearer ${context.token}` : '',\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(newRepl),\n    });\n    if (response.status >= 400) {\n      throw new Error(response.statusText);\n    }\n    const { id, write_token } = await response.json();\n    if (write_token) {\n      localStorage.setItem(id, write_token);\n      const repls = localStorage.getItem('repls');\n      if (repls) {\n        localStorage.setItem('repls', JSON.stringify([...JSON.parse(repls), id]));\n      } else {\n        localStorage.setItem('repls', JSON.stringify([id]));\n      }\n    }\n    mutate(() => ({\n      id,\n      title: newRepl.title,\n      labels: newRepl.labels,\n      files: newRepl.files,\n      version: newRepl.version,\n      public: newRepl.public,\n      size: 0,\n      created_at: '',\n    }));\n    const url = `/${context.profile()}/${id}`;\n    disableFetch = true;\n    navigate(url);\n    return url;\n  };\n\n  const [forkPromptFor, setForkPromptFor] = createSignal<string | null>(null);\n  const [forkDeclinedFor, setForkDeclinedFor] = createSignal<string | null>(null);\n  const forkPromptOpen = () => forkPromptFor() === params.repl;\n  const forkDeclined = () => forkDeclinedFor() === params.repl;\n\n  const onUserEdit = () => {\n    if (!readonly() || forkDeclined()) return;\n    setForkPromptFor(params.repl);\n  };\n\n  const updateRepl = debounce(\n    () => {\n      if (readonly()) return;\n      const files = tabs().map((x) => ({ name: x.name, content: x.source }));\n\n      if (scratchpad()) {\n        localStorage.setItem('scratchpad', JSON.stringify({ files }));\n      }\n\n      const repl = resource.latest;\n      if (!repl) return;\n\n      const loggedIn = context.token && params.user && context.profile() == params.user;\n\n      if (loggedIn || localStorage.getItem(params.repl)) {\n        fetch(`${API}/repl/${params.repl}`, {\n          method: 'PUT',\n          headers: {\n            'authorization': context.token ? `Bearer ${context.token}` : '',\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({\n            ...(localStorage.getItem(params.repl) ? { write_token: localStorage.getItem(params.repl) } : {}),\n            title: repl.title,\n            version: repl.version,\n            public: repl.public,\n            labels: repl.labels,\n            files,\n          }),\n        });\n      }\n    },\n    !!scratchpad() ? 10 : 1000,\n  );\n\n  return (\n    <>\n      <Header\n        compiler={compiler}\n        fork={() => {}}\n        share={async () => {\n          if (scratchpad()) {\n            const url = await publishScratchpad(`${context.user()?.display || 'Anonymous'}'s Scratchpad`);\n            return `${window.location.origin}${url}`;\n          } else if (readonly()) {\n            const original = resource.latest;\n            const url = await publishScratchpad(original?.title ? `${original.title} (fork)` : 'Forked Repl');\n            return `${window.location.origin}${url}`;\n          } else {\n            return window.location.href;\n          }\n        }}\n      >\n        <Show when={resource() && (resource()?.title || (scratchpad() && context.token))}>\n          <input\n            class=\"w-96 border-transparent bg-transparent px-3 py-1.5 shrink rounded-md border transition focus:border-solidc focus:outline-none\"\n            value={resource()?.title ?? ''}\n            placeholder={scratchpad() ? 'Name this repl to save it' : ''}\n            onKeyDown={(e) => {\n              if (e.key === 'Enter') e.currentTarget.blur();\n            }}\n            onChange={(e) => {\n              const title = e.currentTarget.value;\n              if (scratchpad() || readonly()) {\n                if (title) publishScratchpad(title);\n              } else {\n                mutate((x) => x && { ...x, title });\n                updateRepl();\n              }\n            }}\n          />\n        </Show>\n      </Header>\n      <Suspense\n        fallback={\n          <svg\n            class=\"h-12 w-12 animate-spin text-neutral-500 m-auto\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            fill=\"none\"\n            viewBox=\"0 0 24 24\"\n          >\n            <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n            <path\n              class=\"opacity-75\"\n              fill=\"currentColor\"\n              d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n            ></path>\n          </svg>\n        }\n      >\n        <Show when={resource()}>\n          <Repl\n            compiler={compiler}\n            formatter={formatter}\n            linter={linter}\n            isHorizontal={searchParams.isHorizontal != undefined}\n            dark={context.dark()}\n            tabs={tabs()}\n            setTabs={setTabs}\n            reset={reset}\n            current={current()}\n            setCurrent={setCurrent}\n            onUserEdit={onUserEdit}\n            id=\"repl\"\n          />\n        </Show>\n      </Suspense>\n      <Show when={forkPromptOpen()}>\n        <div class=\"top-0 left-0 z-10 h-full w-full bg-black/50 fixed flex items-center justify-center\">\n          <div\n            class=\"w-96 border-neutral-200 bg-white p-4 dark:border-neutral-700 dark:text-white rounded-lg border shadow-lg dark:bg-neutral-900\"\n            role=\"dialog\"\n            aria-modal=\"true\"\n            tabindex=\"-1\"\n          >\n            <p class=\"font-semibold\">Fork this repl?</p>\n            <p class=\"mt-2 text-sm opacity-80\">\n              You're editing someone else's repl. Fork it to a new copy you can save.\n            </p>\n            <div class=\"mt-3 gap-2 flex justify-end\">\n              <Button\n                onClick={() => {\n                  setForkPromptFor(null);\n                  setForkDeclinedFor(params.repl);\n                }}\n              >\n                Cancel\n              </Button>\n              <Button\n                variant=\"primary\"\n                onClick={() => {\n                  setForkPromptFor(null);\n                  const original = resource.latest;\n                  publishScratchpad(original?.title ? `${original.title} (fork)` : 'Forked Repl');\n                }}\n              >\n                Fork\n              </Button>\n            </div>\n          </div>\n        </div>\n      </Show>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/playground/src/pages/home.tsx",
    "content": "import { A, useParams } from '@solidjs/router';\nimport { Icon } from 'solid-heroicons';\nimport { eye, eyeSlash, plus, xMark } from 'solid-heroicons/outline';\nimport { createResource, createSignal, For, Show, Suspense } from 'solid-js';\nimport { createStore, produce } from 'solid-js/store';\nimport { API, useAppContext } from '../context';\nimport { Header } from '../components/header';\nimport { timeAgo } from '../utils/date';\nimport { Button } from 'solid-repl/src/components/ui/Button';\n\ninterface ReplFile {\n  name: string;\n  content: string;\n}\nexport interface APIRepl {\n  id: string;\n  title: string;\n  labels: string[];\n  files: ReplFile[];\n  version: string;\n  public: boolean;\n  size: number;\n  created_at: string;\n  updated_at?: string;\n}\ninterface Repls {\n  total: number;\n  list: APIRepl[];\n}\n\nexport const Home = () => {\n  const params = useParams();\n  const context = useAppContext()!;\n\n  const [repls, setRepls] = createStore<Repls>({ total: 0, list: [] });\n  const [resourceRepls] = createResource<Repls, { user: string | undefined }>(\n    () => ({ user: params.user }),\n    async ({ user }) => {\n      if (!user && !context.token) return { total: 0, list: [] };\n      let output = await fetch(`${API}/repl${user ? `/${user}/list` : '?'}`, {\n        headers: {\n          Authorization: `Bearer ${context.token}`,\n        },\n      }).then((r) => r.json());\n      setRepls(output);\n      return output;\n    },\n  );\n  const get = <T,>(x: T) => {\n    resourceRepls();\n    return x;\n  };\n\n  const [open, setOpen] = createSignal<string>();\n\n  return (\n    <>\n      <Header\n        share={async () => {\n          const url = new URL(document.location.origin);\n          url.pathname = `/${params.user || context.profile()}`;\n          return url.toString();\n        }}\n      />\n      <div class=\"m-8\">\n        <Show\n          when={params.user === context.profile()}\n          fallback={<h1 class=\"mb-4 text-center text-sm\">{`${params.user}'s`} Repls</h1>}\n        >\n          <div class=\"mb-8 flex flex-col align-middle\">\n            <A\n              href=\"/\"\n              class=\"border-neutral-200 px-4 py-3 hover:bg-neutral-300 dark:border-neutral-700 dark:hover:bg-neutral-800 bg-neutral-200 dark:bg-neutral-900 mx-auto flex items-center rounded-lg border text-sm\"\n            >\n              <Icon path={plus} class=\"mr-1 w-6\" /> Create new REPL\n            </A>\n          </div>\n        </Show>\n        <table class=\"w-200 max-w-full border-spacing-0 mx-auto border-separate text-sm\">\n          <thead>\n            <tr class=\"font-medium\">\n              <th class=\"w-1/2 border-neutral-200 px-3 py-2 dark:border-neutral-700 border-b text-left\">Title</th>\n              <th class=\"w-32 border-neutral-200 px-3 py-2 dark:border-neutral-700 border-b text-left last:text-right\">\n                Edited\n              </th>\n              <Show when={params.user === context.profile()}>\n                <th class=\"w-20 border-neutral-200 px-3 py-2 dark:border-neutral-700 border-b text-right\">Options</th>\n              </Show>\n            </tr>\n          </thead>\n          <tbody>\n            <tr class=\"h-1\" aria-hidden />\n            <Suspense\n              fallback={\n                <tr class=\"h-10\">\n                  <td colspan=\"3\" class=\"text-center\">\n                    <svg\n                      class=\"mt-8 h-8 w-8 animate-spin text-neutral-500 mx-auto\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      fill=\"none\"\n                      viewBox=\"0 0 24 24\"\n                    >\n                      <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n                      <path\n                        class=\"opacity-75\"\n                        fill=\"currentColor\"\n                        d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n                      ></path>\n                    </svg>\n                  </td>\n                </tr>\n              }\n            >\n              <For each={get(repls.list)}>\n                {(repl, i) => (\n                  <tr\n                    class=\"group cursor-pointer\"\n                    onclick={(e) => {\n                      if (e.target.tagName !== 'A') e.currentTarget.querySelector('a')!.click();\n                    }}\n                  >\n                    <td class=\"px-3 py-2 group-hover:bg-neutral-200 dark:group-hover:bg-neutral-800 first:rounded-l-lg last:rounded-r-lg\">\n                      <A href={`/${params.user || context.profile()}/${repl.id}`}>{repl.title}</A>\n                    </td>\n                    <td class=\"px-3 py-2 group-hover:bg-neutral-200 dark:group-hover:bg-neutral-800 first:rounded-l-lg last:rounded-r-lg last:text-right\">\n                      {timeAgo(Date.now() - new Date(repl.updated_at || repl.created_at).getTime())}\n                    </td>\n                    <Show when={params.user === context.profile()}>\n                      <td class=\"space-x-1 px-3 py-2 group-hover:bg-neutral-200 dark:group-hover:bg-neutral-800 text-right first:rounded-l-lg last:rounded-r-lg\">\n                        <Icon\n                          path={repl.public ? eye : eyeSlash}\n                          class=\"w-6 inline cursor-pointer\"\n                          onClick={async (e) => {\n                            e.stopPropagation();\n                            fetch(`${API}/repl/${repl.id}`, {\n                              method: 'PATCH',\n                              headers: {\n                                'authorization': `Bearer ${context.token}`,\n                                'Content-Type': 'application/json',\n                              },\n                              body: JSON.stringify({\n                                public: !repl.public,\n                              }),\n                            });\n                            setRepls(\n                              produce((x) => {\n                                x!.list[i()].public = !repl.public;\n                              }),\n                            );\n                          }}\n                        />\n                        <Icon\n                          path={xMark}\n                          class=\"w-6 text-red-700 inline cursor-pointer\"\n                          onClick={(e) => {\n                            e.stopPropagation();\n                            setOpen(repl.id);\n                          }}\n                        />\n                      </td>\n                    </Show>\n                  </tr>\n                )}\n              </For>\n            </Suspense>\n          </tbody>\n        </table>\n      </div>\n      <Show when={!!open()}>\n        <div\n          class=\"top-0 left-0 z-10 h-full w-full bg-black/50 fixed flex items-center justify-center\"\n          onClick={(e) => {\n            if (e.target !== e.currentTarget) return;\n            setOpen(undefined);\n          }}\n          role=\"presentation\"\n        >\n          <div\n            class=\"w-96 border-neutral-200 bg-white p-4 dark:border-neutral-700 dark:text-white dark:bg-neutral-900 rounded-lg border shadow-lg\"\n            role=\"dialog\"\n            aria-modal=\"true\"\n            tabindex=\"-1\"\n          >\n            <p>Are you sure you want to delete that?</p>\n            <div class=\"mt-3 gap-2 flex justify-end\">\n              <Button onClick={() => setOpen(undefined)}>No</Button>\n              <Button\n                class=\"text-red-700 dark:text-red-400\"\n                onClick={() => {\n                  fetch(`${API}/repl/${open()}`, {\n                    method: 'DELETE',\n                    headers: {\n                      authorization: `Bearer ${context.token}`,\n                    },\n                  });\n                  setRepls({\n                    total: repls.total - 1,\n                    list: repls.list.filter((x) => x.id !== open()),\n                  });\n                  setOpen(undefined);\n                }}\n              >\n                Delete\n              </Button>\n            </div>\n          </div>\n        </div>\n      </Show>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/playground/src/pages/login.tsx",
    "content": "import { Navigate, useSearchParams } from '@solidjs/router';\nimport { useAppContext } from '../context';\n\nexport const Login = () => {\n  const [params] = useSearchParams();\n  const context = useAppContext()!;\n  context.token = `${params.token ?? ''}`;\n  return <Navigate href=\"/\" />;\n};\n"
  },
  {
    "path": "packages/playground/src/utils/date.ts",
    "content": "const formatter = new Intl.RelativeTimeFormat('en');\nexport const timeAgo = (ms: number): string => {\n  const sec = Math.round(ms / 1000);\n  const min = Math.round(sec / 60);\n  const hr = Math.round(min / 60);\n  const day = Math.round(hr / 24);\n  const month = Math.round(day / 30);\n  const year = Math.round(month / 12);\n  if (sec < 10) {\n    return 'just now';\n  } else if (sec < 45) {\n    return formatter.format(-sec, 'second');\n  } else if (sec < 90 || min < 45) {\n    return formatter.format(-min, 'minute');\n  } else if (min < 90 || hr < 24) {\n    return formatter.format(-hr, 'hour');\n  } else if (hr < 36 || day < 30) {\n    return formatter.format(-day, 'day');\n  } else if (month < 18) {\n    return formatter.format(-month, 'month');\n  } else {\n    return formatter.format(-year, 'year');\n  }\n};\n"
  },
  {
    "path": "packages/playground/src/utils/exportFiles.tsx",
    "content": "import pkg from '../../package.json';\nimport type { Tab } from 'solid-repl';\nimport dedent from 'dedent';\n\nconst viteConfigFile = dedent`\nimport { defineConfig } from \"vite\";\nimport solidPlugin from \"vite-plugin-solid\";\n\nexport default defineConfig({\n  plugins: [solidPlugin()],\n  build: {\n    target: \"esnext\",\n    polyfillDynamicImport: false,\n  },\n});\n`;\nconst tsConfig = JSON.stringify(\n  {\n    compilerOptions: {\n      strict: true,\n      module: 'ESNext',\n      target: 'ESNext',\n      jsx: 'preserve',\n      esModuleInterop: true,\n      sourceMap: true,\n      allowJs: true,\n      lib: ['es6', 'dom'],\n      rootDir: 'src',\n      moduleResolution: 'node',\n      jsxImportSource: 'solid-js',\n      types: ['solid-js', 'solid-js/dom'],\n    },\n  },\n  null,\n  2,\n);\n\nconst indexHTML = (tabs: Tab[]) => dedent`\n<html>\n  <head>\n    <title>Vite Sandbox</title>\n    <meta charset=\"UTF-8\" />\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n\n    <script type=\"module\" src=\"./src/${tabs[0].name}\"></script>\n  </body>\n</html>\n`;\n\n/**\n * This function will calculate the dependencies of the\n * package.json by using the imports list provided by the bundler,\n * and then generating the package.json itself, for the export\n */\nfunction packageJSON(imports: string[]): string {\n  const deps = imports.reduce(\n    (acc, importPath): Record<string, string> => {\n      const name = importPath.split('/')[0];\n      if (!acc[name]) acc[name] = '*';\n      return acc;\n    },\n    {} as Record<string, string>,\n  );\n\n  return JSON.stringify(\n    {\n      scripts: {\n        start: 'vite',\n        build: 'vite build',\n      },\n      dependencies: deps,\n      devDependencies: {\n        'vite': pkg.devDependencies['vite'],\n        'vite-plugin-solid': pkg.devDependencies['vite-plugin-solid'],\n      },\n    },\n    null,\n    2,\n  );\n}\n\n/**\n * This function will convert the tabs of the playground\n * into a ZIP formatted playground that can then be reimported later on\n */\nexport async function exportToZip(tabs: Tab[]): Promise<void> {\n  const { default: JSZip } = await import('jszip');\n  const zip = new JSZip();\n\n  // basic structure\n  zip.file('index.html', indexHTML(tabs));\n  zip.file('vite.config.ts', viteConfigFile);\n  zip.file('tsconfig.json', tsConfig);\n  zip.folder('src');\n\n  for (const tab of tabs) {\n    if (tab.name == 'import_map.json') {\n      zip.file('package.json', packageJSON(Object.keys(JSON.parse(tab.source))));\n    } else {\n      zip.file(`src/${tab.name}`, tab.source);\n    }\n  }\n\n  const blob = await zip.generateAsync({ type: 'blob' });\n  const url = URL.createObjectURL(blob);\n\n  const anchor = (<a href={url} target=\"_blank\" rel=\"noopener\" download=\"solid-playground-project\" />) as HTMLElement;\n  document.body.prepend(anchor);\n  anchor.click();\n  anchor.remove();\n}\n"
  },
  {
    "path": "packages/playground/src/utils/isDarkTheme.ts",
    "content": "export const isDarkTheme = () => {\n  if (typeof window !== 'undefined') {\n    if (window.localStorage) {\n      const isDarkTheme = window.localStorage.getItem('dark');\n      if (typeof isDarkTheme === 'string') {\n        return isDarkTheme === 'true';\n      }\n    }\n\n    const userMedia = window.matchMedia('(prefers-color-scheme: dark)');\n    if (userMedia.matches) {\n      return true;\n    }\n  }\n\n  // Default theme is light.\n  return false;\n};\n"
  },
  {
    "path": "packages/playground/src/utils/serviceWorker.ts",
    "content": "import { register } from 'register-service-worker';\nimport { createSignal } from 'solid-js';\n\nconst [eventBus, setEventBus] = createSignal();\n\nfunction registerServiceWorker(): void {\n  if ('serviceWorker' in navigator && import.meta.env.PROD) {\n    window.addEventListener('load', () => {\n      register('/sw.js', {\n        updated() {\n          setEventBus(true);\n        },\n      });\n    });\n  }\n}\n\nif (import.meta.env.PROD) {\n  navigator.serviceWorker?.addEventListener('message', (event) => {\n    if (event.data.type == 'cache') {\n      setEventBus(true);\n    }\n  });\n}\n\nexport { eventBus, setEventBus, registerServiceWorker };\n"
  },
  {
    "path": "packages/playground/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\", \"WebWorker\"],\n    \"types\": [\"vite/client\"]\n  },\n  \"include\": [\"./src/**/*\"],\n  \"exclude\": [\"node_modules/\"]\n}\n"
  },
  {
    "path": "packages/playground/unocss.config.ts",
    "content": "import { theme } from '@unocss/preset-wind3';\nimport { defineConfig } from 'unocss';\nimport sharedConfig from '../../uno.config.ts';\n\nexport default defineConfig({\n  ...sharedConfig,\n  theme: {\n    ...(sharedConfig as any).theme,\n    fontFamily: {\n      sans: 'Gordita, ' + theme.fontFamily!.sans,\n    },\n  },\n  content: {\n    filesystem: ['./src/**/*.tsx', '../solid-repl/src/**/*.{tsx,ts}'],\n  },\n});\n"
  },
  {
    "path": "packages/playground/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport solidPlugin from 'vite-plugin-solid';\nimport UnoCSS from 'unocss/vite';\n\nexport default defineConfig((env) => ({\n  plugins: [solidPlugin(), UnoCSS()],\n  define: {\n    'process.env.NODE_DEBUG': 'false',\n    ...(env.command == 'build' ? {} : { global: 'globalThis' }),\n  },\n  build: {\n    target: 'esnext',\n    rolldownOptions: {\n      output: {\n        entryFileNames: `assets/[name].js`,\n        chunkFileNames: `assets/[name].js`,\n        assetFileNames: `assets/[name].[ext]`,\n      },\n    },\n  },\n  worker: {\n    rolldownOptions: {\n      output: {\n        entryFileNames: `assets/[name].js`,\n      },\n    },\n  },\n  server: {\n    proxy: {\n      '/api': {\n        target: 'http://localhost:8787',\n        changeOrigin: true,\n        rewrite: (path) => path.replace(/^\\/api/, ''),\n      },\n    },\n  },\n}));\n"
  },
  {
    "path": "packages/solid-repl/build.ts",
    "content": "import { build } from 'esbuild';\nimport { readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { copyFileSync } from 'fs-extra';\n\nbuild({\n  entryPoints: ['./repl/compiler.ts', './repl/formatter.ts', './repl/linter.ts', './repl/main.css'],\n  outdir: './dist',\n  minify: true,\n  bundle: true,\n  external: ['/Gordita-Medium.woff', '/Gordita-Regular.woff', '/Gordita-Bold.woff'],\n  define: {\n    'process.env.NODE_DEBUG': 'false',\n    'preventAssignment': 'true',\n  },\n}).then(() => {\n  const unoCSS_build = readFileSync('./uno.css');\n  const generated_bundle = readFileSync('./dist/main.css');\n\n  const output_bundle = Buffer.concat([generated_bundle, unoCSS_build]);\n\n  writeFileSync('./dist/bundle.css', output_bundle);\n\n  unlinkSync('./uno.css');\n  unlinkSync('./dist/main.css');\n\n  copyFileSync('./src/types.d.ts', './dist/types.d.ts');\n});\n"
  },
  {
    "path": "packages/solid-repl/package.json",
    "content": "{\n  \"name\": \"solid-repl\",\n  \"version\": \"0.26.0\",\n  \"description\": \"Quickly discover what the solid compiler will generate from your JSX template\",\n  \"homepage\": \"https://playground.solidjs.com\",\n  \"author\": \"Alexandre Mouton-Brady\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/solidjs/solid-playground.git\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"module\": \"dist/repl.jsx\",\n  \"types\": \"src/types.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsc -p tsconfig.build.json && unocss \\\"./src/**\\\" && jiti build.ts\",\n    \"tsc\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@floating-ui/dom\": \"^1.7.6\",\n    \"@shikijs/monaco\": \"^4.0.2\",\n    \"@solid-primitives/media\": \"^2.3.5\",\n    \"@solid-primitives/scheduled\": \"^1.5.3\",\n    \"dedent\": \"^1.7.2\",\n    \"dockview-core\": \"^5.2.0\",\n    \"shiki\": \"^4.0.2\",\n    \"solid-dismiss\": \"^1.8.2\",\n    \"solid-floating-ui\": \"^0.3.1\",\n    \"solid-heroicons\": \"^3.2.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/plugin-syntax-jsx\": \"^7.28.6\",\n    \"@babel/preset-typescript\": \"^7.28.5\",\n    \"@babel/standalone\": \"^7.29.2\",\n    \"@babel/types\": \"^7.29.0\",\n    \"@shikijs/langs\": \"^4.0.2\",\n    \"@shikijs/themes\": \"^4.0.2\",\n    \"@types/babel__standalone\": \"^7.1.9\",\n    \"@types/dedent\": \"^0.7.2\",\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@unocss/cli\": \"^66.6.8\",\n    \"@unocss/preset-wind3\": \"^66.6.8\",\n    \"@unocss/reset\": \"^66.6.8\",\n    \"babel-preset-solid\": \"^1.9.12\",\n    \"esbuild\": \"^0.28.0\",\n    \"eslint-solid-standalone\": \"<0.14.0\",\n    \"fs-extra\": \"^11.3.4\",\n    \"jiti\": \"^2.6.1\",\n    \"monaco-editor\": \"^0.55.1\",\n    \"prettier\": \"^3.8.3\",\n    \"solid-js\": \"1.9.12\",\n    \"typescript\": \"^6.0.3\",\n    \"unocss\": \"^66.6.8\"\n  },\n  \"peerDependencies\": {\n    \"solid-js\": \">=1.7.0\"\n  }\n}\n"
  },
  {
    "path": "packages/solid-repl/repl/compiler.ts",
    "content": "import type { Tab } from 'solid-repl';\n\nimport { transform } from '@babel/standalone';\n// @ts-ignore\nimport babelPresetSolid from 'babel-preset-solid';\n\nimport dd from 'dedent';\n\nfunction uid(str: string) {\n  return Array.from(str)\n    .reduce((s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0, 0)\n    .toString();\n}\n\nfunction babelTransform(filename: string, code: string, externals: Record<string, string>) {\n  const handleImportee = (node: { value: string } | null | undefined) => {\n    if (!node || typeof node.value !== 'string') return;\n    const importee = node.value;\n    if (importee.startsWith('.')) {\n      node.value = 'solidrepl:' + importee;\n    } else if (!importee.includes('://')) {\n      if (!(importee in externals)) externals[importee] = `https://esm.sh/${importee}`;\n    }\n  };\n\n  let { code: transformedCode } = transform(code, {\n    plugins: [\n      function importRewriter() {\n        return {\n          visitor: {\n            Import(path: any) {\n              handleImportee(path.parent.arguments[0]);\n            },\n            ImportDeclaration(path: any) {\n              handleImportee(path.node.source);\n            },\n            ExportAllDeclaration(path: any) {\n              handleImportee(path.node.source);\n            },\n            ExportNamedDeclaration(path: any) {\n              handleImportee(path.node.source);\n            },\n          },\n        };\n      },\n    ],\n    presets: [\n      [babelPresetSolid, { generate: 'dom', hydratable: false }],\n      ['typescript', { onlyRemoveTypeImports: true }],\n    ],\n    filename: filename + '.tsx',\n  });\n\n  return transformedCode!.replace('render(', 'window.dispose = render(');\n}\n\nfunction transformTab(tab: Tab, externals: Record<string, string>): string {\n  if (tab.name.endsWith('.css')) {\n    const id = uid(tab.name);\n    return dd`\n      (() => {\n        let stylesheet = document.getElementById('${id}');\n        if (!stylesheet) {\n          stylesheet = document.createElement('style')\n          stylesheet.setAttribute('id', '${id}')\n          document.head.appendChild(stylesheet)\n        }\n        const styles = document.createTextNode(\\`${tab.source.replace(/`/g, '\\\\`').replace(/\\$\\{/g, '\\\\${')}\\`)\n        stylesheet.innerHTML = ''\n        stylesheet.appendChild(styles)\n      })()\n    `;\n  }\n  return babelTransform(tab.name, tab.source, externals);\n}\n\nfunction compile(tabs: Tab[], event: string) {\n  const externals: Record<string, string> = {};\n  const compiled: Record<string, string> = {};\n  for (const tab of tabs) {\n    const key = `./${tab.name.replace(/\\.(tsx|jsx)$/, '')}`;\n    compiled[key] = transformTab(tab, externals);\n  }\n  return { event, compiled, externals };\n}\n\nfunction babel(tab: Tab, compileOpts: any) {\n  const { code } = transform(tab.source, {\n    presets: [\n      [babelPresetSolid, compileOpts],\n      ['typescript', { onlyRemoveTypeImports: true }],\n    ],\n    filename: tab.name,\n  });\n  return { event: 'BABEL', compiled: code };\n}\n\nself.addEventListener('message', ({ data }) => {\n  const { event, tabs, tab, compileOpts } = data;\n\n  try {\n    if (event === 'BABEL') {\n      self.postMessage(babel(tab, compileOpts));\n    } else if (event === 'ROLLUP') {\n      self.postMessage(compile(tabs, event));\n    }\n  } catch (e) {\n    self.postMessage({ event: 'ERROR', error: e });\n  }\n});\n\nexport {};\n"
  },
  {
    "path": "packages/solid-repl/repl/formatter.ts",
    "content": "import { format as prettierFormat } from 'prettier/standalone';\nimport * as prettierPluginBabel from 'prettier/plugins/babel';\nimport * as prettierPluginEstree from 'prettier/plugins/estree';\n\nfunction format(code: string) {\n  return prettierFormat(code, {\n    parser: 'babel-ts',\n    plugins: [prettierPluginBabel, prettierPluginEstree as any],\n  });\n}\n\nself.addEventListener('message', async ({ data }) => {\n  const { event, code } = data;\n\n  switch (event) {\n    case 'FORMAT':\n      self.postMessage({\n        event: 'FORMAT',\n        code: await format(code),\n      });\n      break;\n  }\n});\n\nexport {};\n"
  },
  {
    "path": "packages/solid-repl/repl/linter.ts",
    "content": "import { verify, verifyAndFix } from 'eslint-solid-standalone';\nimport type { Linter } from 'eslint-solid-standalone';\nimport type { editor } from 'monaco-editor';\n\nexport interface LinterWorkerPayload {\n  event: 'LINT' | 'FIX';\n  code: string;\n  ruleSeverityOverrides?: Record<string, Linter.Severity>;\n}\n\nconst messagesToMarkers = (messages: Array<Linter.LintMessage>): Array<editor.IMarkerData> => {\n  if (messages.some((m) => m.fatal)) return []; // no need for any extra highlights on parse errors\n  return messages.map((m) => ({\n    startLineNumber: m.line,\n    endLineNumber: m.endLine ?? m.line,\n    startColumn: m.column,\n    endColumn: m.endColumn ?? m.column,\n    message: `${m.message}\\neslint(${m.ruleId})`,\n    severity: m.severity === 2 ? 8 /* error */ : 4 /* warning */,\n  }));\n};\n\nself.addEventListener('message', ({ data }: MessageEvent<LinterWorkerPayload>) => {\n  const { event } = data;\n  try {\n    if (event === 'LINT') {\n      const { code, ruleSeverityOverrides } = data;\n      self.postMessage({\n        event: 'LINT' as const,\n        markers: messagesToMarkers(verify(code, ruleSeverityOverrides)),\n      });\n    } else if (event === 'FIX') {\n      const { code, ruleSeverityOverrides } = data;\n      const fixReport = verifyAndFix(code, ruleSeverityOverrides);\n      self.postMessage({\n        event: 'FIX' as const,\n        markers: messagesToMarkers(fixReport.messages),\n        output: fixReport.output,\n        fixed: fixReport.fixed,\n      });\n    }\n  } catch (e) {\n    console.error(e);\n    self.postMessage({ event: 'ERROR' as const, error: e });\n  }\n});\n"
  },
  {
    "path": "packages/solid-repl/repl/main.css",
    "content": "@import url('@unocss/reset/tailwind.css');\n\n@font-face {\n  font-family: 'Gordita';\n  src: url('/Gordita-Regular.woff') format('woff');\n  font-weight: 400;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: 'Gordita';\n  src: url('/Gordita-Bold.woff') format('woff');\n  font-weight: 700;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: 'Gordita';\n  src: url('/Gordita-Medium.woff') format('woff');\n  font-weight: 500;\n  font-style: normal;\n}\n\ndiv[contenteditable='true']:focus {\n  outline: none !important;\n}\n\n.dark {\n  color-scheme: dark;\n}\n\ntextarea.monaco-mouse-cursor-text:focus {\n  box-shadow: unset;\n}\n\n#app .dockview-theme-abyss-spaced {\n  padding-top: 0;\n  --dv-color-abyss-dark: theme('colors.neutral.100');\n  --dv-color-abyss: #ffffff;\n  --dv-color-abyss-light: theme('colors.neutral.100');\n  --dv-color-abyss-lighter: theme('colors.neutral.100');\n  --dv-color-abyss-accent: theme('colors.medium');\n  --dv-color-abyss-primary-text: theme('colors.neutral.800');\n  --dv-color-abyss-secondary-text: theme('colors.neutral.400');\n}\n\n.dark #app .dockview-theme-abyss-spaced {\n  --dv-color-abyss-dark: theme('colors.neutral.950');\n  --dv-color-abyss: theme('colors.neutral.900');\n  --dv-color-abyss-light: theme('colors.neutral.800/50');\n  --dv-color-abyss-lighter: theme('colors.neutral.800/50');\n  --dv-color-abyss-accent: theme('colors.medium');\n  --dv-color-abyss-primary-text: theme('colors.neutral.100');\n  --dv-color-abyss-secondary-text: theme('colors.neutral.400');\n}\n\n#app .dockview-theme-abyss-spaced .dv-groupview.dv-active-group {\n  border: 2px solid var(--dv-color-abyss-accent);\n}\n\n#app .dockview-theme-abyss-spaced .dv-groupview.dv-inactive-group {\n  border: 2px solid transparent;\n}\n"
  },
  {
    "path": "packages/solid-repl/src/components/CompileMode.tsx",
    "content": "import { Component, Setter } from 'solid-js';\nimport { Label } from './ui/Label';\nimport { Input } from './ui/Input';\n\nexport const compileOptions = {\n  SSR: { generate: 'ssr', hydratable: true },\n  DOM: { generate: 'dom', hydratable: false },\n  HYDRATABLE: { generate: 'dom', hydratable: true },\n  UNIVERSAL: {\n    generate: 'universal',\n    hydratable: false,\n    moduleName: 'solid-universal-module' as string,\n  },\n} as const;\n\ninterface CompileModeProps {\n  mode: (typeof compileOptions)[keyof typeof compileOptions];\n  setMode: Setter<(typeof compileOptions)[keyof typeof compileOptions]>;\n  universalModuleName: string;\n  setUniversalModuleName: Setter<string>;\n}\n\nexport const CompileMode: Component<CompileModeProps> = (props) => {\n  return (\n    <div class=\"p-2\">\n      <Label class=\"mb-1 block\">Compile mode</Label>\n\n      <div class=\"mt-1 space-y-1 text-sm\">\n        {(['DOM', 'SSR', 'HYDRATABLE'] as const).map((m) => (\n          <label class=\"space-x-2 mr-auto block cursor-pointer\">\n            <input\n              checked={props.mode === compileOptions[m]}\n              class=\"text-solidc\"\n              onChange={[props.setMode, compileOptions[m]]}\n              type=\"radio\"\n              name=\"dom\"\n            />\n            <span>\n              {m === 'DOM'\n                ? 'Client side rendering'\n                : m === 'SSR'\n                  ? 'Server side rendering'\n                  : 'Client side rendering with hydration'}\n            </span>\n          </label>\n        ))}\n\n        <label class=\"space-x-2 mr-auto block cursor-pointer\">\n          <input\n            checked={props.mode.generate === 'universal'}\n            class=\"text-solidc\"\n            onChange={[props.setMode, compileOptions.UNIVERSAL]}\n            type=\"radio\"\n            name=\"dom\"\n          />\n          <span>Universal Rendering & moduleName:</span>\n          <Input\n            onFocus={[props.setMode, compileOptions.UNIVERSAL]}\n            onInput={(e) => {\n              props.setUniversalModuleName(e.currentTarget.value);\n            }}\n            size=\"sm\"\n            inline\n            class=\"ml-2\"\n            type=\"text\"\n            value={props.universalModuleName}\n            name=\"moduleName\"\n          />\n        </label>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/editor/index.tsx",
    "content": "import { Component, createEffect, onMount, onCleanup, Show } from 'solid-js';\nimport { Uri, languages, editor as mEditor, KeyMod, KeyCode, typescript } from 'monaco-editor';\nimport { useZoom } from '../../hooks/useZoom';\nimport { throttle } from '@solid-primitives/scheduled';\nimport { bell, bellSlash, codeBracket } from 'solid-heroicons/outline';\nimport { register } from './setupSolid';\nimport { IconButton } from '../ui/IconButton';\n\nconst Editor: Component<{\n  model: mEditor.ITextModel;\n  disabled?: true;\n  isDark?: boolean;\n  withMinimap?: boolean;\n  formatter?: Worker;\n  linter?: Worker;\n  displayErrors?: boolean;\n  setDisplayErrors?: (value: boolean) => void;\n  onDocChange?: (code: string) => void;\n  onUserEdit?: () => void;\n  onEditorReady?: (\n    editor: mEditor.IStandaloneCodeEditor,\n    monaco: {\n      Uri: typeof Uri;\n      editor: typeof mEditor;\n    },\n  ) => void;\n}> = (props) => {\n  let parent!: HTMLDivElement;\n  let editor: mEditor.IStandaloneCodeEditor;\n\n  const { zoomState } = useZoom();\n\n  if (props.formatter) {\n    languages.registerDocumentFormattingEditProvider('typescript', {\n      async provideDocumentFormattingEdits(model) {\n        props.formatter!.postMessage({\n          event: 'FORMAT',\n          code: model.getValue(),\n          pos: editor.getPosition(),\n        });\n\n        return new Promise((resolve) => {\n          props.formatter!.addEventListener(\n            'message',\n            ({ data: { code } }) => {\n              resolve([\n                {\n                  range: model.getFullModelRange(),\n                  text: code,\n                },\n              ]);\n            },\n            { once: true },\n          );\n        });\n      },\n    });\n  }\n  if (props.linter) {\n    const listener = ({ data }: any) => {\n      if (props.displayErrors) {\n        const { event } = data;\n        if (event === 'LINT') {\n          mEditor.setModelMarkers(props.model, 'eslint', data.markers);\n        } else if (event === 'FIX') {\n          mEditor.setModelMarkers(props.model, 'eslint', data.markers);\n          data.fixed && props.model.setValue(data.output);\n        }\n      }\n    };\n    props.linter.addEventListener('message', listener);\n    onCleanup(() => props.linter?.removeEventListener('message', listener));\n  }\n\n  const runLinter = throttle((code: string) => {\n    if (props.linter && props.displayErrors) {\n      props.linter.postMessage({\n        event: 'LINT',\n        code,\n      });\n    }\n  }, 250);\n\n  // Initialize Monaco\n  onMount(() => {\n    editor = mEditor.create(parent, {\n      model: null,\n      fontFamily: 'Menlo, Monaco, \"Courier New\", monospace',\n      automaticLayout: true,\n      readOnly: props.disabled,\n      fontSize: zoomState.fontSize,\n      lineDecorationsWidth: 5,\n      lineNumbersMinChars: 3,\n      padding: { top: 15 },\n      minimap: {\n        enabled: props.withMinimap,\n      },\n      dropIntoEditor: {\n        enabled: false,\n      },\n    });\n\n    createEffect(() => {\n      editor.updateOptions({ readOnly: !!props.disabled });\n    });\n\n    if (props.linter) {\n      editor.addAction({\n        id: 'eslint.executeAutofix',\n        label: 'Fix all auto-fixable problems',\n        contextMenuGroupId: '1_modification',\n        contextMenuOrder: 3.5,\n        run: (ed) => {\n          const code = ed.getValue();\n          if (code) {\n            props.linter?.postMessage({\n              event: 'FIX',\n              code,\n            });\n          }\n        },\n      });\n    }\n\n    editor.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, () => {\n      // auto-format\n      editor.getAction('editor.action.formatDocument')?.run();\n      // auto-fix problems\n      props.displayErrors && editor.getAction('eslint.executeAutofix')?.run();\n      editor.focus();\n    });\n\n    editor.onDidChangeModelContent((e) => {\n      const code = editor.getValue();\n      props.onDocChange?.(code);\n      runLinter(code);\n      if (!e.isFlush) props.onUserEdit?.();\n    });\n  });\n  onCleanup(() => editor.dispose());\n\n  createEffect(() => {\n    editor.setModel(props.model);\n    register();\n  });\n\n  createEffect(() => {\n    mEditor.setTheme(props.isDark ? 'dark-plus' : 'light-plus');\n  });\n\n  createEffect(() => {\n    const fontSize = zoomState.fontSize;\n    editor.updateOptions({ fontSize });\n  });\n\n  createEffect(() => {\n    if (props.disabled) return;\n    typescript.typescriptDefaults.setDiagnosticsOptions({\n      noSemanticValidation: !props.displayErrors,\n      noSyntaxValidation: !props.displayErrors,\n    });\n  });\n\n  createEffect(() => {\n    if (props.displayErrors) {\n      // run on mount and when displayLintMessages is turned on\n      runLinter(editor.getValue());\n    } else {\n      // reset eslint markers when displayLintMessages is turned off\n      mEditor.setModelMarkers(props.model, 'eslint', []);\n    }\n  });\n\n  onMount(() => {\n    props.onEditorReady?.(editor, { Uri, editor: mEditor });\n  });\n\n  return (\n    <>\n      <div class=\"min-h-0 min-w-0 p-0 flex-1\" ref={parent} />\n      <Show when={!props.disabled}>\n        <div class=\"w-full border-t-1 border-neutral-200 bg-white px-2 dark:border-neutral-700 dark:bg-neutral-900 flex h-[30px] items-center justify-between\">\n          <div></div>\n          <div class=\"space-x-1 flex items-center\">\n            <IconButton\n              icon={props.displayErrors ? bell : bellSlash}\n              size=\"sm\"\n              title={props.displayErrors ? 'Disable error reporting' : 'Enable error reporting'}\n              onClick={() => props.setDisplayErrors?.(!props.displayErrors)}\n            />\n            <IconButton\n              icon={codeBracket}\n              size=\"sm\"\n              title=\"Format Document\"\n              onClick={() => editor.getAction('editor.action.formatDocument')?.run()}\n            />\n            <span class=\"text-neutral-500 dark:text-neutral-400 px-2 text-sm\">TypeScript</span>\n          </div>\n        </div>\n      </Show>\n    </>\n  );\n};\n\nexport default Editor;\n"
  },
  {
    "path": "packages/solid-repl/src/components/editor/monacoTabs.tsx",
    "content": "import { createMemo, onCleanup } from 'solid-js';\nimport type { Tab } from 'solid-repl';\nimport { Uri, editor, IDisposable } from 'monaco-editor';\n\nexport const createMonacoTabs = (folder: string, tabs: () => Tab[]) => {\n  const currentTabs = createMemo<Map<string, { model: editor.ITextModel; watcher: IDisposable }>>((prevTabs) => {\n    const newTabs = new Map<string, { model: editor.ITextModel; watcher: IDisposable }>();\n    for (const tab of tabs()) {\n      const url = `file:///${folder}/${tab.name}`;\n      const lookup = prevTabs?.get(url);\n      if (!lookup) {\n        const uri = Uri.parse(url);\n        const model = editor.createModel(tab.source, undefined, uri);\n        const watcher = model.onDidChangeContent(() => (tab.source = model.getValue()));\n        newTabs.set(url, { model, watcher });\n      } else {\n        lookup.model.setValue(tab.source);\n        lookup.watcher.dispose();\n        lookup.watcher = lookup.model.onDidChangeContent(() => (tab.source = lookup.model.getValue()));\n        newTabs.set(url, lookup);\n      }\n    }\n\n    if (prevTabs) {\n      for (const [old, lookup] of prevTabs) {\n        if (!newTabs.has(old)) lookup.model.dispose();\n      }\n    }\n    return newTabs;\n  });\n  onCleanup(() => {\n    for (const lookup of currentTabs().values()) lookup.model.dispose();\n  });\n\n  return currentTabs;\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/editor/setupSolid.ts",
    "content": "import { languages, editor, typescript } from 'monaco-editor';\nimport { shikiToMonaco } from '@shikijs/monaco';\nimport { createHighlighterCoreSync } from 'shiki/core';\nimport { createJavaScriptRegexEngine } from 'shiki/engine/javascript';\nimport darkPlus from '@shikijs/themes/dark-plus';\nimport lightPlus from '@shikijs/themes/light-plus';\nimport tsx from '@shikijs/langs/tsx';\nimport css from '@shikijs/langs/css';\nimport html from '@shikijs/langs/html';\nimport json from '@shikijs/langs/json';\n\nconst jsEngine = createJavaScriptRegexEngine();\n\nconst compilerOptions: typescript.CompilerOptions = {\n  strict: true,\n  target: typescript.ScriptTarget.ESNext,\n  module: typescript.ModuleKind.ESNext,\n  moduleResolution: typescript.ModuleResolutionKind.NodeJs,\n  jsx: typescript.JsxEmit.Preserve,\n  jsxImportSource: 'solid-js',\n  allowNonTsExtensions: true,\n};\n\ntypescript.typescriptDefaults.setCompilerOptions(compilerOptions);\ntypescript.javascriptDefaults.setCompilerOptions(compilerOptions);\n\nconst loader = createHighlighterCoreSync({\n  themes: [darkPlus, lightPlus],\n  langs: [tsx, css, html, json],\n  engine: jsEngine,\n});\n\nlanguages.register({ id: 'tsx' });\nlanguages.register({ id: 'css' });\nlanguages.register({ id: 'html' });\nlanguages.register({ id: 'json' });\n\nexport function register() {\n  shikiToMonaco(loader, {\n    editor,\n    languages: {\n      ...languages,\n      setTokensProvider: (id: string, provider: any) => {\n        if (id === 'tsx') {\n          languages.setTokensProvider('tsx', provider);\n          languages.setTokensProvider('typescript', provider);\n          languages.setTokensProvider('javascript', provider);\n        } else languages.setTokensProvider(id, provider);\n      },\n    } as any,\n  });\n}\n\nregister();\n"
  },
  {
    "path": "packages/solid-repl/src/components/error.tsx",
    "content": "import { Component, createMemo, createSignal } from 'solid-js';\n\nimport { Icon } from 'solid-heroicons';\nimport { chevronDown, chevronRight, xMark } from 'solid-heroicons/solid';\nimport { IconButton } from './ui/IconButton';\n\nexport const Error: Component<{\n  onDismiss: (...args: unknown[]) => unknown;\n  message: string;\n}> = (props) => {\n  const lines = createMemo(() => props.message.split('\\n'));\n  const firstLine = () => lines()[0] ?? '';\n  const stackTrace = () => lines().slice(1).join('\\n');\n  const [isOpen, setIsOpen] = createSignal(false);\n\n  return (\n    <div class=\"border-red-300 bg-red-50 p-2 dark:bg-red-900/20 relative border-t-2\">\n      <details class=\"text-red-800 dark:text-red-300\" onToggle={(event) => setIsOpen(event.currentTarget.open)}>\n        <summary class=\"pr-8 flex cursor-pointer items-center\">\n          <Icon class=\"mr-1 h-5 w-5 opacity-70\" path={isOpen() ? chevronDown : chevronRight} />\n          <code class=\"text-sm font-medium\" innerText={firstLine()}></code>\n        </summary>\n\n        <pre class=\"mt-2 ml-6 overflow-auto whitespace-pre text-sm opacity-80\">\n          <code innerText={stackTrace()}></code>\n        </pre>\n      </details>\n      <IconButton\n        icon={xMark}\n        class=\"top-2 right-2 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-800 dark:text-red-300 absolute\"\n        size=\"sm\"\n        onClick={() => props.onDismiss()}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/newTab.tsx",
    "content": "import { Component, createSignal, For, createMemo, onMount, Show } from 'solid-js';\nimport { Icon } from 'solid-heroicons';\nimport {\n  magnifyingGlass,\n  documentPlus,\n  document as documentIcon,\n  square_2Stack,\n  chevronRight,\n  arrowUpTray,\n  ellipsisHorizontal,\n} from 'solid-heroicons/outline';\nimport type { Tab } from 'solid-repl';\nimport { Input } from './ui/Input';\nimport { IconButton } from './ui/IconButton';\nimport { Label } from './ui/Label';\nimport { Menu, MenuItem } from './ui/Menu';\nimport { pencil, trash as trashIcon } from 'solid-heroicons/outline';\nimport Dismiss from 'solid-dismiss';\n\ninterface NewTabProps {\n  tabs: Tab[];\n  onOpenPane: (id: string) => void;\n  onOpenFile: (name: string) => void;\n  onNewFile: (name: string) => void;\n  onUpload: (name: string, source: string) => void;\n  onDeleteFile: (name: string) => void;\n  onRenameFile: (oldName: string, newName: string) => void;\n  onClose: () => void;\n}\n\nexport const NewTab: Component<NewTabProps> = (props) => {\n  const [query, setQuery] = createSignal('');\n  const [selectedIndex, setSelectedIndex] = createSignal(0);\n  const [renamingFile, setRenamingFile] = createSignal<string | null>(null);\n  let inputRef!: HTMLInputElement;\n  let fileInputRef!: HTMLInputElement;\n\n  const categories = createMemo(() => {\n    const q = query().toLowerCase();\n    const sections: { title: string; items: any[] }[] = [];\n    let count = 0;\n\n    // Panes - Always show Preview and Output\n    const paneItems = ['Preview', 'Output']\n      .filter((p) => p.toLowerCase().includes(q))\n      .map((p) => ({\n        type: 'pane',\n        id: p,\n        label: p,\n        icon: square_2Stack,\n        globalIndex: count++,\n      }));\n    if (paneItems.length) {\n      sections.push({ title: 'Panes', items: paneItems });\n    }\n\n    // Files\n    const files = props.tabs.filter((t) => t.name.toLowerCase().includes(q));\n    if (files.length > 0) {\n      sections.push({\n        title: 'Files',\n        items: files.map((t) => ({\n          type: 'file',\n          id: t.name,\n          label: t.name,\n          icon: documentIcon,\n          globalIndex: count++,\n        })),\n      });\n    }\n\n    // Actions\n    const actions = [];\n    if (q === '' || 'upload file'.includes(q)) {\n      actions.push({\n        type: 'action',\n        id: 'upload',\n        label: 'Upload File',\n        icon: arrowUpTray,\n      });\n    }\n    if (q !== '' && !props.tabs.some((t) => t.name.toLowerCase() === q)) {\n      actions.push({\n        type: 'new',\n        id: q,\n        label: `Create \"${query()}\"`,\n        icon: documentPlus,\n      });\n    }\n\n    if (actions.length > 0) {\n      sections.push({\n        title: 'Actions',\n        items: actions.map((item) => ({ ...item, globalIndex: count++ })),\n      });\n    }\n\n    return sections;\n  });\n\n  const allItems = createMemo(() => categories().flatMap((c) => c.items));\n\n  const handleKeyDown = (e: KeyboardEvent) => {\n    if (renamingFile()) return;\n\n    const items = allItems();\n    if (e.key === 'ArrowDown') {\n      setSelectedIndex((i) => (i + 1) % items.length);\n    } else if (e.key === 'ArrowUp') {\n      setSelectedIndex((i) => (i - 1 + items.length) % items.length);\n    } else if (e.key === 'Enter') {\n      const selected = items[selectedIndex()];\n      if (selected) {\n        handleSelect(selected);\n      }\n    }\n  };\n\n  const handleSelect = (item: any) => {\n    props.onClose();\n    if (item.type === 'pane') {\n      props.onOpenPane(item.id);\n    } else if (item.type === 'file') {\n      props.onOpenFile(item.id);\n    } else if (item.type === 'new') {\n      props.onNewFile(item.id);\n    } else if (item.type === 'action') {\n      if (item.id === 'upload') {\n        fileInputRef.click();\n      }\n    }\n  };\n\n  const handleFileUpload = (e: Event) => {\n    const file = (e.target as HTMLInputElement).files?.[0];\n    if (file) {\n      const reader = new FileReader();\n      reader.onload = (e) => {\n        const content = e.target?.result as string;\n        props.onUpload(file.name, content);\n        props.onClose();\n      };\n      reader.readAsText(file);\n    }\n  };\n\n  const [activeMenu, setActiveMenu] = createSignal<string | null>(null);\n\n  onMount(() => requestAnimationFrame(() => inputRef.focus()));\n\n  return (\n    <div class=\"h-full w-full bg-white px-6 dark:bg-neutral-900 pb-30 flex flex-col overflow-y-scroll\">\n      <input type=\"file\" ref={fileInputRef} class=\"hidden\" onChange={handleFileUpload} />\n      <div class=\"w-full mx-auto max-w-2xl\">\n        <div class=\"top-0 z-20 bg-white py-6 dark:bg-neutral-900 sticky shrink-0\">\n          <Icon\n            path={magnifyingGlass}\n            class=\"left-4 h-5 w-5 text-neutral-400 pointer-events-none absolute top-1/2 -translate-y-1/2\"\n          />\n          <Input\n            ref={inputRef}\n            type=\"text\"\n            class=\"w-full pl-10\"\n            placeholder=\"Search panes, files, or type a new filename...\"\n            value={query()}\n            onInput={(e) => {\n              setQuery(e.currentTarget.value);\n              setSelectedIndex(0);\n            }}\n            onKeyDown={handleKeyDown}\n          />\n        </div>\n\n        <For each={categories()}>\n          {(category) => (\n            <div class=\"space-y-1 flex flex-col\">\n              <Label class=\"z-10 bg-white px-3 py-2 dark:bg-neutral-900 border-neutral-200 dark:border-neutral-700 sticky top-[84px] border-b\">\n                {category.title}\n              </Label>\n              <For each={category.items}>\n                {(item) => {\n                  let btnRef: HTMLButtonElement | undefined;\n                  const isActive = () => item.globalIndex === selectedIndex();\n                  const isRenaming = () => renamingFile() === item.id;\n                  return (\n                    <div class=\"group relative\">\n                      <div\n                        class=\"w-full p-2 flex items-center rounded-lg cursor-pointer text-sm transition-colors\"\n                        classList={{\n                          'bg-neutral-200 dark:bg-neutral-700': isActive(),\n                          'hover:bg-neutral-100 dark:hover:bg-neutral-800/50': !isActive() && !isRenaming(),\n                          'hover:bg-neutral-200 dark:hover:bg-neutral-700': isActive(),\n                        }}\n                        onClick={() => !isRenaming() && handleSelect(item)}\n                      >\n                        <div\n                          class=\"mr-3 h-8 w-8 flex shrink-0 items-center justify-center rounded-full\"\n                          classList={{\n                            'bg-solidc/10 text-solidc dark:bg-neutral-600 dark:text-white': isActive(),\n                            'bg-neutral-100 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400': !isActive(),\n                          }}\n                        >\n                          <Icon path={item.icon} class=\"h-4 w-4\" />\n                        </div>\n                        <div class=\"min-w-0 flex-1 text-left\">\n                          <Show when={isRenaming()} fallback={<div class=\"truncate\">{item.label}</div>}>\n                            <Input\n                              autofocus\n                              size=\"sm\"\n                              value={item.label}\n                              onClick={(e) => e.stopPropagation()}\n                              onKeyDown={(e) => {\n                                if (e.key === 'Enter') {\n                                  e.stopPropagation();\n                                  setRenamingFile(null);\n                                  props.onRenameFile(item.id, e.currentTarget.value);\n                                } else if (e.key === 'Escape') {\n                                  setRenamingFile(null);\n                                }\n                              }}\n                              onBlur={(e) => {\n                                if (isRenaming()) {\n                                  setRenamingFile(null);\n                                  props.onRenameFile(item.id, e.currentTarget.value);\n                                }\n                              }}\n                            />\n                          </Show>\n                        </div>\n                        <div class=\"ml-2 space-x-1 flex shrink-0 items-center\">\n                          <Show when={item.type === 'file' && !isActive() && !isRenaming()}>\n                            <IconButton\n                              ref={btnRef}\n                              icon={ellipsisHorizontal}\n                              class=\"p-1 opacity-0 group-hover:opacity-100\"\n                              size=\"sm\"\n                              onClick={(e) => {\n                                e.stopPropagation();\n                                setActiveMenu(activeMenu() === item.id ? null : item.id);\n                              }}\n                            />\n                          </Show>\n                          <Show when={isActive() && !isRenaming()}>\n                            <Icon path={chevronRight} class=\"h-4 w-4\" />\n                          </Show>\n                        </div>\n                      </div>\n                      <Show when={item.type === 'file'}>\n                        <Dismiss\n                          open={() => activeMenu() === item.id}\n                          setOpen={(val) => {\n                            if (!val) setActiveMenu(null);\n                          }}\n                          menuButton={() => btnRef}\n                        >\n                          <Menu class=\"right-0 mt-1 absolute top-full\" onClose={() => setActiveMenu(null)}>\n                            <MenuItem\n                              label=\"Open\"\n                              onClick={() => {\n                                handleSelect(item);\n                                setActiveMenu(null);\n                              }}\n                            />\n                            <MenuItem\n                              label=\"Rename\"\n                              icon={pencil}\n                              onClick={() => {\n                                setRenamingFile(item.id);\n                                setActiveMenu(null);\n                              }}\n                            />\n                            <MenuItem\n                              label=\"Delete\"\n                              icon={trashIcon}\n                              variant=\"danger\"\n                              onClick={() => {\n                                if (confirm(`Delete ${item.label}?`)) {\n                                  props.onDeleteFile(item.id);\n                                }\n                                setActiveMenu(null);\n                              }}\n                            />\n                          </Menu>\n                        </Dismiss>\n                      </Show>\n                    </div>\n                  );\n                }}\n              </For>\n            </div>\n          )}\n        </For>\n        <Show when={categories().length === 0}>\n          <div class=\"mt-8 text-neutral-500 shrink-0 text-center text-sm\">\n            <p>No results found for \"{query()}\"</p>\n          </div>\n        </Show>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/preview.tsx",
    "content": "import { Component, createEffect, JSX, onCleanup, onMount } from 'solid-js';\nimport { useZoom } from '../hooks/useZoom';\nimport { Orientation, SplitviewComponent } from 'dockview-core';\nimport { SolidPanelView } from '../dockview/solid';\n\nconst dispatchZoomKeyToParent = `\n  document.addEventListener('keydown', (e) => {\n    if (!(e.ctrlKey || e.metaKey)) return;\n    if (!['=', '-'].includes(e.key)) return;\n    window.parent.postMessage({ event: 'ZOOM_KEY', value: { key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey } }, '*');\n    e.preventDefault();\n  }, true);\n`;\n\n// Sandboxed iframes get a unique opaque origin, which causes two problems for chobitsu:\n//   1. localStorage / sessionStorage throw on access (opaque origins have no storage).\n//   2. chobitsu's getUrl()/getOrigin() fall back to parent.location.{href,origin} for\n//      \"about:\" / \"null\"-origin pages, and that read is cross-origin and throws — which\n//      blanks out Page.getResourceTree, so chii's Sources panel stays empty.\n// We install in-memory storage shims and replace `parent` with a thin object that returns\n// the iframe's own location while forwarding postMessage to the real parent (so we can\n// still talk to the playground).\nconst sandboxShim = `\n  (() => {\n    const make = () => {\n      const m = new Map();\n      return {\n        getItem: (k) => (m.has(k) ? m.get(k) : null),\n        setItem: (k, v) => { m.set(k, String(v)); },\n        removeItem: (k) => { m.delete(k); },\n        clear: () => { m.clear(); },\n        key: (i) => Array.from(m.keys())[i] ?? null,\n        get length() { return m.size; },\n      };\n    };\n    Object.defineProperty(window, 'localStorage', { value: make(), configurable: true });\n    Object.defineProperty(window, 'sessionStorage', { value: make(), configurable: true });\n    const realParent = window.parent;\n    window.parent = {\n      location: { href: location.href, origin: location.origin || 'about:srcdoc' },\n      postMessage: (msg, target, transfer) => realParent.postMessage(msg, target, transfer),\n    };\n  })();\n`;\n\nconst mainIframeScript = `\n  (() => {\n    let finisher = undefined;\n    let cache = {};\n\n    const buildModule = (name, source, sources) => {\n      if (cache[name]) return cache[name];\n      cache[name] = 'error:cyclic import';\n      const out = source.replace(/(['\"])solidrepl:([^'\"]+)\\\\1/g, (_, q, rel) => {\n        if (sources[rel] == null) return q + rel + q;\n        return q + buildModule(rel, sources[rel], sources) + q;\n      });\n      const blob = new Blob([out], { type: 'text/javascript' });\n      cache[name] = URL.createObjectURL(blob);\n      return cache[name];\n    };\n\n    const handleCodeUpdate = (sources) => {\n      if (!sources || typeof sources['./main'] !== 'string') return;\n\n      window.dispose?.();\n      window.dispose = undefined;\n\n      if (document.getElementById('app')) document.getElementById('app').innerHTML = '';\n\n      console.clear();\n\n      document.getElementById('appsrc')?.remove();\n\n      for (const url of Object.values(cache)) {\n        if (typeof url === 'string' && url.startsWith('blob:')) URL.revokeObjectURL(url);\n      }\n      cache = {};\n\n      const script = document.createElement('script');\n      script.id = 'appsrc';\n      script.type = 'module';\n      finisher = () => {};\n      script.onload = () => {\n        if (finisher) finisher();\n        finisher = undefined;\n      };\n      script.src = buildModule('./main', sources['./main'], sources);\n      document.body.appendChild(script);\n\n      const load = document.getElementById('load');\n      if (load) load.remove();\n    };\n\n    const sendToDevtools = (message) => {\n      window.parent.postMessage(JSON.stringify(message), '*');\n    };\n    let id = 0;\n    const sendToChobitsu = (message) => {\n      message.id = 'tmp' + ++id;\n      chobitsu.sendRawMessage(JSON.stringify(message));\n    };\n    chobitsu.setOnMessage((message) => {\n      if (message.includes('\"id\":\"tmp')) return;\n      window.parent.postMessage(message, '*');\n    });\n\n    let pageSource = '';\n    const pageDomain = chobitsu.domain('Page');\n    if (pageDomain) {\n      pageDomain.getResourceContent = (params) => {\n        if (params.frameId === '1') {\n          return Promise.resolve({ base64Encoded: false, content: pageSource });\n        }\n        return Promise.resolve({ base64Encoded: false, content: '' });\n      };\n    }\n\n    const handle = (data) => {\n      try {\n        const { event, value } = data;\n        if (event === 'CODE_UPDATE') {\n          const next = () => handleCodeUpdate(value);\n          if (finisher !== undefined) finisher = next;\n          else next();\n        } else if (event === 'IMPORT_MAP') {\n          document.getElementById('importmap')?.remove();\n          const importMap = document.createElement('script');\n          importMap.id = 'importmap';\n          importMap.type = 'importmap';\n          importMap.textContent = JSON.stringify({ imports: value });\n          document.head.appendChild(importMap);\n        } else if (event === 'DARK') {\n          document.documentElement.classList.toggle('dark', value);\n        } else if (event === 'PAGE_SOURCE') {\n          pageSource = value;\n        } else if (event === 'DEV') {\n          chobitsu.sendRawMessage(data.data);\n        } else if (event === 'LOADED') {\n          sendToDevtools({\n            method: 'Page.frameNavigated',\n            params: {\n              frame: { id: '1', mimeType: 'text/html', securityOrigin: parent.location.origin, url: parent.location.href },\n              type: 'Navigation',\n            },\n          });\n          sendToChobitsu({ method: 'Network.enable' });\n          sendToDevtools({ method: 'Runtime.executionContextsCleared' });\n          sendToChobitsu({ method: 'Runtime.enable' });\n          sendToChobitsu({ method: 'Debugger.enable' });\n          sendToChobitsu({ method: 'DOMStorage.enable' });\n          sendToChobitsu({ method: 'DOM.enable' });\n          sendToChobitsu({ method: 'CSS.enable' });\n          sendToChobitsu({ method: 'Overlay.enable' });\n          sendToDevtools({ method: 'DOM.documentUpdated' });\n        }\n      } catch (e) {\n        console.error(e);\n      }\n    };\n\n    window.addEventListener('message', (e) => handle(e.data));\n\n    ${dispatchZoomKeyToParent}\n  })();\n`;\n\nconst iframeHtml = `<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link href=\"https://ga.jspm.io/npm:modern-normalize@3.0.1/modern-normalize.css\" rel=\"stylesheet\" />\n    <style>\n      html, body { position: relative; width: 100%; height: 100%; }\n      body {\n        color: #333; margin: 0; padding: 8px; box-sizing: border-box;\n        font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n        max-width: 100%;\n      }\n      .dark body { color: #e5e7eb; }\n      .dark { color-scheme: dark; }\n      input, button, select, textarea {\n        padding: 0.4em; margin: 0 0 0.5em 0; box-sizing: border-box;\n        border: 1px solid #ccc; border-radius: 2px;\n      }\n      button { color: #333; background-color: #f4f4f4; outline: none; }\n      button:disabled { color: #999; }\n      button:not(:disabled):active { background-color: #ddd; }\n      button:focus { border-color: #666; }\n    </style>\n    <script>${sandboxShim}</script>\n    <script src=\"https://cdn.jsdelivr.net/npm/chobitsu@1.8.6/dist/chobitsu.min.js\"></script>\n    <script>${mainIframeScript}</script>\n  </head>\n  <body>\n    <div id=\"load\" style=\"display: flex; height: 80vh; align-items: center; justify-content: center\">\n      <p style=\"font-size: 1.5rem\">Loading the playground...</p>\n    </div>\n    <div id=\"app\"></div>\n  </body>\n</html>`;\n\nconst useDevtoolsSrc = () => {\n  const html = `\n  <!DOCTYPE html>\n  <html lang=\"en\">\n  <meta charset=\"utf-8\">\n  <title>DevTools</title>\n  <style>\n    @media (prefers-color-scheme: dark) {\n      body {\n        background-color: rgb(41 42 45);\n      }\n    }\n  </style>\n  <script>${dispatchZoomKeyToParent}</script>\n  <meta name=\"referrer\" content=\"no-referrer\">\n  <script src=\"https://unpkg.com/@ungap/custom-elements/es.js\"></script>\n  <script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/chii@1.15.5/public/front_end/entrypoints/chii_app/chii_app.js\"></script>\n  <body class=\"undocked\" id=\"-blink-dev-tools\">`;\n  const devtoolsRawUrl = URL.createObjectURL(new Blob([html], { type: 'text/html' }));\n  onCleanup(() => URL.revokeObjectURL(devtoolsRawUrl));\n  return `${devtoolsRawUrl}#?embedded=${encodeURIComponent(location.origin)}`;\n};\n\nexport const Preview: Component<Props> = (props) => {\n  const { zoomState } = useZoom();\n\n  let iframe!: HTMLIFrameElement;\n  let devtoolsIframe!: HTMLIFrameElement;\n  let outerContainer!: HTMLDivElement;\n\n  let devtoolsLoaded = false;\n  let isIframeReady = false;\n\n  const sendToIframe = (msg: any) => {\n    if (!isIframeReady) return;\n    iframe.contentWindow!.postMessage(msg, '*');\n  };\n\n  createEffect(() => {\n    if (!props.reloadSignal) return;\n\n    isIframeReady = false;\n    iframe.srcdoc = iframeHtml;\n  });\n\n  const devtoolsSrc = useDevtoolsSrc();\n\n  const styleScale = () => {\n    const pointerEvents = props.pointerEvents ? 'inherit' : 'none';\n    if (zoomState.scale === 100 || !zoomState.scaleIframe) return `pointer-events: ${pointerEvents};`;\n\n    return `pointer-events: ${pointerEvents}; width: ${zoomState.scale}%; height: ${zoomState.scale}%; transform: scale(${\n      zoomState.zoom / 100\n    }); transform-origin: 0 0;`;\n  };\n\n  onMount(() => {\n    const frameworkComponents: Record<string, () => JSX.Element> = {\n      preview: () => (\n        <iframe\n          title=\"Solid REPL\"\n          class=\"h-full min-h-0 w-full min-w-0 bg-white p-0 dark:bg-neutral-900 block overflow-scroll\"\n          style={styleScale()}\n          ref={iframe}\n          srcdoc={iframeHtml}\n          onload={() => {\n            isIframeReady = true;\n\n            if (devtoolsLoaded) sendToIframe({ event: 'LOADED' });\n            sendToIframe({ event: 'PAGE_SOURCE', value: iframeHtml });\n            sendToIframe({ event: 'IMPORT_MAP', value: props.importMap });\n            if (props.code['./main']) sendToIframe({ event: 'CODE_UPDATE', value: props.code });\n            sendToIframe({ event: 'DARK', value: props.isDark });\n          }}\n          // @ts-ignore\n          sandbox=\"allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-modals allow-pointer-lock\"\n        />\n      ),\n      devtools: () => (\n        <iframe\n          title=\"Devtools\"\n          class=\"h-full min-h-0 w-full min-w-0\"\n          style={`pointer-events: ${props.pointerEvents ? 'inherit' : 'none'}`}\n          ref={devtoolsIframe}\n          src={devtoolsSrc}\n          onload={() => (devtoolsLoaded = true)}\n          classList={{ block: props.devtools, hidden: !props.devtools }}\n        />\n      ),\n    };\n    const splitview = new SplitviewComponent(outerContainer, {\n      orientation: Orientation.VERTICAL,\n\n      createComponent: ({ id, name }) => {\n        return new SolidPanelView(id, name, frameworkComponents[name]);\n      },\n    });\n    splitview.addPanel({\n      id: 'preview',\n      component: 'preview',\n      minimumSize: 100,\n    });\n    splitview.addPanel({\n      id: 'devtools',\n      component: 'devtools',\n      minimumSize: 100,\n      snap: true,\n    });\n\n    createEffect(() => {\n      sendToIframe({ event: 'DARK', value: props.isDark });\n    });\n\n    createEffect(() => {\n      sendToIframe({ event: 'IMPORT_MAP', value: props.importMap });\n    });\n\n    createEffect(() => {\n      if (!props.code['./main']) return;\n      sendToIframe({ event: 'CODE_UPDATE', value: props.code });\n    });\n\n    const messageListener = (event: MessageEvent) => {\n      if (event.data?.event === 'ZOOM_KEY') {\n        document.dispatchEvent(new KeyboardEvent('keydown', event.data.value));\n        return;\n      }\n      if (event.source === iframe.contentWindow) {\n        devtoolsIframe.contentWindow!.postMessage(event.data, '*');\n      }\n      if (event.source === devtoolsIframe.contentWindow) {\n        iframe.contentWindow!.postMessage({ event: 'DEV', data: event.data }, '*');\n      }\n    };\n    window.addEventListener('message', messageListener);\n    onCleanup(() => window.removeEventListener('message', messageListener));\n\n    createEffect(() => {\n      localStorage.setItem('uiTheme', props.isDark ? '\"dark\"' : '\"default\"');\n\n      if (!devtoolsLoaded) return;\n\n      devtoolsIframe.contentWindow!.location.reload();\n    });\n  });\n\n  return <div class=\"min-h-0 flex flex-1 flex-col\" ref={outerContainer} classList={props.classList}></div>;\n};\n\ntype Props = {\n  importMap: Record<string, string>;\n  classList?: {\n    [k: string]: boolean | undefined;\n  };\n  code: Record<string, string>;\n  reloadSignal: boolean;\n  devtools: boolean;\n  isDark: boolean;\n  pointerEvents: boolean;\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/repl.tsx",
    "content": "import { createSignal, createEffect, batch, onCleanup, createMemo, onMount, Show, createRoot, JSX } from 'solid-js';\nimport { unwrap } from 'solid-js/store';\nimport { Preview } from './preview';\nimport { Error } from './error';\nimport { throttle } from '@solid-primitives/scheduled';\nimport { editor, Uri } from 'monaco-editor';\nimport { createMonacoTabs } from './editor/monacoTabs';\nimport { NewTab } from './newTab';\nimport { CompileMode, compileOptions } from './CompileMode';\nimport { IconButton } from './ui/IconButton';\n\nimport Editor from './editor';\nimport type { Repl as ReplProps } from 'solid-repl/dist/repl';\nimport type { Tab } from 'solid-repl';\nimport { DockviewComponent, Orientation, GroupPanelPartInitParameters, themeAbyssSpaced } from 'dockview-core';\nimport { insert } from 'solid-js/web';\nimport { Icon } from 'solid-heroicons';\nimport { plus, trash, pencil, xMark } from 'solid-heroicons/outline';\nimport '../../node_modules/dockview-core/dist/styles/dockview.css';\nimport { Menu, MenuItem } from './ui/Menu';\nimport Dismiss from 'solid-dismiss';\n\nconst getImportMap = (tabs: Tab[]): Record<string, string> => {\n  try {\n    const rawImportMap = tabs.find((tab) => tab.name === 'import_map.json');\n    return JSON.parse(rawImportMap?.source ?? '{}');\n  } catch {\n    return {};\n  }\n};\n\nexport const Repl: ReplProps = (props) => {\n  const { compiler, formatter, linter } = props;\n  let now: number;\n\n  const [error, setError] = createSignal('');\n  const [output, setOutput] = createSignal<Record<string, string>>({});\n  const [universalModuleName, setUniversalModuleName] = createSignal('solid-universal-module');\n  const [mode, setMode] = createSignal<(typeof compileOptions)[keyof typeof compileOptions]>(compileOptions.DOM);\n\n  const userTabs = () => props.tabs.filter((tab) => tab.name != 'import_map.json');\n\n  const [outputVisible, setOutputVisible] = createSignal(false);\n  const [previewVisible, setPreviewVisible] = createSignal(false);\n  const [importMap, setImportMap] = createSignal(getImportMap(props.tabs), {\n    equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),\n  });\n\n  const outputUri = Uri.parse(`file:///${props.id}/output_dont_import.ts`);\n  const outputModel = editor.createModel('', 'typescript', outputUri);\n  onCleanup(() => outputModel.dispose());\n\n  const onCompilerMessage = ({ data }: any) => {\n    const { event, compiled, externals, error } = data;\n    if (event === 'ERROR') {\n      console.error(error);\n      return setError(error.message);\n    } else setError('');\n\n    if (event === 'BABEL') {\n      outputModel.setValue(compiled);\n    }\n\n    if (event === 'ROLLUP') {\n      const currentMap = { ...importMap() };\n      for (const file in currentMap) {\n        // Catch any `jspm.dev` URLs and migrate them to `esm.sh`\n        if (currentMap[file] === `https://jspm.dev/${file}`) {\n          currentMap[file] = `https://esm.sh/${file}`;\n        }\n        if (!(file in externals) && currentMap[file] === `https://esm.sh/${file}`) {\n          delete currentMap[file];\n        }\n      }\n      for (const file in externals) {\n        if (!(file in currentMap)) {\n          currentMap[file] = externals[file];\n        }\n      }\n      console.log(`Compilation took: ${performance.now() - now}ms`);\n\n      batch(() => {\n        let tab = props.tabs.find((tab) => tab.name === 'import_map.json');\n        if (!tab) {\n          tab = {\n            name: 'import_map.json',\n            source: JSON.stringify(currentMap, null, 2),\n          };\n          props.setTabs(props.tabs.concat(tab));\n        } else {\n          tab.source = JSON.stringify(currentMap, null, 2);\n          const { model } = monacoTabs().get(`file:///${props.id}/import_map.json`)!;\n          model.setValue(tab.source);\n        }\n\n        setOutput(compiled);\n        setImportMap(currentMap);\n      });\n    }\n  };\n  compiler.addEventListener('message', onCompilerMessage);\n  onCleanup(() => compiler.removeEventListener('message', onCompilerMessage));\n\n  /**\n   * We need to debounce a bit the compilation because\n   * it takes ~15ms to compile with the web worker...\n   * Also, real time feedback can be stressful\n   */\n  const sendCompile = (message: any) => {\n    now = performance.now();\n\n    compiler.postMessage(message);\n  };\n  const applyRollupCompilation = throttle(sendCompile, 250);\n  const applyBabelCompilation = throttle(sendCompile, 250);\n\n  const compile = () => {\n    if (previewVisible()) {\n      applyRollupCompilation({\n        event: 'ROLLUP',\n        tabs: unwrap(userTabs()),\n      });\n    }\n    if (outputVisible() && props.current?.endsWith('.tsx')) {\n      let compileOpts = mode();\n      if (compileOpts === compileOptions.UNIVERSAL) {\n        compileOpts = {\n          generate: 'universal',\n          hydratable: false,\n          moduleName: universalModuleName(),\n        };\n      }\n      applyBabelCompilation({\n        event: 'BABEL',\n        tab: unwrap(props.tabs.find((tab) => tab.name == props.current)),\n        compileOpts,\n      });\n    }\n  };\n\n  /**\n   * The heart of the playground. This recompile on\n   * every tab source changes.\n   */\n  createEffect(() => {\n    if (!props.tabs.length) return;\n\n    compile();\n  });\n  const monacoTabs = createMonacoTabs(props.id, () => props.tabs);\n  const currentModel = createMemo(() => monacoTabs().get(`file:///${props.id}/${props.current}`)!.model);\n\n  let ref!: HTMLDivElement;\n\n  const [reloadSignal] = createSignal(false, { equals: false });\n  const [devtoolsOpen] = createSignal(!props.hideDevtools);\n  const [displayErrors, setDisplayErrors] = createSignal(true);\n\n  onMount(() => {\n    const newFile = (name: string) => {\n      if (!name.trim()) return;\n      const newTab = {\n        name: name,\n        source: '',\n      };\n      batch(() => {\n        props.setTabs(props.tabs.concat(newTab));\n        props.setCurrent(newTab.name);\n      });\n      dockview.addPanel({\n        id: name,\n        tabComponent: 'file',\n        component: 'editor',\n        params: {\n          currentModel: monacoTabs().get(`file:///${props.id}/${name}`)!.model,\n        },\n      });\n    };\n\n    const deleteFile = (name: string) => {\n      if (name === 'main.tsx') return;\n      const newTabs = props.tabs.filter((tab) => tab.name !== name);\n      const panel = dockview.getGroupPanel(name);\n      panel?.api.close();\n      batch(() => {\n        props.setTabs(newTabs);\n        if (props.current === name) {\n          const mainPanel = dockview.getGroupPanel('main.tsx');\n          mainPanel?.focus();\n          props.setCurrent('main.tsx');\n        }\n      });\n    };\n\n    const renameFile = (oldName: string, newName: string) => {\n      if (oldName === 'main.tsx') return;\n      if (newName === oldName) return;\n      if (!newName.trim()) return;\n      const exists = props.tabs.some((tab) => tab.name === newName);\n      if (exists) {\n        alert('A file with that name already exists');\n        return;\n      }\n\n      const tab = props.tabs.find((t) => t.name === oldName);\n      if (!tab) return;\n\n      const newTabs = props.tabs.map((t) => (t.name === oldName ? { ...t, name: newName } : t));\n\n      batch(() => {\n        props.setTabs(newTabs);\n        if (props.current === oldName) {\n          props.setCurrent(newName);\n        }\n      });\n\n      // Update Panel ID if it exists? Dockview often requires adding/removing for ID change.\n      // Easiest is to close old and open new at same position or just let user re-open.\n      // But we can try to keep it simple: if open, close and reopen.\n      const panel = dockview.getGroupPanel(oldName);\n      if (panel) {\n        panel.api.close();\n        dockview.addPanel({\n          id: newName,\n          tabComponent: 'file',\n          component: 'editor',\n          params: {\n            currentModel: monacoTabs().get(`file:///${props.id}/${newName}`)!.model,\n          },\n        });\n      }\n    };\n\n    const dockview = new DockviewComponent(ref, {\n      theme: themeAbyssSpaced,\n      defaultTabComponent: 'default',\n      createLeftHeaderActionComponent: () => {\n        const element = (<div class=\"h-full px-1 flex items-center\"></div>) as HTMLDivElement;\n        let disposer: () => void;\n\n        return {\n          element,\n          init: (params) => {\n            createRoot((dispose) => {\n              disposer = dispose;\n\n              insert(element, () => (\n                <IconButton\n                  icon={plus}\n                  class=\"h-[28px]\"\n                  size=\"sm\"\n                  onClick={() => {\n                    const panel = dockview.getPanel('newTab');\n                    if (panel) {\n                      panel.focus();\n                    } else {\n                      params.group.focus();\n                      dockview.addPanel({\n                        id: 'newTab',\n                        title: 'New Tab',\n                        tabComponent: 'default',\n                        component: 'newTab',\n                      });\n                    }\n                  }}\n                  title=\"New Tab\"\n                >\n                  <span class=\"sr-only\">New tab</span>\n                </IconButton>\n              ));\n            });\n          },\n          dispose: () => disposer?.(),\n        };\n      },\n      createTabComponent: (panel) => {\n        const element = (<div class=\"h-full pl-2 flex items-center\"></div>) as HTMLDivElement;\n        let disposer: () => void;\n\n        return {\n          element,\n          init: (params) => {\n            const [showMenu, setShowMenu] = createSignal(false);\n            const [menuPos, setMenuPos] = createSignal({ x: 0, y: 0 });\n\n            createRoot((dispose) => {\n              disposer = dispose;\n              const [panelTitle, setPanelTitle] = createSignal(params.title);\n              params.api.onDidTitleChange((e) => {\n                setPanelTitle(e.title);\n              });\n              const isFile = panel.name == 'file';\n              let containerRef: HTMLDivElement | undefined;\n\n              insert(element, () => (\n                <div\n                  ref={containerRef}\n                  class=\"h-full space-x-2 group flex items-center\"\n                  onContextMenu={(e) => {\n                    if (!isFile) return;\n                    e.preventDefault();\n                    e.stopPropagation();\n                    setMenuPos({ x: e.clientX, y: e.clientY });\n                    setShowMenu(true);\n                  }}\n                >\n                  <span class=\"truncate text-sm\">{panelTitle()}</span>\n\n                  <button\n                    class=\"p-0.5 hover:bg-neutral-200 dark:hover:bg-neutral-700 ml-auto rounded-sm opacity-0 transition-opacity group-hover:opacity-100\"\n                    onClick={(e) => {\n                      if (e.defaultPrevented) return;\n                      e.preventDefault();\n                      params.api.close();\n                    }}\n                    title=\"Close tab\"\n                  >\n                    <Icon path={xMark} class=\"h-3 w-3 text-neutral-500\" />\n                  </button>\n\n                  <Show when={isFile && showMenu()}>\n                    <Dismiss\n                      open={() => true}\n                      setOpen={(val) => {\n                        if (!val) setShowMenu(false);\n                      }}\n                      show\n                      menuButton={containerRef}\n                    >\n                      <Menu\n                        style={{ top: `${menuPos().y}px`, left: `${menuPos().x}px` }}\n                        onClose={() => setShowMenu(false)}\n                      >\n                        <MenuItem\n                          label=\"Rename\"\n                          icon={pencil}\n                          onClick={() => {\n                            const newName = prompt('Rename file to:', panelTitle());\n                            if (newName) renameFile(panelTitle(), newName);\n                            setShowMenu(false);\n                          }}\n                        />\n                        <MenuItem\n                          label=\"Delete\"\n                          icon={trash}\n                          variant=\"danger\"\n                          onClick={() => {\n                            if (confirm(`Delete ${panelTitle()}?`)) {\n                              deleteFile(panelTitle());\n                            }\n                            setShowMenu(false);\n                          }}\n                        />\n                      </Menu>\n                    </Dismiss>\n                  </Show>\n                </div>\n              ));\n            });\n\n            const hide = () => setShowMenu(false);\n            window.addEventListener('click', hide);\n            onCleanup(() => window.removeEventListener('click', hide));\n          },\n          dispose: () => disposer?.(),\n        };\n      },\n      createRightHeaderActionComponent: () => {\n        const element = (<div class=\"h-full px-1 flex items-center justify-end\"></div>) as HTMLDivElement;\n        let disposer: () => void;\n\n        return {\n          element,\n          init: (params) => {\n            const [isTSX, setIsTSX] = createSignal(false);\n            params.group.api.onDidActivePanelChange((e) => {\n              if (!e) return;\n              setIsTSX(e.panel.id.endsWith('.tsx'));\n            });\n            createRoot((dispose) => {\n              disposer = dispose;\n\n              insert(element, () => (\n                <Show when={isTSX()}>\n                  <IconButton\n                    icon={trash}\n                    class=\"h-[28px]\"\n                    size=\"sm\"\n                    onClick={() => {\n                      const confirmReset = confirm('Are you sure you want to reset the editor?');\n                      if (!confirmReset) return;\n                      props.reset();\n                    }}\n                    title=\"Reset Editor\"\n                  >\n                    <span class=\"sr-only\">Reset Editor</span>\n                  </IconButton>\n                </Show>\n              ));\n            });\n          },\n          dispose: () => disposer?.(),\n        };\n      },\n      createComponent(options) {\n        const element = (<div class=\"h-full flex flex-col\"></div>) as HTMLDivElement;\n        let disposer: () => void;\n        let onInit: ((params: GroupPanelPartInitParameters) => (() => void) | void) | undefined;\n\n        let component: (\n          params: GroupPanelPartInitParameters['params'],\n          x: GroupPanelPartInitParameters,\n        ) => JSX.Element = () => null;\n\n        switch (options.name) {\n          case 'newTab':\n            component = (_, params) => (\n              <NewTab\n                tabs={props.tabs}\n                onOpenPane={(id) => {\n                  const panel = dockview.getGroupPanel(id);\n                  if (panel) {\n                    panel.focus();\n                  } else {\n                    dockview.addPanel({\n                      id,\n                      tabComponent: 'default',\n                      component: id.toLowerCase(),\n                      renderer: id === 'Preview' ? 'always' : undefined,\n                    });\n                  }\n                }}\n                onOpenFile={(name) => {\n                  const panel = dockview.getGroupPanel(name);\n                  if (panel) {\n                    panel.focus();\n                  } else {\n                    dockview.addPanel({\n                      id: name,\n                      tabComponent: 'file',\n                      component: 'editor',\n                      params: {\n                        currentModel: monacoTabs().get(`file:///${props.id}/${name}`)!.model,\n                      },\n                    });\n                  }\n                }}\n                onNewFile={newFile}\n                onUpload={(name: string, source: string) => {\n                  const newTab = { name, source };\n                  batch(() => {\n                    props.setTabs(props.tabs.concat(newTab));\n                    props.setCurrent(newTab.name);\n                  });\n                  setTimeout(() => {\n                    dockview.addPanel({\n                      id: name,\n                      tabComponent: 'file',\n                      component: 'editor',\n                      params: {\n                        currentModel: monacoTabs().get(`file:///${props.id}/${name}`)!.model,\n                      },\n                    });\n                  });\n                }}\n                onDeleteFile={deleteFile}\n                onRenameFile={renameFile}\n                onClose={() => {\n                  params.api.close();\n                }}\n              />\n            );\n            break;\n          case 'editor':\n            component = (params) => (\n              <Editor\n                model={params.currentModel}\n                onDocChange={(code: string) => {\n                  if (params.currentModel.uri.path.includes('import_map.json')) {\n                    const newImportMap = JSON.parse(code);\n                    setImportMap(newImportMap);\n                  } else {\n                    compile();\n                  }\n                }}\n                onUserEdit={props.onUserEdit}\n                formatter={formatter}\n                linter={linter}\n                isDark={props.dark}\n                withMinimap={false}\n                displayErrors={displayErrors()}\n                setDisplayErrors={setDisplayErrors}\n              />\n            );\n            break;\n          case 'preview':\n            setPreviewVisible(true);\n            onCleanup(() => {\n              setPreviewVisible(false);\n            });\n            const [previewIsActive, setPreviewIsActive] = createSignal(false);\n            component = () => (\n              <Preview\n                importMap={importMap()}\n                code={output()}\n                reloadSignal={reloadSignal()}\n                devtools={devtoolsOpen()}\n                isDark={props.dark}\n                pointerEvents={previewIsActive()}\n              />\n            );\n            onInit = (params) => {\n              setPreviewIsActive(params.api.isActive);\n              const disposable = params.api.onDidActiveChange((e) => {\n                setPreviewIsActive(e.isActive);\n              });\n              return () => disposable.dispose();\n            };\n            break;\n          case 'output':\n            setOutputVisible(true);\n            onCleanup(() => {\n              setOutputVisible(false);\n            });\n            component = () => (\n              <section class=\"min-h-0 min-w-0 divide-y-1 divide-slate-200 dark:divide-neutral-800 relative flex flex-1 flex-col\">\n                <Editor model={outputModel} isDark={props.dark} disabled withMinimap={false} />\n\n                <CompileMode\n                  mode={mode()}\n                  setMode={setMode}\n                  universalModuleName={universalModuleName()}\n                  setUniversalModuleName={setUniversalModuleName}\n                />\n              </section>\n            );\n            break;\n        }\n\n        let onInitDisposer: (() => void) | undefined;\n\n        return {\n          element,\n          init: (params) => {\n            const result = onInit?.(params);\n            if (result) onInitDisposer = result;\n            createRoot((dispose) => {\n              insert(element, () => component(params.params, params));\n              disposer = dispose;\n            });\n          },\n          dispose: () => {\n            onInitDisposer?.();\n            disposer?.();\n          },\n        };\n      },\n    });\n\n    dockview.fromJSON({\n      grid: {\n        root: {\n          type: 'branch',\n          data: [\n            {\n              type: 'leaf',\n              data: { views: ['main.tsx'], activeView: 'main.tsx', id: '1' },\n              size: 400,\n            },\n            {\n              type: 'leaf',\n              data: { views: ['Preview', 'Output'], activeView: 'Preview', id: '2' },\n              size: 250,\n            },\n          ],\n          size: 480,\n        },\n        width: 1600,\n        height: 480,\n        orientation: Orientation.HORIZONTAL,\n      },\n      activeGroup: '1',\n      panels: {\n        Output: {\n          id: 'Output',\n          tabComponent: 'default',\n          contentComponent: 'output',\n        },\n        Preview: {\n          id: 'Preview',\n          contentComponent: 'preview',\n          tabComponent: 'default',\n          renderer: 'always',\n        },\n        [props.current!]: {\n          id: props.current!,\n          tabComponent: 'file',\n          contentComponent: 'editor',\n          params: {\n            currentModel: currentModel(),\n          },\n        },\n      },\n    });\n\n    dockview.onDidActivePanelChange((e) => {\n      if (!e) return;\n      if ('currentModel' in (e.params ?? {})) {\n        props.setCurrent(e.id);\n      }\n    });\n  });\n\n  return (\n    <div class=\"h-full min-h-0 text-black dark:text-white flex flex-1 flex-col overflow-hidden font-sans\">\n      <div ref={ref} class=\"min-h-0 flex flex-1 flex-col\" />\n      <Show when={error()}>\n        <Error message={error()} onDismiss={() => setError('')} />\n      </Show>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/ui/Button.tsx",
    "content": "import { ParentComponent, JSX, splitProps } from 'solid-js';\n\nconst baseClasses =\n  'flex items-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-solidc disabled:pointer-events-none disabled:opacity-50 space-x-2';\n\nconst variants = {\n  primary: 'bg-solidc text-white hover:bg-solidc/90 px-4 py-2 justify-center',\n  ghost: 'hover:bg-neutral-200 dark:hover:bg-neutral-800 text-neutral-700 dark:text-neutral-300 px-2 py-1.5',\n};\n\nexport interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {\n  variant?: keyof typeof variants;\n  active?: boolean;\n}\n\nexport const Button: ParentComponent<ButtonProps> = (props) => {\n  const [local, others] = splitProps(props, ['variant', 'active', 'class', 'classList', 'children']);\n\n  return (\n    <button\n      {...others}\n      class={`${baseClasses} ${variants[local.variant || 'ghost']} ${local.class || ''}`}\n      classList={{\n        'bg-neutral-200 dark:bg-neutral-700 text-black dark:text-white': local.active,\n        ...local.classList,\n      }}\n    >\n      {local.children}\n    </button>\n  );\n};\n\nexport interface LinkButtonProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {\n  variant?: keyof typeof variants;\n  active?: boolean;\n}\n\nexport const LinkButton: ParentComponent<LinkButtonProps> = (props) => {\n  const [local, others] = splitProps(props, ['variant', 'active', 'class', 'classList', 'children']);\n\n  return (\n    <a\n      {...others}\n      class={`${baseClasses} ${variants[local.variant || 'ghost']} ${local.class || ''}`}\n      classList={{\n        'bg-neutral-200 dark:bg-neutral-700 text-black dark:text-white': local.active,\n        ...local.classList,\n      }}\n    >\n      {local.children}\n    </a>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/ui/Checkbox.tsx",
    "content": "import { Component, JSX, splitProps } from 'solid-js';\n\nexport interface CheckboxProps extends JSX.InputHTMLAttributes<HTMLInputElement> {\n  label: string;\n}\n\nexport const Checkbox: Component<CheckboxProps> = (props) => {\n  const [local, others] = splitProps(props, ['label', 'class', 'classList']);\n\n  return (\n    <label\n      class={`space-x-2 dark:text-white flex cursor-pointer items-center ${local.class || ''}`}\n      classList={local.classList}\n    >\n      <input\n        type=\"checkbox\"\n        {...others}\n        class=\"h-4 w-4 border-neutral-300 dark:border-neutral-700 dark:bg-neutral-800 shrink-0 cursor-pointer rounded-sm text-solidc focus:ring-solidc\"\n      />\n      <span class=\"text-sm\">{local.label}</span>\n    </label>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/ui/IconButton.tsx",
    "content": "import { Component, JSX } from 'solid-js';\nimport { Icon } from 'solid-heroicons';\n\nexport interface IconButtonProps {\n  ref?: HTMLButtonElement;\n  icon: any;\n  onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;\n  title?: string;\n  active?: boolean;\n  disabled?: boolean;\n  class?: string;\n  size?: 'sm' | 'md' | 'lg';\n  children?: JSX.Element;\n}\n\nconst sizes = {\n  sm: 'p-1',\n  md: 'p-1.5',\n  lg: 'p-2',\n};\n\nconst iconSizes = {\n  sm: 'h-4 w-4',\n  md: 'h-5 w-5',\n  lg: 'h-6 w-6',\n};\n\nexport const IconButton: Component<IconButtonProps> = (props) => {\n  return (\n    <button\n      ref={props.ref}\n      type=\"button\"\n      onClick={props.onClick}\n      title={props.title}\n      disabled={props.disabled}\n      class={`hover:bg-neutral-200 dark:hover:bg-neutral-700 inline-flex items-center justify-center rounded-md opacity-80 transition-all hover:opacity-100 disabled:pointer-events-none disabled:opacity-50 ${\n        sizes[props.size || 'md']\n      } ${props.class || ''}`}\n      classList={{\n        'bg-neutral-200 dark:bg-neutral-700 opacity-100': props.active,\n      }}\n    >\n      <Icon path={props.icon} class={iconSizes[props.size || 'md']} />\n      {props.children}\n    </button>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/ui/Input.tsx",
    "content": "import { Component, JSX, splitProps } from 'solid-js';\n\nexport interface InputProps extends JSX.InputHTMLAttributes<HTMLInputElement> {\n  size?: 'sm' | 'md';\n  inline?: boolean;\n}\n\nconst sizes = {\n  sm: 'px-2 py-1',\n  md: 'px-3 py-2',\n};\n\nexport const Input: Component<InputProps> = (props) => {\n  const [local, others] = splitProps(props, ['class', 'size', 'inline']);\n\n  return (\n    <input\n      {...others}\n      class={`border-neutral-200 bg-transparent dark:border-neutral-700 rounded-md border text-sm transition-colors duration-200 focus:border-solidc focus:outline-none dark:focus:border-solidc ${\n        local.inline ? 'inline-block w-auto' : 'block w-full'\n      } ${sizes[local.size || 'md']} ${local.class || ''}`}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/ui/Label.tsx",
    "content": "import { ParentComponent, JSX, splitProps } from 'solid-js';\n\nexport const Label: ParentComponent<JSX.LabelHTMLAttributes<HTMLLabelElement>> = (props) => {\n  const [local, others] = splitProps(props, ['class', 'children']);\n\n  return (\n    <label {...others} class={`text-neutral-500 dark:text-neutral-400 text-sm font-semibold ${local.class || ''}`}>\n      {local.children}\n    </label>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/components/ui/Menu.tsx",
    "content": "import { ParentComponent, Component, Show } from 'solid-js';\nimport { Icon } from 'solid-heroicons';\n\nexport interface MenuProps {\n  onClose: () => void;\n  class?: string;\n  style?: JSX.CSSProperties;\n}\n\nexport const Menu: ParentComponent<MenuProps> = (props) => {\n  return (\n    <div\n      class={`min-w-32 border-neutral-200 bg-white py-1 dark:border-neutral-700 dark:bg-neutral-800 z-[1000] rounded-lg border shadow-lg ${\n        props.class || 'fixed'\n      }`}\n      style={props.style}\n      onClick={(e) => e.stopPropagation()}\n    >\n      {props.children}\n    </div>\n  );\n};\n\nexport interface MenuItemProps {\n  label: string;\n  icon?: any;\n  onClick: () => void;\n  variant?: 'danger' | 'default';\n}\n\nexport const MenuItem: Component<MenuItemProps> = (props) => {\n  return (\n    <button\n      class=\"w-full space-x-2 px-3 py-2 flex items-center text-left text-sm transition-colors\"\n      classList={{\n        'hover:bg-neutral-50 dark:hover:bg-neutral-700': props.variant !== 'danger',\n        'text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20': props.variant === 'danger',\n      }}\n      onClick={() => {\n        props.onClick();\n      }}\n    >\n      <Show when={props.icon} fallback={<div class=\"h-3 w-3\" />}>\n        <Icon path={props.icon} class=\"h-3 w-3\" />\n      </Show>\n      <span>{props.label}</span>\n    </button>\n  );\n};\n"
  },
  {
    "path": "packages/solid-repl/src/dockview/solid.tsx",
    "content": "import { GridviewPanel, SplitviewPanel } from 'dockview-core';\nimport { createRoot } from 'solid-js';\nimport { insert } from 'solid-js/web';\n\nexport class SolidPanelView extends SplitviewPanel {\n  constructor(\n    id: string,\n    component: string,\n    private readonly myComponent: any,\n  ) {\n    super(id, component);\n  }\n  getComponent() {\n    const dispose = createRoot((dispose) => {\n      insert(this.element, () => this.myComponent(this.params));\n      return dispose;\n    });\n    return {\n      update: () => {},\n      dispose,\n    };\n  }\n}\n\nexport class SolidGridPanelView extends GridviewPanel {\n  constructor(\n    id: string,\n    component: string,\n    private readonly myComponent: any,\n  ) {\n    super(id, component);\n  }\n  getComponent() {\n    const dispose = createRoot((dispose) => {\n      insert(this.element, () => this.myComponent.call(this, this.params));\n      return dispose;\n    });\n    return {\n      update: () => {},\n      dispose,\n    };\n  }\n}\n"
  },
  {
    "path": "packages/solid-repl/src/hooks/useZoom.ts",
    "content": "import { createStore, SetStoreFunction } from 'solid-js/store';\n\ntype ZoomState = {\n  zoom: number;\n  scaleIframe: boolean;\n  overrideNative: boolean;\n  fontSize: number;\n  scale: number;\n};\n\nconst getLS = () => {\n  const result = localStorage.getItem('zoomState');\n  if (result == null) return null;\n  return JSON.parse(result) as ZoomState;\n};\n\nconst ls = getLS();\nconst initFontSize = 14;\nconst initScale = 100;\n\nconst [zoomState, setZoomStateInternal] = createStore<ZoomState>({\n  overrideNative: ls?.overrideNative || true,\n  scaleIframe: ls?.scaleIframe || true,\n  zoom: ls?.zoom || 100,\n  get fontSize() {\n    return initFontSize * (this.zoom / 100);\n  },\n  get scale() {\n    return initScale * (100 / this.zoom);\n  },\n});\n\nconst setZoomState: SetStoreFunction<ZoomState> = (...args: any[]) => {\n  (setZoomStateInternal as any)(...args);\n  localStorage.setItem(\n    'zoomState',\n    JSON.stringify({\n      zoom: zoomState.zoom,\n      scaleIframe: zoomState.scaleIframe,\n      overrideNative: zoomState.overrideNative,\n    }),\n  );\n};\n\nexport const useZoom = () => {\n  const updateZoom = (input: 'increase' | 'decrease' | 'reset') => {\n    let { zoom } = zoomState;\n\n    switch (input) {\n      case 'increase':\n        zoom += 10;\n        break;\n      case 'decrease':\n        zoom -= 10;\n        break;\n      default:\n        zoom = 100;\n        break;\n    }\n\n    setZoomState('zoom', Math.min(Math.max(zoom, 40), 200));\n  };\n\n  return { zoomState, updateZoom, setZoomState };\n};\n"
  },
  {
    "path": "packages/solid-repl/src/index.ts",
    "content": "import type { Tab } from 'solid-repl';\n\nconst indexTSX = `import { render } from \"solid-js/web\";\nimport { createSignal } from \"solid-js\";\n\nfunction Counter() {\n  const [count, setCount] = createSignal(1);\n  const increment = () => setCount(count => count + 1);\n\n  return (\n    <button type=\"button\" onClick={increment}>\n      {count()}\n    </button>\n  );\n}\n\nrender(() => <Counter />, document.getElementById(\"app\")!);\n`;\n\nexport const defaultTabs: Tab[] = [\n  {\n    name: 'main.tsx',\n    source: indexTSX,\n  },\n];\n"
  },
  {
    "path": "packages/solid-repl/src/repl.tsx",
    "content": "import { Repl } from './components/repl';\nexport default Repl;\n"
  },
  {
    "path": "packages/solid-repl/src/types.d.ts",
    "content": "declare module 'solid-repl' {\n  export interface Tab {\n    name: string;\n    source: string;\n  }\n\n  export const defaultTabs: Tab[];\n}\n\ndeclare module 'solid-repl/dist/repl' {\n  export type Repl = import('solid-js').Component<{\n    compiler: Worker;\n    formatter: Worker;\n    linter: Worker;\n    isHorizontal: boolean;\n    dark: boolean;\n    tabs: Tab[];\n    id: string;\n    hideDevtools?: boolean;\n    setTabs: (tab: Tab[]) => void;\n    reset: () => void;\n    current: string | undefined;\n    setCurrent: (tabId: string) => void;\n    onUserEdit?: () => void;\n    onEditorReady?: (\n      editor: import('monaco-editor').editor.IStandaloneCodeEditor,\n      monaco: {\n        Uri: typeof import('monaco-editor').Uri;\n        editor: typeof import('monaco-editor').editor;\n      },\n    ) => void;\n  }>;\n  const Repl: Repl;\n  export default Repl;\n}\n\ninterface Window {\n  MonacoEnvironment: {\n    getWorker: (_moduleId: unknown, label: string) => Worker;\n  };\n}\n"
  },
  {
    "path": "packages/solid-repl/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"noEmit\": false\n  },\n  \"include\": [\"./src/**/*\"],\n  \"exclude\": [\"node_modules/\"]\n}\n"
  },
  {
    "path": "packages/solid-repl/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"include\": [\"./src/**/*\", \"repl/**/*\"],\n  \"exclude\": [\"node_modules/\"]\n}\n"
  },
  {
    "path": "packages/solid-repl/unocss.config.ts",
    "content": "import { theme } from '@unocss/preset-wind3';\nimport { defineConfig } from 'unocss';\nimport sharedConfig from '../../uno.config.ts';\n\nexport default defineConfig({\n  ...(sharedConfig as any),\n  theme: {\n    ...(sharedConfig as any).theme,\n    fontFamily: {\n      sans: 'Gordita, ' + theme.fontFamily!.sans,\n    },\n  },\n});\n"
  },
  {
    "path": "patches/monaco-editor.patch",
    "content": "diff --git a/esm/vs/editor/editor.main.js b/esm/vs/editor/editor.main.js\nindex 626b49d8529e1784a898a72811e199a385ba4440..55d691dc1659f0dde07c208259c5e6301d980701 100644\n--- a/esm/vs/editor/editor.main.js\n+++ b/esm/vs/editor/editor.main.js\n@@ -2,87 +2,10 @@ import * as monaco_contribution from '../language/css/monaco.contribution.js';\n import * as monaco_contribution$1 from '../language/html/monaco.contribution.js';\n import * as monaco_contribution$2 from '../language/json/monaco.contribution.js';\n import * as monaco_contribution$3 from '../language/typescript/monaco.contribution.js';\n-import '../basic-languages/abap/abap.contribution.js';\n-import '../basic-languages/apex/apex.contribution.js';\n-import '../basic-languages/azcli/azcli.contribution.js';\n-import '../basic-languages/bat/bat.contribution.js';\n-import '../basic-languages/bicep/bicep.contribution.js';\n-import '../basic-languages/cameligo/cameligo.contribution.js';\n-import '../basic-languages/clojure/clojure.contribution.js';\n-import '../basic-languages/coffee/coffee.contribution.js';\n-import '../basic-languages/cpp/cpp.contribution.js';\n-import '../basic-languages/csharp/csharp.contribution.js';\n-import '../basic-languages/csp/csp.contribution.js';\n import '../basic-languages/css/css.contribution.js';\n-import '../basic-languages/cypher/cypher.contribution.js';\n-import '../basic-languages/dart/dart.contribution.js';\n-import '../basic-languages/dockerfile/dockerfile.contribution.js';\n-import '../basic-languages/ecl/ecl.contribution.js';\n-import '../basic-languages/elixir/elixir.contribution.js';\n-import '../basic-languages/flow9/flow9.contribution.js';\n-import '../basic-languages/fsharp/fsharp.contribution.js';\n-import '../basic-languages/freemarker2/freemarker2.contribution.js';\n-import '../basic-languages/go/go.contribution.js';\n-import '../basic-languages/graphql/graphql.contribution.js';\n-import '../basic-languages/handlebars/handlebars.contribution.js';\n-import '../basic-languages/hcl/hcl.contribution.js';\n import '../basic-languages/html/html.contribution.js';\n-import '../basic-languages/ini/ini.contribution.js';\n-import '../basic-languages/java/java.contribution.js';\n import '../basic-languages/javascript/javascript.contribution.js';\n-import '../basic-languages/julia/julia.contribution.js';\n-import '../basic-languages/kotlin/kotlin.contribution.js';\n-import '../basic-languages/less/less.contribution.js';\n-import '../basic-languages/lexon/lexon.contribution.js';\n-import '../basic-languages/lua/lua.contribution.js';\n-import '../basic-languages/liquid/liquid.contribution.js';\n-import '../basic-languages/m3/m3.contribution.js';\n-import '../basic-languages/markdown/markdown.contribution.js';\n-import '../basic-languages/mdx/mdx.contribution.js';\n-import '../basic-languages/mips/mips.contribution.js';\n-import '../basic-languages/msdax/msdax.contribution.js';\n-import '../basic-languages/mysql/mysql.contribution.js';\n-import '../basic-languages/objective-c/objective-c.contribution.js';\n-import '../basic-languages/pascal/pascal.contribution.js';\n-import '../basic-languages/pascaligo/pascaligo.contribution.js';\n-import '../basic-languages/perl/perl.contribution.js';\n-import '../basic-languages/pgsql/pgsql.contribution.js';\n-import '../basic-languages/php/php.contribution.js';\n-import '../basic-languages/pla/pla.contribution.js';\n-import '../basic-languages/postiats/postiats.contribution.js';\n-import '../basic-languages/powerquery/powerquery.contribution.js';\n-import '../basic-languages/powershell/powershell.contribution.js';\n-import '../basic-languages/protobuf/protobuf.contribution.js';\n-import '../basic-languages/pug/pug.contribution.js';\n-import '../basic-languages/python/python.contribution.js';\n-import '../basic-languages/qsharp/qsharp.contribution.js';\n-import '../basic-languages/r/r.contribution.js';\n-import '../basic-languages/razor/razor.contribution.js';\n-import '../basic-languages/redis/redis.contribution.js';\n-import '../basic-languages/redshift/redshift.contribution.js';\n-import '../basic-languages/restructuredtext/restructuredtext.contribution.js';\n-import '../basic-languages/ruby/ruby.contribution.js';\n-import '../basic-languages/rust/rust.contribution.js';\n-import '../basic-languages/sb/sb.contribution.js';\n-import '../basic-languages/scala/scala.contribution.js';\n-import '../basic-languages/scheme/scheme.contribution.js';\n-import '../basic-languages/scss/scss.contribution.js';\n-import '../basic-languages/shell/shell.contribution.js';\n-import '../basic-languages/solidity/solidity.contribution.js';\n-import '../basic-languages/sophia/sophia.contribution.js';\n-import '../basic-languages/sparql/sparql.contribution.js';\n-import '../basic-languages/sql/sql.contribution.js';\n-import '../basic-languages/st/st.contribution.js';\n-import '../basic-languages/swift/swift.contribution.js';\n-import '../basic-languages/systemverilog/systemverilog.contribution.js';\n-import '../basic-languages/tcl/tcl.contribution.js';\n-import '../basic-languages/twig/twig.contribution.js';\n import '../basic-languages/typescript/typescript.contribution.js';\n-import '../basic-languages/typespec/typespec.contribution.js';\n-import '../basic-languages/vb/vb.contribution.js';\n-import '../basic-languages/wgsl/wgsl.contribution.js';\n-import '../basic-languages/xml/xml.contribution.js';\n-import '../basic-languages/yaml/yaml.contribution.js';\n import * as index from '../../external/monaco-lsp-client/out/index.js';\n export { index as lsp };\n import './browser/coreCommands.js';\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - packages/*\n"
  },
  {
    "path": "scripts/oxlint-plugin-unocss.ts",
    "content": "import { createSyncFn } from 'synckit';\nimport { definePlugin, defineRule, type Ranged } from '@oxlint/plugins';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst validateClass = createSyncFn(resolve(__dirname, './unocss-worker.ts'));\n\nconst metadataCache = new Map<string, any>();\n\nfunction getMetadata(cls: string) {\n  if (metadataCache.has(cls)) return metadataCache.get(cls);\n  const meta = validateClass(cls);\n  metadataCache.set(cls, meta);\n  return meta;\n}\n\nfunction getClassesFromAttribute(node: any): { cls: string; node: Ranged; definitelyActive: boolean }[] {\n  const classes: { cls: string; node: Ranged; definitelyActive: boolean }[] = [];\n  if (node.name.name === 'class' || node.name.name === 'classList') {\n    if (node.value && node.value.type === 'Literal' && typeof node.value.value === 'string') {\n      node.value.value\n        .split(/\\s+/)\n        .filter(Boolean)\n        .forEach((cls: string) => {\n          classes.push({ cls, node: node.value, definitelyActive: true });\n        });\n    } else if (node.value && node.value.type === 'JSXExpressionContainer') {\n      const expression = node.value.expression;\n      if (expression && expression.type === 'ObjectExpression') {\n        const props = expression.properties || [];\n        for (const prop of props) {\n          if (prop.type === 'Property') {\n            const isDefinitelyActive = prop.value.type === 'Literal' && prop.value.value === true;\n            const key = prop.key;\n            if (key.type === 'Literal' && typeof key.value === 'string') {\n              key.value\n                .split(/\\s+/)\n                .filter(Boolean)\n                .forEach((cls: string) => {\n                  classes.push({ cls, node: key, definitelyActive: isDefinitelyActive });\n                });\n            } else if (key.type === 'Identifier') {\n              classes.push({ cls: key.name, node: key, definitelyActive: isDefinitelyActive });\n            }\n          }\n        }\n      }\n    }\n  }\n  return classes;\n}\n\nconst validClassRule = defineRule({\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'Ensure UnoCSS classes are valid',\n      category: 'Possible Errors',\n    },\n    messages: {\n      invalidClass: 'Invalid UnoCSS class: {{cls}}',\n    },\n  },\n  create(context) {\n    return {\n      JSXAttribute(node) {\n        const classes = getClassesFromAttribute(node);\n        for (const { cls, node: targetNode } of classes) {\n          if (cls.startsWith('{') || cls.includes('$') || cls.includes('(') || cls === 'group') continue;\n          const meta = getMetadata(cls);\n          if (!meta.valid) {\n            context.report({\n              message: `Invalid UnoCSS class: ${cls}`,\n              node: targetNode,\n            });\n          }\n        }\n      },\n    };\n  },\n});\n\nconst conflictingClassesRule = defineRule({\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'Ensure UnoCSS classes do not conflict',\n      category: 'Possible Errors',\n    },\n    messages: {\n      conflictingClass: 'Conflicting UnoCSS class: {{cls}} and {{conflictingCls}} both set {{prop}}',\n    },\n  },\n  create(context) {\n    return {\n      JSXAttribute(node) {\n        const classes = getClassesFromAttribute(node);\n        // We only check for conflicts among classes that are definitely active together\n        const activeClasses = classes.filter((c) => c.definitelyActive);\n\n        // context -> property -> class\n        const propertyMap = new Map<string, Map<string, string>>();\n\n        for (const { cls } of activeClasses) {\n          if (cls.startsWith('{') || cls.includes('$') || cls.includes('(') || cls === 'group') continue;\n          const meta = getMetadata(cls);\n          if (!meta.valid || !meta.info) continue;\n\n          for (const info of meta.info) {\n            const clsContext = info.context || 'default';\n            if (!propertyMap.has(clsContext)) {\n              propertyMap.set(clsContext, new Map());\n            }\n\n            const propMap = propertyMap.get(clsContext)!;\n            for (const prop of info.properties) {\n              if (propMap.has(prop)) {\n                const conflictingCls = propMap.get(prop)!;\n                if (conflictingCls !== cls) {\n                  context.report({\n                    message: `Conflicting UnoCSS classes: \"${cls}\" and \"${conflictingCls}\" both set \"${prop}\" in context \"${clsContext}\"`,\n                    node: node.value || node,\n                  });\n                }\n              } else {\n                propMap.set(prop, cls);\n              }\n            }\n          }\n        }\n      },\n    };\n  },\n});\n\nconst plugin = definePlugin({\n  meta: { name: 'unocss' },\n  rules: {\n    'valid-class': validClassRule,\n    'no-conflicting-classes': conflictingClassesRule,\n  },\n});\n\nexport default plugin;\n"
  },
  {
    "path": "scripts/unocss-worker.ts",
    "content": "import { runAsWorker } from 'synckit';\nimport { createGenerator } from 'unocss';\nimport { createJiti } from 'jiti';\n\nconst jiti = createJiti(import.meta.url);\nconst unoConfig = await jiti.import('../uno.config.ts');\nconst uno = await createGenerator((unoConfig as any).default);\n\nrunAsWorker(async (cls: string) => {\n  const res = await uno.generate(cls, { preflights: false });\n  if (res.matched.size === 0) return { valid: false };\n\n  const info: { properties: string[]; context: string }[] = [];\n\n  // Basic CSS parser to extract properties and their context\n  // We expect simple CSS from uno.generate(single_class)\n  const css = res.css;\n  const stack: string[] = [];\n  let currentContext = '';\n\n  // Simple regex-based parsing\n  // This is not a full CSS parser but should be enough for UnoCSS output for a single class\n  const lines = css.split('\\n');\n  let currentProperties: string[] = [];\n  let insideRule = false;\n\n  for (let line of lines) {\n    line = line.trim();\n    if (!line || line.startsWith('/*')) continue;\n\n    if (line.startsWith('@')) {\n      const braceIndex = line.indexOf('{');\n      const atRule = braceIndex !== -1 ? line.slice(0, braceIndex).trim() : line;\n      stack.push(atRule);\n      if (line.includes('}')) {\n        // Single line at-rule? (rare in UnoCSS but possible)\n        stack.pop();\n      }\n      continue;\n    }\n\n    if (line.includes('{')) {\n      const selector = line.split('{')[0].trim();\n      const escapedClsForRegex = cls.replace(/[:[\\]()%/$,!]/g, '\\\\\\\\$&');\n      const contextSelector = selector.replace(new RegExp(`\\\\.${escapedClsForRegex}`, 'g'), '').trim();\n\n      currentContext = [...stack, contextSelector].filter(Boolean).join(' ');\n      currentProperties = [];\n      insideRule = true;\n\n      if (line.includes('}')) {\n        const content = line.slice(line.indexOf('{') + 1, line.lastIndexOf('}'));\n        extractProperties(content, currentProperties);\n        info.push({ properties: currentProperties, context: currentContext });\n        currentProperties = [];\n        insideRule = false;\n      }\n      continue;\n    }\n\n    if (line.includes('}')) {\n      if (insideRule) {\n        if (currentProperties.length > 0) {\n          info.push({ properties: currentProperties, context: currentContext });\n          currentProperties = [];\n        }\n        insideRule = false;\n      } else {\n        stack.pop();\n      }\n      continue;\n    }\n\n    if (insideRule && line.includes(':')) {\n      extractProperties(line, currentProperties);\n    }\n  }\n\n  return { valid: true, info };\n});\n\nfunction extractProperties(content: string, target: string[]) {\n  const decls = content.split(';');\n  for (const decl of decls) {\n    const colonIndex = decl.indexOf(':');\n    if (colonIndex !== -1) {\n      const prop = decl.slice(0, colonIndex).trim();\n      if (prop && !prop.startsWith('--un-')) {\n        target.push(prop);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n    \"allowSyntheticDefaultImports\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"preserve\",\n    \"resolveJsonModule\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"skipLibCheck\": true,\n    \"jsxImportSource\": \"solid-js\",\n    \"allowImportingTsExtensions\": true,\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "uno.config.ts",
    "content": "import { presetWind } from '@unocss/preset-wind3';\nimport { transformerDirectives, defineConfig } from 'unocss';\n\nexport default defineConfig({\n  theme: {\n    colors: {\n      dark: '#07254A',\n      medium: '#446b9e',\n      solidc: '#2c4f7c',\n    },\n  },\n  presets: [presetWind()],\n  transformers: [transformerDirectives()],\n});\n"
  }
]