@epic-web/workshop-app 6.88.1 → 6.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/build/client/assets/{_exerciseNumber-BkoIHU4s.js → _exerciseNumber-BZvzAp9f.js} +2 -2
  2. package/build/client/assets/{_exerciseNumber-BkoIHU4s.js.map → _exerciseNumber-BZvzAp9f.js.map} +1 -1
  3. package/build/client/assets/{_exerciseNumber_.finished-BRspQYAo.js → _exerciseNumber_.finished-D5eMZ_ok.js} +2 -2
  4. package/build/client/assets/{_exerciseNumber_.finished-BRspQYAo.js.map → _exerciseNumber_.finished-D5eMZ_ok.js.map} +1 -1
  5. package/build/client/assets/{_extra-qH2jDBa4.js → _extra-Bd0-gThk.js} +2 -2
  6. package/build/client/assets/{_extra-qH2jDBa4.js.map → _extra-Bd0-gThk.js.map} +1 -1
  7. package/build/client/assets/{_layout-Bg1RFS9D.js → _layout-CbP-ZGKA.js} +2 -2
  8. package/build/client/assets/{_layout-Bg1RFS9D.js.map → _layout-CbP-ZGKA.js.map} +1 -1
  9. package/build/client/assets/{app-BH96aYJx.js → app-BTlBQWy_.js} +2 -2
  10. package/build/client/assets/{app-BH96aYJx.js.map → app-BTlBQWy_.js.map} +1 -1
  11. package/build/client/assets/{cache-5tXauWqp.js → cache-Cjjb4Th1.js} +2 -2
  12. package/build/client/assets/{cache-5tXauWqp.js.map → cache-Cjjb4Th1.js.map} +1 -1
  13. package/build/client/assets/{db-CPB2ZdAy.js → db-n83JPsm9.js} +2 -2
  14. package/build/client/assets/{db-CPB2ZdAy.js.map → db-n83JPsm9.js.map} +1 -1
  15. package/build/client/assets/{diff-BncP0Faq.js → diff-C4LoXlDb.js} +3 -3
  16. package/build/client/assets/diff-C4LoXlDb.js.map +1 -0
  17. package/build/client/assets/{diff-IVQnB3Pk.js → diff-Fut2J8f5.js} +2 -2
  18. package/build/client/assets/{diff-IVQnB3Pk.js.map → diff-Fut2J8f5.js.map} +1 -1
  19. package/build/client/assets/{finished-CYdLPdvJ.js → finished-CsRM8483.js} +2 -2
  20. package/build/client/assets/{finished-CYdLPdvJ.js.map → finished-CsRM8483.js.map} +1 -1
  21. package/build/client/assets/{index-n4CWCwSA.js → index-BnHkUMN_.js} +2 -2
  22. package/build/client/assets/{index-n4CWCwSA.js.map → index-BnHkUMN_.js.map} +1 -1
  23. package/build/client/assets/{index-CKhNevM7.js → index-Bo0DKzj5.js} +2 -2
  24. package/build/client/assets/{index-CKhNevM7.js.map → index-Bo0DKzj5.js.map} +1 -1
  25. package/build/client/assets/{index-Cb8U85TK.js → index-m9K7ACZe.js} +2 -2
  26. package/build/client/assets/{index-Cb8U85TK.js.map → index-m9K7ACZe.js.map} +1 -1
  27. package/build/client/assets/launch-editor-C-8ah8qM.js +2 -0
  28. package/build/client/assets/launch-editor-C-8ah8qM.js.map +1 -0
  29. package/build/client/assets/{manifest-d6d8ad00.js → manifest-72875289.js} +1 -1
  30. package/build/client/assets/{mdx-Bvb9XNyL.js → mdx-C3Jl4O5D.js} +2 -2
  31. package/build/client/assets/{mdx-Bvb9XNyL.js.map → mdx-C3Jl4O5D.js.map} +1 -1
  32. package/build/client/assets/{playground-CEUo0DCy.js → playground-BV2y1ifv.js} +2 -2
  33. package/build/client/assets/{playground-CEUo0DCy.js.map → playground-BV2y1ifv.js.map} +1 -1
  34. package/build/client/assets/{preview-BS9wGiJ3.js → preview-Dav9KrHN.js} +2 -2
  35. package/build/client/assets/{preview-BS9wGiJ3.js.map → preview-Dav9KrHN.js.map} +1 -1
  36. package/build/server/index.js +36 -4
  37. package/build/server/index.js.map +1 -1
  38. package/package.json +3 -3
  39. package/build/client/assets/diff-BncP0Faq.js.map +0 -1
  40. package/build/client/assets/launch-editor-C8OwTN5L.js +0 -2
  41. package/build/client/assets/launch-editor-C8OwTN5L.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cache-5tXauWqp.js","sources":["../../../app/routes/admin+/cache.tsx"],"sourcesContent":["import {\n\tdeleteCacheEntry,\n\tdeleteWorkshopCache,\n\tgetAllWorkshopCaches,\n\tgetGlobalCaches,\n\tglobalCacheDirectoryExists,\n\treadWorkshopCacheMetadata,\n\tupdateCacheEntry,\n} from '@epic-web/workshop-utils/cache.server'\nimport { getEnv } from '@epic-web/workshop-utils/env.server'\nimport { getErrorMessage } from '@epic-web/workshop-utils/utils'\nimport { useEffect, useRef, useState } from 'react'\nimport { href, useFetcher, useSearchParams } from 'react-router'\nimport { ClientOnly } from 'remix-utils/client-only'\nimport { z } from 'zod'\nimport {\n\tButton,\n\tIconButton,\n\ticonButtonClassName,\n} from '#app/components/button.tsx'\nimport { Icon } from '#app/components/icons.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport {\n\tcalculateExpirationTime,\n\tcn,\n\tensureUndeployed,\n\tformatDuration,\n\tformatFileSize,\n\tformatTimeRemaining,\n\tuseDayjs,\n\tuseDoubleCheck,\n\tuseInterval,\n} from '#app/utils/misc.tsx'\nimport { type Route } from './+types/cache.ts'\n\nfunction normalizeSearchQuery(query: string) {\n\treturn query.trim().toLowerCase()\n}\n\nfunction entryValueMatches(value: unknown, query: string) {\n\tif (!query) return true\n\tif (value === null || value === undefined) return false\n\tif (typeof value === 'string') {\n\t\treturn value.toLowerCase().includes(query)\n\t}\n\ttry {\n\t\tconst serialized = JSON.stringify(value)\n\t\tif (!serialized) return false\n\t\treturn serialized.toLowerCase().includes(query)\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction isLikelyMd5Hash(value: string) {\n\treturn /^[a-f0-9]{32}$/i.test(value)\n}\n\nfunction getShortHash(value: string) {\n\tif (!isLikelyMd5Hash(value)) return value\n\treturn `${value.slice(0, 7)}...${value.slice(-6)}`\n}\n\ntype WorkshopMeta = {\n\tid: string\n\tdisplayName: string\n\tshortId: string\n\trepoName?: string\n\tsubtitle?: string\n\thasStoredDisplayName: boolean\n}\n\nexport async function loader({ request }: Route.LoaderArgs) {\n\tensureUndeployed()\n\tconst currentWorkshopId = getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID\n\tconst allWorkshopCaches = await getAllWorkshopCaches()\n\tconst globalCaches = await getGlobalCaches()\n\tconst allCaches = [...allWorkshopCaches, ...globalCaches]\n\n\tconst url = new URL(request.url)\n\tconst filterQuery = url.searchParams.get('q') || ''\n\n\t// Ensure 'global' is always in available workshops if global cache directory exists\n\tconst availableWorkshopIds = new Set(allCaches.map((w) => w.workshopId))\n\tconst globalDirExists = await globalCacheDirectoryExists()\n\tif (globalDirExists) {\n\t\tavailableWorkshopIds.add('global')\n\t}\n\t// Always include the current workshop in the UI, even if it has no caches yet.\n\tif (currentWorkshopId) {\n\t\tavailableWorkshopIds.add(currentWorkshopId)\n\t}\n\n\tconst selectedWorkshops = url.searchParams\n\t\t.get('workshops')\n\t\t?.split(',')\n\t\t.filter(Boolean) || [\n\t\tcurrentWorkshopId,\n\t\t...(globalDirExists ? ['global'] : []),\n\t]\n\tconst normalizedQuery = normalizeSearchQuery(filterQuery)\n\n\tconst workshopMetaById: Record<string, WorkshopMeta> = {}\n\n\tif (availableWorkshopIds.has('global')) {\n\t\tworkshopMetaById.global = {\n\t\t\tid: 'global',\n\t\t\tdisplayName: 'Global caches',\n\t\t\tshortId: 'global',\n\t\t\thasStoredDisplayName: true,\n\t\t}\n\t}\n\n\tconst workshopIds = Array.from(availableWorkshopIds).filter(\n\t\t(workshopId) => workshopId !== 'global',\n\t)\n\tconst workshopIdMetas = await Promise.all(\n\t\tworkshopIds.map(async (workshopId) => {\n\t\t\tconst meta = await readWorkshopCacheMetadata(workshopId)\n\t\t\treturn [workshopId, meta] as const\n\t\t}),\n\t)\n\tfor (const [workshopId, meta] of workshopIdMetas) {\n\t\tworkshopMetaById[workshopId] = {\n\t\t\tid: workshopId,\n\t\t\tdisplayName: meta?.displayName ?? workshopId,\n\t\t\tshortId: getShortHash(workshopId),\n\t\t\trepoName: meta?.repoName,\n\t\t\tsubtitle: meta?.subtitle,\n\t\t\thasStoredDisplayName: Boolean(meta?.displayName),\n\t\t}\n\t}\n\n\tconst compareWorkshopIds = (a: string, b: string) => {\n\t\tif (a === currentWorkshopId) return -1\n\t\tif (b === currentWorkshopId) return 1\n\t\tif (a === 'global') return -1\n\t\tif (b === 'global') return 1\n\t\tconst aLabel = workshopMetaById[a]?.displayName ?? a\n\t\tconst bLabel = workshopMetaById[b]?.displayName ?? b\n\t\treturn aLabel.localeCompare(bLabel)\n\t}\n\n\t// Filter caches based on search query and selected workshops\n\tconst filteredCaches = allCaches\n\t\t.filter(\n\t\t\t(workshopCache) =>\n\t\t\t\tselectedWorkshops.includes(workshopCache.workshopId) ||\n\t\t\t\tselectedWorkshops.length === 0,\n\t\t)\n\t\t.map((workshopCache) => {\n\t\t\tconst workshop =\n\t\t\t\tworkshopMetaById[workshopCache.workshopId] ??\n\t\t\t\t({\n\t\t\t\t\tid: workshopCache.workshopId,\n\t\t\t\t\tdisplayName: workshopCache.workshopId,\n\t\t\t\t\tshortId: getShortHash(workshopCache.workshopId),\n\t\t\t\t\thasStoredDisplayName: false,\n\t\t\t\t} satisfies WorkshopMeta)\n\t\t\tconst workshopLabel = workshop.displayName\n\t\t\tconst workshopMatchesQuery =\n\t\t\t\tnormalizedQuery === ''\n\t\t\t\t\t? true\n\t\t\t\t\t: workshopLabel.toLowerCase().includes(normalizedQuery) ||\n\t\t\t\t\t\tworkshopCache.workshopId.toLowerCase().includes(normalizedQuery)\n\t\t\treturn {\n\t\t\t\t...workshopCache,\n\t\t\t\tworkshop,\n\t\t\t\tcaches: workshopCache.caches\n\t\t\t\t\t.map((cache) => {\n\t\t\t\t\t\tconst cacheNameMatches =\n\t\t\t\t\t\t\tnormalizedQuery === ''\n\t\t\t\t\t\t\t\t? true\n\t\t\t\t\t\t\t\t: workshopMatchesQuery ||\n\t\t\t\t\t\t\t\t\tcache.name.toLowerCase().includes(normalizedQuery)\n\t\t\t\t\t\tconst entries = cache.entries\n\t\t\t\t\t\t\t.filter((entry) => {\n\t\t\t\t\t\t\t\tif (normalizedQuery === '') return true\n\t\t\t\t\t\t\t\tif (cacheNameMatches) return true\n\t\t\t\t\t\t\t\tif (entry.key.toLowerCase().includes(normalizedQuery))\n\t\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t\treturn entryValueMatches(entry.entry.value, normalizedQuery)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.map((entry) => ({\n\t\t\t\t\t\t\t\tkey: entry.key,\n\t\t\t\t\t\t\t\tfilename: entry.filename,\n\t\t\t\t\t\t\t\tsize: entry.size,\n\t\t\t\t\t\t\t\tfilepath: entry.filepath,\n\t\t\t\t\t\t\t\tmetadata: entry.entry.metadata,\n\t\t\t\t\t\t\t}))\n\t\t\t\t\t\treturn { ...cache, entries }\n\t\t\t\t\t})\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(cache) => cache.entries.length > 0 || normalizedQuery === '',\n\t\t\t\t\t),\n\t\t\t}\n\t\t})\n\t\t.filter(\n\t\t\t(workshopCache) =>\n\t\t\t\tworkshopCache.caches.length > 0 || normalizedQuery === '',\n\t\t)\n\n\treturn {\n\t\tcurrentWorkshopId,\n\t\tcurrentWorkshop:\n\t\t\tworkshopMetaById[currentWorkshopId] ??\n\t\t\t({\n\t\t\t\tid: currentWorkshopId,\n\t\t\t\tdisplayName: currentWorkshopId,\n\t\t\t\tshortId: getShortHash(currentWorkshopId),\n\t\t\t\thasStoredDisplayName: false,\n\t\t\t} satisfies WorkshopMeta),\n\t\tfilteredCaches: [...filteredCaches].sort((a, b) =>\n\t\t\tcompareWorkshopIds(a.workshopId, b.workshopId),\n\t\t),\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops: Array.from(availableWorkshopIds)\n\t\t\t.map((workshopId) => {\n\t\t\t\treturn (\n\t\t\t\t\tworkshopMetaById[workshopId] ??\n\t\t\t\t\t({\n\t\t\t\t\t\tid: workshopId,\n\t\t\t\t\t\tdisplayName: workshopId,\n\t\t\t\t\t\tshortId: getShortHash(workshopId),\n\t\t\t\t\t\thasStoredDisplayName: false,\n\t\t\t\t\t} satisfies WorkshopMeta)\n\t\t\t\t)\n\t\t\t})\n\t\t\t.sort((a, b) => compareWorkshopIds(a.id, b.id)),\n\t}\n}\n\nconst ActionSchema = z.discriminatedUnion('intent', [\n\tz.object({\n\t\tintent: z.literal('delete-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-cache'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-workshop-cache'),\n\t\tworkshopId: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('update-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t\tnewValue: z.string(),\n\t}),\n])\n\nexport async function action({ request }: Route.ActionArgs) {\n\tensureUndeployed()\n\n\tconst formData = await request.formData()\n\tconst rawData = Object.fromEntries(formData.entries())\n\tconst result = ActionSchema.safeParse(rawData)\n\n\tif (!result.success) {\n\t\treturn { status: 'error', error: 'Invalid request' } as const\n\t}\n\n\tconst data = result.data\n\n\ttry {\n\t\tswitch (data.intent) {\n\t\t\tcase 'delete-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\tawait deleteCacheEntry(path)\n\t\t\t\treturn { status: 'success', message: 'Cache entry deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId, data.cacheName)\n\t\t\t\treturn { status: 'success', message: 'Cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-workshop-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId)\n\t\t\t\treturn { status: 'success', message: 'Workshop cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'update-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\ttry {\n\t\t\t\t\tconst parsedValue = JSON.parse(data.newValue)\n\t\t\t\t\tawait updateCacheEntry(path, parsedValue)\n\t\t\t\t\treturn { status: 'success', message: 'Cache entry updated' } as const\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\terror: getErrorMessage(error, 'Invalid JSON value'),\n\t\t\t\t\t} as const\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error('Cache action error:', error)\n\t\treturn { status: 'error', error: 'Operation failed' } as const\n\t}\n}\n\nfunction WorkshopChooser({\n\tselectedWorkshops,\n\tavailableWorkshops,\n\tcurrentWorkshopId,\n}: {\n\tselectedWorkshops: string[]\n\tavailableWorkshops: Array<WorkshopMeta>\n\tcurrentWorkshopId: string\n}) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\n\tconst handleWorkshopChange = (workshop: string, checked: boolean) => {\n\t\tconst newSelected = checked\n\t\t\t? [...selectedWorkshops, workshop]\n\t\t\t: selectedWorkshops.filter((w) => w !== workshop)\n\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (newSelected.length > 0) {\n\t\t\tparams.set('workshops', newSelected.join(','))\n\t\t} else {\n\t\t\tparams.delete('workshops')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Workshop Filter</h3>\n\t\t\t<div className=\"flex flex-wrap gap-3\">\n\t\t\t\t{availableWorkshops.map((workshop) => {\n\t\t\t\t\tconst label = workshop.displayName\n\t\t\t\t\tconst shortId = workshop.shortId\n\t\t\t\t\tconst secondary = workshop.repoName ?? workshop.subtitle\n\t\t\t\t\tconst hasStoredDisplayName = workshop.hasStoredDisplayName\n\t\t\t\t\tconst showDetailsLine =\n\t\t\t\t\t\thasStoredDisplayName && workshop.id !== 'global'\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tkey={workshop.id}\n\t\t\t\t\t\t\tclassName=\"flex items-center gap-2\"\n\t\t\t\t\t\t\ttitle={secondary ? `${label} (${secondary})` : label}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tchecked={selectedWorkshops.includes(workshop.id)}\n\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\thandleWorkshopChange(workshop.id, e.target.checked)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tclassName=\"rounded\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t'text-sm',\n\t\t\t\t\t\t\t\t\tworkshop.id === currentWorkshopId\n\t\t\t\t\t\t\t\t\t\t? 'text-primary font-bold'\n\t\t\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span className=\"inline-flex max-w-[22rem] flex-col leading-tight\">\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\thasStoredDisplayName\n\t\t\t\t\t\t\t\t\t\t\t\t? 'truncate'\n\t\t\t\t\t\t\t\t\t\t\t\t: 'font-mono text-xs break-all',\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{showDetailsLine ? (\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t'text-muted-foreground font-mono text-xs font-normal',\n\t\t\t\t\t\t\t\t\t\t\t\tworkshop.id === currentWorkshopId\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'text-primary/80'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\ttitle={workshop.id}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{secondary ? `${secondary} • ` : null}\n\t\t\t\t\t\t\t\t\t\t\t{shortId}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t{workshop.id === currentWorkshopId ? '(current)' : null}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t)\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nfunction SearchFilter({ filterQuery }: { filterQuery: string }) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\tconst [inputValue, setInputValue] = useState(filterQuery)\n\tconst inputRef = useRef<HTMLInputElement>(null)\n\n\t// Update input value when filterQuery changes (e.g., from URL)\n\tuseEffect(() => {\n\t\tsetInputValue(filterQuery)\n\t}, [filterQuery])\n\n\tconst handleSearch = (query: string) => {\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (query) {\n\t\t\tparams.set('q', query)\n\t\t} else {\n\t\t\tparams.delete('q')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\tconst handleClear = () => {\n\t\tsetInputValue('')\n\t\thandleSearch('')\n\t\tinputRef.current?.focus()\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Search Cache Entries</h3>\n\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"Search by key, value, cache name, or workshop...\"\n\t\t\t\t\tvalue={inputValue}\n\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\tsetInputValue(e.target.value)\n\t\t\t\t\t\thandleSearch(e.target.value)\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"border-border bg-background text-foreground focus:ring-ring flex-1 rounded-md border px-3 py-2 focus:ring-2 focus:outline-none\"\n\t\t\t\t/>\n\t\t\t\t{inputValue ? (\n\t\t\t\t\t<IconButton onClick={handleClear} title=\"Clear search\">\n\t\t\t\t\t\t<Icon name=\"Close\" className=\"h-4 w-4\" />\n\t\t\t\t\t</IconButton>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Inline entry editor component\nfunction InlineEntryEditor({\n\tworkshopId,\n\tcacheName,\n\tfilename,\n\tentryKey,\n}: {\n\tworkshopId: string\n\tcacheName: string\n\tfilename: string\n\tentryKey: string\n}) {\n\tconst entryFetcher = useFetcher<{\n\t\tentry: {\n\t\t\tvalue: unknown\n\t\t\tmetadata: {\n\t\t\t\tcreatedTime: number\n\t\t\t\tttl?: number | null\n\t\t\t\tswr?: number\n\t\t\t}\n\t\t} | null\n\t}>()\n\tconst updateFetcher = useFetcher<typeof action>()\n\tconst [editValue, setEditValue] = useState<string | null>(null)\n\tconst [baselineValue, setBaselineValue] = useState<string | null>(null)\n\tconst [hasChanges, setHasChanges] = useState(false)\n\tconst [hasRequested, setHasRequested] = useState(false)\n\tconst [hasStartedLoading, setHasStartedLoading] = useState(false)\n\tconst submittedValueRef = useRef<string | null>(null)\n\tconst prevUpdateStateRef = useRef<string>(updateFetcher.state)\n\n\tconst entryPath = `${workshopId}/${cacheName}/${filename}`\n\tconst entryValue = entryFetcher.data?.entry?.value\n\tconst isEntryLoading = entryFetcher.state === 'loading'\n\tconst hasEntry = Boolean(entryFetcher.data?.entry)\n\tconst entryMissing = entryFetcher.data?.entry === null\n\tconst entryFetchFailed =\n\t\thasRequested &&\n\t\thasStartedLoading &&\n\t\t!isEntryLoading &&\n\t\tentryFetcher.data === undefined &&\n\t\tentryFetcher.state === 'idle'\n\n\tuseEffect(() => {\n\t\tif (entryFetcher.state !== 'idle') {\n\t\t\tsetHasStartedLoading(true)\n\t\t}\n\t}, [entryFetcher.state])\n\n\tuseEffect(() => {\n\t\tif (hasEntry) {\n\t\t\tconst formattedValue = JSON.stringify(entryValue, null, 2) ?? ''\n\t\t\tsetEditValue(formattedValue)\n\t\t\tsetBaselineValue(formattedValue)\n\t\t\tsetHasChanges(false)\n\t\t}\n\t}, [hasEntry, entryValue])\n\n\tuseEffect(() => {\n\t\tconst currentUpdateState = updateFetcher.state\n\t\tconst wasSubmitting = prevUpdateStateRef.current !== 'idle'\n\t\tconst nowIdle = currentUpdateState === 'idle'\n\n\t\tif (wasSubmitting && nowIdle && updateFetcher.data?.status === 'success') {\n\t\t\tsetBaselineValue(submittedValueRef.current ?? '')\n\t\t\tsetHasChanges(false)\n\t\t}\n\t\tprevUpdateStateRef.current = currentUpdateState\n\t}, [updateFetcher.state, updateFetcher.data?.status])\n\n\tconst handleSave = () => {\n\t\tif (editValue === null) return\n\t\tsubmittedValueRef.current = editValue\n\t\tvoid updateFetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'update-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t\tnewValue: editValue,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst handleChange = (value: string) => {\n\t\tsetEditValue(value)\n\t\tif (baselineValue === null) {\n\t\t\tsetHasChanges(false)\n\t\t\treturn\n\t\t}\n\t\tsetHasChanges(value !== baselineValue)\n\t}\n\n\tconst handleReset = () => {\n\t\tif (baselineValue === null) return\n\t\tsetEditValue(baselineValue)\n\t\tsetHasChanges(false)\n\t}\n\n\tconst handleToggle = (event: React.SyntheticEvent<HTMLDetailsElement>) => {\n\t\tif (event.currentTarget.open && !hasRequested) {\n\t\t\tvoid entryFetcher.load(\n\t\t\t\thref('/admin/cache/*', {\n\t\t\t\t\t'*': entryPath,\n\t\t\t\t}),\n\t\t\t)\n\t\t\tsetHasRequested(true)\n\t\t} else if (!event.currentTarget.open) {\n\t\t\t// Reset on close to allow retry\n\t\t\tsetHasRequested(false)\n\t\t\tsetHasStartedLoading(false)\n\t\t}\n\t}\n\n\tconst handleRetry = () => {\n\t\tvoid entryFetcher.load(\n\t\t\thref('/admin/cache/*', {\n\t\t\t\t'*': entryPath,\n\t\t\t}),\n\t\t)\n\t}\n\n\treturn (\n\t\t<details className=\"mt-2\" onToggle={handleToggle}>\n\t\t\t<summary className=\"text-muted-foreground hover:text-foreground cursor-pointer text-sm\">\n\t\t\t\tEdit entry details\n\t\t\t</summary>\n\t\t\t<div className=\"border-border bg-muted mt-2 space-y-3 rounded border p-3\">\n\t\t\t\t{isEntryLoading ? (\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">Loading entry...</p>\n\t\t\t\t) : entryFetchFailed ? (\n\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t<p className=\"text-destructive text-sm\">\n\t\t\t\t\t\t\tFailed to load entry details.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Button varient=\"mono\" onClick={handleRetry}>\n\t\t\t\t\t\t\tRetry\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t) : entryMissing ? (\n\t\t\t\t\t<p className=\"text-destructive text-sm\">\n\t\t\t\t\t\tEntry details were not found.\n\t\t\t\t\t</p>\n\t\t\t\t) : hasEntry ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Key:</label>\n\t\t\t\t\t\t\t<code className=\"bg-background rounded border px-2 py-1 text-sm\">\n\t\t\t\t\t\t\t\t{entryKey}\n\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Value:</label>\n\t\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\t\tvalue={editValue ?? ''}\n\t\t\t\t\t\t\t\tonChange={(e) => handleChange(e.target.value)}\n\t\t\t\t\t\t\t\tclassName=\"resize-vertical border-border bg-background text-foreground focus:ring-ring h-32 w-full rounded border p-2 font-mono text-sm focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\t\t\tplaceholder=\"Enter JSON value...\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvarient=\"primary\"\n\t\t\t\t\t\t\t\tonClick={handleSave}\n\t\t\t\t\t\t\t\tdisabled={!hasChanges || updateFetcher.state !== 'idle'}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{updateFetcher.state !== 'idle' ? 'Saving...' : 'Save'}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvarient=\"mono\"\n\t\t\t\t\t\t\t\tonClick={handleReset}\n\t\t\t\t\t\t\t\tdisabled={!hasChanges}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tReset\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\tOpen to load entry details.\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</details>\n\t)\n}\n\nfunction SkippedFilesSection({\n\tskippedFiles,\n\tworkshopId,\n\tcacheName,\n}: {\n\tskippedFiles: Array<{\n\t\tfilename: string\n\t\terror: string\n\t\tsize: number\n\t\tskipped: true\n\t}>\n\tworkshopId: string\n\tcacheName: string\n}) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tif (skippedFiles.length === 0) return null\n\n\treturn (\n\t\t<div className=\"border-warning bg-warning mt-4 rounded border p-3\">\n\t\t\t<div className=\"mb-2 flex items-center gap-2\">\n\t\t\t\t<Icon\n\t\t\t\t\tname=\"TriangleAlert\"\n\t\t\t\t\tclassName=\"text-warning-foreground h-4 w-4\"\n\t\t\t\t/>\n\t\t\t\t<h5 className=\"text-warning-foreground font-medium\">\n\t\t\t\t\tSkipped Files ({skippedFiles.length})\n\t\t\t\t</h5>\n\t\t\t</div>\n\t\t\t<p className=\"text-warning-foreground/80 mb-3 text-sm\">\n\t\t\t\tThese cache files were skipped because they exceed the 3MB size limit:\n\t\t\t</p>\n\t\t\t<div className=\"space-y-2\">\n\t\t\t\t{skippedFiles.map((skippedFile) => (\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={skippedFile.filename}\n\t\t\t\t\t\tclassName=\"border-warning/20 bg-warning/5 flex items-center justify-between rounded border p-2\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground truncate font-mono text-sm font-medium\">\n\t\t\t\t\t\t\t\t{skippedFile.filename}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground/70 text-xs\">\n\t\t\t\t\t\t\t\t{skippedFile.error} • Size:{' '}\n\t\t\t\t\t\t\t\t<span title={`${skippedFile.size} bytes`}>\n\t\t\t\t\t\t\t\t\t{formatFileSize(skippedFile.size)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"ml-2 flex shrink-0\">\n\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\tonConfirm={() => {\n\t\t\t\t\t\t\t\t\tvoid fetcher.submit(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tintent: 'delete-entry',\n\t\t\t\t\t\t\t\t\t\t\tworkshopId,\n\t\t\t\t\t\t\t\t\t\t\tcacheName,\n\t\t\t\t\t\t\t\t\t\t\tfilename: skippedFile.filename,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{ method: 'POST' },\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\ttitle=\"Delete large cache file\"\n\t\t\t\t\t\t\t\tclassName=\"text-destructive-foreground hover:bg-destructive/20 hover:text-destructive-foreground\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport default function CacheManagement({ loaderData }: Route.ComponentProps) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tconst deleteEntry = (\n\t\tworkshopId: string,\n\t\tcacheName: string,\n\t\tfilename: string,\n\t) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteCache = (workshopId: string, cacheName: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-cache',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteWorkshopCache = (workshopId: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-workshop-cache',\n\t\t\t\tworkshopId,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst {\n\t\tcurrentWorkshopId,\n\t\tcurrentWorkshop,\n\t\tfilteredCaches,\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops,\n\t} = loaderData\n\tconst currentWorkshopMeta = currentWorkshop\n\n\treturn (\n\t\t<div className=\"space-y-6\">\n\t\t\t<div>\n\t\t\t\t<h2 className=\"mb-2 text-2xl font-bold\">Cache Management</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">\n\t\t\t\t\tCurrent Workshop:{' '}\n\t\t\t\t\t<span className=\"text-foreground font-semibold\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\tcurrentWorkshopMeta?.hasStoredDisplayName\n\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t: 'font-mono text-xs',\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{currentWorkshopMeta.displayName ?? currentWorkshopId}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</p>\n\t\t\t\t{currentWorkshopMeta?.hasStoredDisplayName ? (\n\t\t\t\t\t<p className=\"text-muted-foreground mt-1 text-xs\">\n\t\t\t\t\t\tInstance ID:{' '}\n\t\t\t\t\t\t<span className=\"font-mono\" title={currentWorkshopId}>\n\t\t\t\t\t\t\t{currentWorkshopMeta.shortId}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</p>\n\t\t\t\t) : null}\n\t\t\t</div>\n\n\t\t\t<WorkshopChooser\n\t\t\t\tselectedWorkshops={selectedWorkshops}\n\t\t\t\tavailableWorkshops={availableWorkshops}\n\t\t\t\tcurrentWorkshopId={currentWorkshopId}\n\t\t\t/>\n\n\t\t\t<SearchFilter filterQuery={filterQuery} />\n\n\t\t\t{fetcher.data?.status === 'success' ? (\n\t\t\t\t<div className=\"border-border bg-success text-success-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.message}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{fetcher.data?.status === 'error' ? (\n\t\t\t\t<div className=\"border-border bg-destructive text-destructive-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.error}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{filteredCaches.length === 0 ? (\n\t\t\t\t<div className=\"text-muted-foreground py-8 text-center\">\n\t\t\t\t\tNo caches found matching your criteria.\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t<div className=\"space-y-6\">\n\t\t\t\t{filteredCaches.map((workshopCache) => (\n\t\t\t\t\t<details\n\t\t\t\t\t\tkey={workshopCache.workshopId}\n\t\t\t\t\t\topen={workshopCache.workshopId === currentWorkshopId}\n\t\t\t\t\t>\n\t\t\t\t\t\t<summary className=\"border-border bg-card hover:bg-accent cursor-pointer rounded-lg border p-4\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between gap-4\">\n\t\t\t\t\t\t\t\t<div className=\"min-w-0\">\n\t\t\t\t\t\t\t\t\t<h3 className=\"text-card-foreground flex min-w-0 items-center gap-2 text-lg font-semibold\">\n\t\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-5 w-5 shrink-0\" />\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t'min-w-0',\n\t\t\t\t\t\t\t\t\t\t\t\tworkshopCache.workshop.hasStoredDisplayName\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'truncate'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'font-mono text-xs break-all',\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\ttitle={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.displayName}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t{workshopCache.workshopId === currentWorkshopId ? (\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"bg-primary text-primary-foreground rounded px-2 py-1 text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t\tCurrent\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t{workshopCache.workshop.hasStoredDisplayName &&\n\t\t\t\t\t\t\t\t\tworkshopCache.workshopId !== 'global' ? (\n\t\t\t\t\t\t\t\t\t\t<div className=\"text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.repoName ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.repoName}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t) : workshopCache.workshop.subtitle ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.subtitle}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"font-mono\"\n\t\t\t\t\t\t\t\t\t\t\t\ttitle={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.shortId}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\tdeleteWorkshopCache(workshopCache.workshopId)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttitle=\"Delete all workshop caches\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t<div className=\"mt-4 space-y-4 pl-4\">\n\t\t\t\t\t\t\t{workshopCache.caches.map((cache) => {\n\t\t\t\t\t\t\t\tconst totalSize = cache.entries.reduce(\n\t\t\t\t\t\t\t\t\t(sum, entry) => sum + (entry.size || 0),\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst skippedSize = (cache.skippedFiles || []).reduce(\n\t\t\t\t\t\t\t\t\t(sum, file) => sum + file.size,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst grandTotal = totalSize + skippedSize\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<details key={cache.name} className=\"bg-muted rounded-md\">\n\t\t\t\t\t\t\t\t\t\t<summary className=\"hover:bg-accent cursor-pointer p-3\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<h4 className=\"text-muted-foreground flex items-center gap-2 font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t({cache.entries.length} entr\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 1 ? 'y' : 'ies'})\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{grandTotal > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t•{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${grandTotal} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(grandTotal)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttotal\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{skippedSize > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-warning\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${skippedSize} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(skippedSize)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tskipped)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteCache(workshopCache.workshopId, cache.name)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete cache\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"p-3 pt-0\">\n\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tNo entries match your search.\n\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t{cache.skippedFiles && cache.skippedFiles.length > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<SkippedFilesSection\n\t\t\t\t\t\t\t\t\t\t\t\t\tskippedFiles={cache.skippedFiles}\n\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t({ key, metadata, filename, size, filepath }) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background rounded border p-3\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mb-1 flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"truncate font-mono text-sm font-medium\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{size ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"bg-muted text-muted-foreground inline-flex items-center rounded px-1.5 py-0.5 text-xs whitespace-nowrap\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={`${size} bytes`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(size)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CacheMetadata metadata={metadata} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"ml-4 flex shrink-0 gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={href('/admin/cache/*', {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'*': `${workshopCache.workshopId}/${cache.name}/${filename}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background text-foreground hover:bg-muted focus:ring-ring inline-flex h-8 w-8 items-center justify-center rounded border focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"View JSON\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"ExternalLink\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{filepath ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<LaunchEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfile={filepath}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={iconButtonClassName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"Files\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Open in editor\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</LaunchEditor>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteEntry(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopCache.workshopId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcache.name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete entry\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<InlineEntryEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename={filename}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentryKey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</details>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Component for displaying cache metadata with live countdown\nfunction CacheMetadata({\n\tmetadata,\n}: {\n\tmetadata: {\n\t\tcreatedTime: number\n\t\tttl?: number | null\n\t\tswr?: number\n\t}\n}) {\n\tconst dayjs = useDayjs()\n\tconst [, setCurrentTime] = useState(Date.now())\n\tconst expirationTime = calculateExpirationTime(metadata)\n\n\t// Update time every second for live countdown\n\tuseInterval(() => {\n\t\tsetCurrentTime(Date.now())\n\t}, 1000)\n\n\tconst createdDate = dayjs(metadata.createdTime)\n\tconst timeRemaining = expirationTime\n\t\t? formatTimeRemaining(expirationTime)\n\t\t: { text: 'Never', isExpired: false, isExpiringSoon: false }\n\n\treturn (\n\t\t<div className=\"text-muted-foreground flex flex-col gap-1 text-xs\">\n\t\t\t<div>\n\t\t\t\tCreated: {createdDate.format('MMM D, YYYY HH:mm:ss')}{' '}\n\t\t\t\t<ClientOnly>{() => `(${createdDate.fromNow()})`}</ClientOnly>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-wrap items-center gap-3\">\n\t\t\t\t{metadata.ttl !== undefined && metadata.ttl !== null ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tTTL:{' '}\n\t\t\t\t\t\t{metadata.ttl === Infinity ? (\n\t\t\t\t\t\t\t'Forever'\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<span title={`${metadata.ttl}ms`}>\n\t\t\t\t\t\t\t\t{formatDuration(metadata.ttl)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t{metadata.swr !== undefined ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tSWR:{' '}\n\t\t\t\t\t\t<span title={`${metadata.swr}ms`}>\n\t\t\t\t\t\t\t{formatDuration(metadata.swr)}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<div\n\t\t\t\t\tclassName={`inline-flex w-auto rounded-full px-2 py-[2px] font-medium ${\n\t\t\t\t\t\ttimeRemaining.isExpired\n\t\t\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t\t\t: timeRemaining.isExpiringSoon\n\t\t\t\t\t\t\t\t? 'bg-warning text-warning-foreground'\n\t\t\t\t\t\t\t\t: 'text-foreground'\n\t\t\t\t\t}`}\n\t\t\t\t>\n\t\t\t\t\t{expirationTime ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tExpires: {dayjs(expirationTime).format('MMM D, YYYY HH:mm:ss')} (\n\t\t\t\t\t\t\t<span className=\"tabular-nums\">\n\t\t\t\t\t\t\t\t<ClientOnly>{() => timeRemaining.text}</ClientOnly>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t'Expires: Never'\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Double-check delete button\nfunction DoubleCheckButton({\n\tonConfirm,\n\tchildren,\n\tclassName,\n\t...props\n}: React.ComponentPropsWithoutRef<'button'> & {\n\tonConfirm: () => void\n}) {\n\tconst doubleCheck = useDoubleCheck()\n\n\treturn (\n\t\t<IconButton\n\t\t\t{...doubleCheck.getButtonProps({\n\t\t\t\tonClick: doubleCheck.doubleCheck ? onConfirm : undefined,\n\t\t\t\t...props,\n\t\t\t})}\n\t\t\tclassName={cn(\n\t\t\t\tdoubleCheck.doubleCheck\n\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t: null,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{doubleCheck.doubleCheck ? '✓' : children}\n\t\t</IconButton>\n\t)\n}\n"],"names":["WorkshopChooser","selectedWorkshops","availableWorkshops","currentWorkshopId","searchParams","setSearchParams","useSearchParams","handleWorkshopChange","workshop","checked","newSelected","filter","w","params","URLSearchParams","length","set","join","delete","jsxs","className","children","jsx","map","label","displayName","shortId","secondary","repoName","subtitle","hasStoredDisplayName","showDetailsLine","id","title","type","includes","onChange","e","target","cn","SearchFilter","filterQuery","inputValue","setInputValue","useState","inputRef","useRef","useEffect","handleSearch","query","handleClear","current","focus","ref","placeholder","value","IconButton","onClick","Icon","name","InlineEntryEditor","workshopId","cacheName","filename","entryKey","entryFetcher","useFetcher","updateFetcher","editValue","setEditValue","baselineValue","setBaselineValue","hasChanges","setHasChanges","hasRequested","setHasRequested","hasStartedLoading","setHasStartedLoading","submittedValueRef","prevUpdateStateRef","state","entryPath","entryValue","data","entry","isEntryLoading","hasEntry","Boolean","entryMissing","entryFetchFailed","formattedValue","JSON","stringify","currentUpdateState","status","handleSave","submit","intent","newValue","method","handleChange","handleReset","handleToggle","event","currentTarget","open","load","href","handleRetry","onToggle","Button","varient","Fragment","disabled","SkippedFilesSection","skippedFiles","fetcher","skippedFile","error","size","formatFileSize","DoubleCheckButton","onConfirm","cache","_UNSAFE_withComponentProps","loaderData","deleteEntry","deleteCache","deleteWorkshopCache","currentWorkshop","filteredCaches","currentWorkshopMeta","message","workshopCache","caches","totalSize","entries","reduce","sum","skippedSize","file","grandTotal","key","metadata","filepath","CacheMetadata","rel","LaunchEditor","iconButtonClassName","dayjs","useDayjs","setCurrentTime","Date","now","expirationTime","calculateExpirationTime","useInterval","createdDate","createdTime","timeRemaining","formatTimeRemaining","text","isExpired","isExpiringSoon","format","ClientOnly","fromNow","ttl","Infinity","formatDuration","swr","props","doubleCheck","useDoubleCheck","getButtonProps"],"mappings":"whBAkTA,SAASA,EAAgB,CACxBC,kBAAAA,EACAC,mBAAAA,EACAC,kBAAAA,CACD,EAIG,CACF,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAA,EAElCC,EAAuBA,CAACC,EAAkBC,IAAqB,CACpE,MAAMC,EAAcD,EACjB,CAAC,GAAGR,EAAmBO,CAAQ,EAC/BP,EAAkBU,OAAQC,GAAMA,IAAMJ,CAAQ,EAE3CK,EAAS,IAAIC,gBAAgBV,CAAY,EAC3CM,EAAYK,OAAS,EACxBF,EAAOG,IAAI,YAAaN,EAAYO,KAAK,GAAG,CAAC,EAE7CJ,EAAOK,OAAO,WAAW,EAE1Bb,EAAgBQ,CAAM,CACvB,EAEA,OACCM,EAAAA,KAAC,MAAA,CAAIC,UAAU,OACdC,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAGF,UAAU,6BAA6BC,SAAA,iBAAA,CAAe,QACzD,MAAA,CAAID,UAAU,uBACbC,SAAAnB,EAAmBqB,IAAKf,GAAa,CACrC,MAAMgB,EAAQhB,EAASiB,YACjBC,EAAUlB,EAASkB,QACnBC,EAAYnB,EAASoB,UAAYpB,EAASqB,SAC1CC,EAAuBtB,EAASsB,qBAChCC,EACLD,GAAwBtB,EAASwB,KAAO,SACzC,OACCb,EAAAA,KAAC,QAAA,CAEAC,UAAU,0BACVa,MAAON,EAAY,GAAGH,CAAK,KAAKG,CAAS,IAAMH,EAE/CH,SAAA,CAAAC,EAAAA,IAAC,QAAA,CACAY,KAAK,WACLzB,QAASR,EAAkBkC,SAAS3B,EAASwB,EAAE,EAC/CI,SAAWC,GACV9B,EAAqBC,EAASwB,GAAIK,EAAEC,OAAO7B,OAAO,EAEnDW,UAAU,SAAA,CACX,EACAD,EAAAA,KAAC,OAAA,CACAC,UAAWmB,EACV,UACA/B,EAASwB,KAAO7B,EACb,yBACA,IACJ,EAEAkB,SAAA,CAAAF,EAAAA,KAAC,OAAA,CAAKC,UAAU,mDACfC,SAAA,CAAAC,EAAAA,IAAC,OAAA,CACAF,UAAWmB,EACVT,EACG,WACA,6BACJ,EAECT,SAAAG,EACF,EACCO,EACAZ,EAAAA,KAAC,OAAA,CACAC,UAAWmB,EACV,sDACA/B,EAASwB,KAAO7B,EACb,kBACA,IACJ,EACA8B,MAAOzB,EAASwB,GAEfX,SAAA,CAAAM,EAAY,GAAGA,CAAS,MAAQ,KAChCD,CAAA,EACF,EACG,IAAA,CAAA,CACL,EAAQ,IACPlB,EAASwB,KAAO7B,EAAoB,YAAc,IAAA,CAAA,CACpD,CAAA,CAAA,EA9CKK,EAASwB,EA+Cf,CAEF,CAAC,CAAA,CACF,CAAA,CAAA,CACD,CAEF,CAEA,SAASQ,EAAa,CAAEC,YAAAA,CAAY,EAA4B,CAC/D,KAAM,CAACrC,EAAcC,CAAe,EAAIC,EAAA,EAClC,CAACoC,EAAYC,CAAa,EAAIC,EAAAA,SAASH,CAAW,EAClDI,EAAWC,EAAAA,OAAyB,IAAI,EAG9CC,EAAAA,UAAU,IAAM,CACfJ,EAAcF,CAAW,CAC1B,EAAG,CAACA,CAAW,CAAC,EAEhB,MAAMO,EAAgBC,GAAkB,CACvC,MAAMpC,EAAS,IAAIC,gBAAgBV,CAAY,EAC3C6C,EACHpC,EAAOG,IAAI,IAAKiC,CAAK,EAErBpC,EAAOK,OAAO,GAAG,EAElBb,EAAgBQ,CAAM,CACvB,EAEMqC,EAAcA,IAAM,CACzBP,EAAc,EAAE,EAChBK,EAAa,EAAE,EACfH,EAASM,SAASC,MAAA,CACnB,EAEA,OACCjC,EAAAA,KAAC,MAAA,CAAIC,UAAU,OACdC,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAGF,UAAU,6BAA6BC,SAAA,sBAAA,CAAoB,EAC/DF,EAAAA,KAAC,MAAA,CAAIC,UAAU,aACdC,SAAA,CAAAC,EAAAA,IAAC,QAAA,CACA+B,IAAKR,EACLX,KAAK,OACLoB,YAAY,mDACZC,MAAOb,EACPN,SAAWC,GAAM,CAChBM,EAAcN,EAAEC,OAAOiB,KAAK,EAC5BP,EAAaX,EAAEC,OAAOiB,KAAK,CAC5B,EACAnC,UAAU,iIACX,EACCsB,EACApB,EAAAA,IAACkC,EAAA,CAAWC,QAASP,EAAajB,MAAM,eACvCZ,SAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,QAAQvC,UAAU,UAAU,EACxC,EACG,IAAA,CAAA,CACL,CAAA,CAAA,CACD,CAEF,CAGA,SAASwC,EAAkB,CAC1BC,WAAAA,EACAC,UAAAA,EACAC,SAAAA,EACAC,SAAAA,CACD,EAKG,CACF,MAAMC,EAAeC,EAAA,EAUfC,EAAgBD,EAAA,EAChB,CAACE,EAAWC,CAAY,EAAIzB,EAAAA,SAAwB,IAAI,EACxD,CAAC0B,EAAeC,CAAgB,EAAI3B,EAAAA,SAAwB,IAAI,EAChE,CAAC4B,EAAYC,CAAa,EAAI7B,EAAAA,SAAS,EAAK,EAC5C,CAAC8B,EAAcC,CAAe,EAAI/B,EAAAA,SAAS,EAAK,EAChD,CAACgC,EAAmBC,CAAoB,EAAIjC,EAAAA,SAAS,EAAK,EAC1DkC,EAAoBhC,EAAAA,OAAsB,IAAI,EAC9CiC,EAAqBjC,EAAAA,OAAeqB,EAAca,KAAK,EAEvDC,EAAY,GAAGpB,CAAU,IAAIC,CAAS,IAAIC,CAAQ,GAClDmB,EAAajB,EAAakB,MAAMC,OAAO7B,MACvC8B,EAAiBpB,EAAae,QAAU,UACxCM,EAAWC,EAAQtB,EAAakB,MAAMC,MACtCI,EAAevB,EAAakB,MAAMC,QAAU,KAC5CK,EACLf,GACAE,GACA,CAACS,GACDpB,EAAakB,OAAS,QACtBlB,EAAae,QAAU,OAExBjC,EAAAA,UAAU,IAAM,CACXkB,EAAae,QAAU,QAC1BH,EAAqB,EAAI,CAE3B,EAAG,CAACZ,EAAae,KAAK,CAAC,EAEvBjC,EAAAA,UAAU,IAAM,CACf,GAAIuC,EAAU,CACb,MAAMI,EAAiBC,KAAKC,UAAUV,EAAY,KAAM,CAAC,GAAK,GAC9Db,EAAaqB,CAAc,EAC3BnB,EAAiBmB,CAAc,EAC/BjB,EAAc,EAAK,CACpB,CACD,EAAG,CAACa,EAAUJ,CAAU,CAAC,EAEzBnC,EAAAA,UAAU,IAAM,CACf,MAAM8C,EAAqB1B,EAAca,MACnBD,EAAmB5B,UAAY,QACrC0C,IAAuB,QAEP1B,EAAcgB,MAAMW,SAAW,YAC9DvB,EAAiBO,EAAkB3B,SAAW,EAAE,EAChDsB,EAAc,EAAK,GAEpBM,EAAmB5B,QAAU0C,CAC9B,EAAG,CAAC1B,EAAca,MAAOb,EAAcgB,MAAMW,MAAM,CAAC,EAEpD,MAAMC,EAAaA,IAAM,CACpB3B,IAAc,OAClBU,EAAkB3B,QAAUiB,EACvBD,EAAc6B,OAClB,CACCC,OAAQ,eACRpC,WAAAA,EACAC,UAAAA,EACAC,SAAAA,EACAmC,SAAU9B,CACX,EACA,CAAE+B,OAAQ,MAAO,CAClB,EACD,EAEMC,EAAgB7C,GAAkB,CAEvC,GADAc,EAAad,CAAK,EACde,IAAkB,KAAM,CAC3BG,EAAc,EAAK,EACnB,MACD,CACAA,EAAclB,IAAUe,CAAa,CACtC,EAEM+B,EAAcA,IAAM,CACrB/B,IAAkB,OACtBD,EAAaC,CAAa,EAC1BG,EAAc,EAAK,EACpB,EAEM6B,EAAgBC,GAAoD,CACrEA,EAAMC,cAAcC,MAAQ,CAAC/B,GAC3BT,EAAayC,KACjBC,EAAK,iBAAkB,CACtB,IAAK1B,CACN,CAAC,CACF,EACAN,EAAgB,EAAI,GACT4B,EAAMC,cAAcC,OAE/B9B,EAAgB,EAAK,EACrBE,EAAqB,EAAK,EAE5B,EAEM+B,EAAcA,IAAM,CACpB3C,EAAayC,KACjBC,EAAK,iBAAkB,CACtB,IAAK1B,CACN,CAAC,CACF,CACD,EAEA,OACC9D,EAAAA,KAAC,UAAA,CAAQC,UAAU,OAAOyF,SAAUP,EACnCjF,SAAA,CAAAC,EAAAA,IAAC,UAAA,CAAQF,UAAU,qEAAqEC,SAAA,oBAAA,CAExF,EACAC,EAAAA,IAAC,MAAA,CAAIF,UAAU,2DACbC,WACAC,EAAAA,IAAC,IAAA,CAAEF,UAAU,gCAAgCC,4BAAgB,EAC1DoE,EACHtE,EAAAA,KAAC,MAAA,CAAIC,UAAU,YACdC,SAAA,CAAAC,EAAAA,IAAC,IAAA,CAAEF,UAAU,2BAA2BC,SAAA,+BAAA,CAExC,QACCyF,EAAA,CAAOC,QAAQ,OAAOtD,QAASmD,EAAavF,SAAA,OAAA,CAE7C,CAAA,EACD,EACGmE,EACHlE,EAAAA,IAAC,IAAA,CAAEF,UAAU,2BAA2BC,SAAA,gCAExC,EACGiE,EACHnE,EAAAA,KAAA6F,WAAA,CACC3F,SAAA,CAAAF,EAAAA,KAAC,MAAA,CACAE,SAAA,CAAAC,EAAAA,IAAC,QAAA,CAAMF,UAAU,iCAAiCC,SAAA,MAAA,CAAI,EACtDC,EAAAA,IAAC,OAAA,CAAKF,UAAU,iDACdC,SAAA2C,CAAA,CACF,CAAA,CAAA,CACD,SACC,MAAA,CACA3C,SAAA,CAAAC,EAAAA,IAAC,QAAA,CAAMF,UAAU,iCAAiCC,SAAA,QAAA,CAAM,EACxDC,EAAAA,IAAC,WAAA,CACAiC,MAAOa,GAAa,GACpBhC,SAAWC,GAAM+D,EAAa/D,EAAEC,OAAOiB,KAAK,EAC5CnC,UAAU,+JACVkC,YAAY,qBAAA,CACb,CAAA,CAAA,CACD,EACAnC,EAAAA,KAAC,MAAA,CAAIC,UAAU,aACdC,SAAA,CAAAC,EAAAA,IAACwF,EAAA,CACAC,QAAQ,UACRtD,QAASsC,EACTkB,SAAU,CAACzC,GAAcL,EAAca,QAAU,OAEhD3D,SAAA8C,EAAca,QAAU,OAAS,YAAc,MAAA,CACjD,EACA1D,EAAAA,IAACwF,EAAA,CACAC,QAAQ,OACRtD,QAAS4C,EACTY,SAAU,CAACzC,EACXnD,SAAA,OAAA,CAED,CAAA,CAAA,CACD,CAAA,CAAA,CACD,EAEAC,EAAAA,IAAC,IAAA,CAAEF,UAAU,gCAAgCC,uCAE7C,CAAA,CAEF,CAAA,CAAA,CACD,CAEF,CAEA,SAAS6F,GAAoB,CAC5BC,aAAAA,EACAtD,WAAAA,EACAC,UAAAA,CACD,EASG,CACF,MAAMsD,EAAUlD,EAAA,EAEhB,OAAIiD,EAAapG,SAAW,EAAU,KAGrCI,EAAAA,KAAC,MAAA,CAAIC,UAAU,oDACdC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,+BACdC,SAAA,CAAAC,EAAAA,IAACoC,EAAA,CACAC,KAAK,gBACLvC,UAAU,iCAAA,CACX,EACAD,EAAAA,KAAC,KAAA,CAAGC,UAAU,sCAAsCC,SAAA,CAAA,kBACnC8F,EAAapG,OAAO,GAAA,CAAA,CACrC,CAAA,CAAA,CACD,EACAO,EAAAA,IAAC,IAAA,CAAEF,UAAU,0CAA0CC,SAAA,wEAAA,CAEvD,QACC,MAAA,CAAID,UAAU,YACbC,SAAA8F,EAAa5F,IAAK8F,GAClBlG,EAAAA,KAAC,MAAA,CAEAC,UAAU,sFAEVC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,iBACdC,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAIF,UAAU,iEACbC,SAAAgG,EAAYtD,QAAA,CACd,EACA5C,EAAAA,KAAC,MAAA,CAAIC,UAAU,qCACbC,SAAA,CAAAgG,EAAYC,MAAM,WAAS,IAC5BhG,EAAAA,IAAC,OAAA,CAAKW,MAAO,GAAGoF,EAAYE,IAAI,SAC9BlG,SAAAmG,EAAeH,EAAYE,IAAI,CAAA,CACjC,CAAA,CAAA,CACD,CAAA,CAAA,CACD,EACAjG,EAAAA,IAAC,MAAA,CAAIF,UAAU,qBACdC,SAAAC,EAAAA,IAACmG,EAAA,CACAC,UAAWA,IAAM,CACXN,EAAQpB,OACZ,CACCC,OAAQ,eACRpC,WAAAA,EACAC,UAAAA,EACAC,SAAUsD,EAAYtD,QACvB,EACA,CAAEoC,OAAQ,MAAO,CAClB,CACD,EACAlE,MAAM,0BACNb,UAAU,wFAEVC,SAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,SAASvC,UAAU,UAAU,EACzC,CAAA,CACD,CAAA,CAAA,EAhCKiG,EAAYtD,QAiClB,CACA,CAAA,CACF,CAAA,CAAA,CACD,CAEF,CAEA,MAAA4D,GAAAC,EAAA,SAAwC,CAAEC,WAAAA,CAAW,EAAyB,CAC7E,MAAMT,EAAUlD,EAAA,EAEV4D,EAAcA,CACnBjE,EACAC,EACAC,IACI,CACCqD,EAAQpB,OACZ,CACCC,OAAQ,eACRpC,WAAAA,EACAC,UAAAA,EACAC,SAAAA,CACD,EACA,CAAEoC,OAAQ,MAAO,CAClB,CACD,EAEM4B,EAAcA,CAAClE,EAAoBC,IAAsB,CACzDsD,EAAQpB,OACZ,CACCC,OAAQ,eACRpC,WAAAA,EACAC,UAAAA,CACD,EACA,CAAEqC,OAAQ,MAAO,CAClB,CACD,EAEM6B,EAAuBnE,GAAuB,CAC9CuD,EAAQpB,OACZ,CACCC,OAAQ,wBACRpC,WAAAA,CACD,EACA,CAAEsC,OAAQ,MAAO,CAClB,CACD,EAEM,CACLhG,kBAAAA,EACA8H,gBAAAA,EACAC,eAAAA,EACAzF,YAAAA,EACAxC,kBAAAA,EACAC,mBAAAA,CACD,EAAI2H,EACEM,EAAsBF,EAE5B,OACC9G,EAAAA,KAAC,MAAA,CAAIC,UAAU,YACdC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CACAE,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAGF,UAAU,0BAA0BC,SAAA,kBAAA,CAAgB,EACxDF,EAAAA,KAAC,IAAA,CAAEC,UAAU,wBAAwBC,SAAA,CAAA,oBAClB,IAClBC,EAAAA,IAAC,OAAA,CAAKF,UAAU,gCACfC,SAAAC,EAAAA,IAAC,OAAA,CACAF,UAAWmB,EACV4F,GAAqBrG,qBAClB,KACA,mBACJ,EAECT,WAAoBI,aAAetB,EACrC,CAAA,CACD,CAAA,EACD,EACCgI,GAAqBrG,qBACrBX,EAAAA,KAAC,IAAA,CAAEC,UAAU,qCAAqCC,SAAA,CAAA,eACpC,UACZ,OAAA,CAAKD,UAAU,YAAYa,MAAO9B,EACjCkB,WAAoBK,OAAA,CACtB,CAAA,EACD,EACG,IAAA,CAAA,CACL,EAEAJ,EAAAA,IAACtB,EAAA,CACAC,kBAAAA,EACAC,mBAAAA,EACAC,kBAAAA,CAAA,CACD,EAEAmB,EAAAA,IAACkB,GAAaC,YAAAA,CAAA,CAA0B,EAEvC2E,EAAQjC,MAAMW,SAAW,UACzBxE,EAAAA,IAAC,MAAA,CAAIF,UAAU,sEACbC,SAAA+F,EAAQjC,KAAKiD,OAAA,CACf,EACG,KAEHhB,EAAQjC,MAAMW,SAAW,QACzBxE,EAAAA,IAAC,MAAA,CAAIF,UAAU,8EACbC,SAAA+F,EAAQjC,KAAKmC,KAAA,CACf,EACG,KAEHY,EAAenH,SAAW,EAC1BO,EAAAA,IAAC,OAAIF,UAAU,yCAAyCC,mDAExD,EACG,WAEH,MAAA,CAAID,UAAU,YACbC,SAAA6G,EAAe3G,IAAK8G,GACpBlH,EAAAA,KAAC,UAAA,CAEAsF,KAAM4B,EAAcxE,aAAe1D,EAEnCkB,SAAA,CAAAC,EAAAA,IAAC,WAAQF,UAAU,6EAClBC,SAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,0CACdC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,UACdC,SAAA,CAAAF,EAAAA,KAAC,KAAA,CAAGC,UAAU,6EACbC,SAAA,CAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,QAAQvC,UAAU,kBAAA,CAAmB,EAChDE,EAAAA,IAAC,OAAA,CACAF,UAAWmB,EACV,UACA8F,EAAc7H,SAASsB,qBACpB,WACA,6BACJ,EACAG,MAAOoG,EAAcxE,WAEpBxC,WAAcb,SAASiB,WAAA,CACzB,EACC4G,EAAcxE,aAAe1D,EAC7BmB,EAAAA,IAAC,QAAKF,UAAU,+DAA+DC,mBAE/E,EACG,IAAA,CAAA,CACL,EACCgH,EAAc7H,SAASsB,sBACxBuG,EAAcxE,aAAe,SAC5B1C,EAAAA,KAAC,MAAA,CAAIC,UAAU,uEACbC,SAAA,CAAAgH,EAAc7H,SAASoB,SACvBN,EAAAA,IAAC,OAAA,CAAKF,UAAU,WACdC,SAAAgH,EAAc7H,SAASoB,QAAA,CACzB,EACGyG,EAAc7H,SAASqB,eACzB,OAAA,CAAKT,UAAU,WACdC,SAAAgH,EAAc7H,SAASqB,SACzB,EACG,KACJP,EAAAA,IAAC,OAAA,CACAF,UAAU,YACVa,MAAOoG,EAAcxE,WAEpBxC,WAAcb,SAASkB,OAAA,CACzB,CAAA,EACD,EACG,IAAA,CAAA,CACL,EACAJ,EAAAA,IAACmG,EAAA,CACAC,UAAWA,IACVM,EAAoBK,EAAcxE,UAAU,EAE7C5B,MAAM,6BAENZ,SAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,SAASvC,UAAU,UAAU,CAAA,CACzC,CAAA,EACD,CAAA,CACD,EAEAE,EAAAA,IAAC,OAAIF,UAAU,sBACbC,WAAciH,OAAO/G,IAAKoG,GAAU,CACpC,MAAMY,EAAYZ,EAAMa,QAAQC,OAC/B,CAACC,EAAKtD,IAAUsD,GAAOtD,EAAMmC,MAAQ,GACrC,CACD,EACMoB,GAAehB,EAAMR,cAAgB,CAAA,GAAIsB,OAC9C,CAACC,EAAKE,IAASF,EAAME,EAAKrB,KAC1B,CACD,EACMsB,EAAaN,EAAYI,EAE/B,OACCxH,EAAAA,KAAC,UAAA,CAAyBC,UAAU,sBACnCC,SAAA,CAAAC,EAAAA,IAAC,WAAQF,UAAU,qCAClBC,SAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,oCACdC,SAAA,CAAAF,EAAAA,KAAC,KAAA,CAAGC,UAAU,4DACbC,SAAA,CAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,QAAQvC,UAAU,UAAU,EACtCuG,EAAMhE,KACPxC,EAAAA,KAAC,OAAA,CAAKC,UAAU,UAAUC,SAAA,CAAA,IACvBsG,EAAMa,QAAQzH,OAAO,QACtB4G,EAAMa,QAAQzH,SAAW,EAAI,IAAM,MAAM,GAAA,EAC3C,EACC8H,EAAa,EACb1H,EAAAA,KAAC,OAAA,CAAKC,UAAU,gCAAgCC,SAAA,CAAA,IAC7C,IACFC,EAAAA,IAAC,QAAKW,MAAO,GAAG4G,CAAU,SACxBxH,SAAAmG,EAAeqB,CAAU,CAAA,CAC3B,EAAQ,IAAI,QAEXF,EAAc,EACdxH,EAAAA,KAAC,OAAA,CAAKC,UAAU,eACdC,SAAA,CAAA,IAAI,IAELC,EAAAA,IAAC,QAAKW,MAAO,GAAG0G,CAAW,SACzBtH,SAAAmG,EAAemB,CAAW,CAAA,CAC5B,EAAQ,IAAI,UAAA,EAEb,EACG,IAAA,EACL,EACG,IAAA,CAAA,CACL,EACArH,EAAAA,IAACmG,EAAA,CACAC,UAAWA,IACVK,EAAYM,EAAcxE,WAAY8D,EAAMhE,IAAI,EAEjD1B,MAAM,eAENZ,SAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,SAASvC,UAAU,UAAU,CAAA,CACzC,CAAA,EACD,CAAA,CACD,EAEAD,EAAAA,KAAC,MAAA,CAAIC,UAAU,WACbC,SAAA,CAAAsG,EAAMa,QAAQzH,SAAW,EACzBO,EAAAA,IAAC,KAAEF,UAAU,gCAAgCC,yCAE7C,EACG,KAEHsG,EAAMR,cAAgBQ,EAAMR,aAAapG,OAAS,EAClDO,EAAAA,IAAC4F,GAAA,CACAC,aAAcQ,EAAMR,aACpBtD,WAAYwE,EAAcxE,WAC1BC,UAAW6D,EAAMhE,KAClB,EACG,KAEJrC,EAAAA,IAAC,MAAA,CAAIF,UAAU,YACbC,WAAMmH,QAAQjH,IACd,CAAC,CAAEuH,IAAAA,EAAKC,SAAAA,EAAUhF,SAAAA,EAAUwD,KAAAA,EAAMyB,SAAAA,CAAS,IAC1C7H,EAAAA,KAAC,MAAA,CAEAC,UAAU,iDAEVC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,mCACdC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,iBACdC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,+BACdC,SAAA,CAAAC,EAAAA,IAAC,MAAA,CACAF,UAAU,yCACVa,MAAO6G,EAENzH,SAAAyH,EACF,EACCvB,EACAjG,EAAAA,IAAC,OAAA,CACAF,UAAU,0GACVa,MAAO,GAAGsF,CAAI,SAEblG,WAAekG,CAAI,EACrB,EACG,IAAA,CAAA,CACL,EACAjG,EAAAA,IAAC2H,IAAcF,SAAAA,CAAA,CAAoB,CAAA,CAAA,CACpC,EACA5H,EAAAA,KAAC,MAAA,CAAIC,UAAU,2BACdC,SAAA,CAAAC,EAAAA,IAAC,IAAA,CACAqF,KAAMA,EAAK,iBAAkB,CAC5B,IAAK,GAAG0B,EAAcxE,UAAU,IAAI8D,EAAMhE,IAAI,IAAII,CAAQ,EAC3D,CAAC,EACDzB,OAAO,SACP4G,IAAI,sBACJ9H,UAAU,4KACVa,MAAM,YAENZ,SAAAC,EAAAA,IAACoC,EAAA,CACAC,KAAK,eACLvC,UAAU,UACX,EACD,EACC4H,EACA1H,EAAAA,IAAC6H,EAAA,CACAP,KAAMI,EACN5H,UAAWgI,EAEX/H,SAAAC,EAAAA,IAACoC,EAAA,CACAC,KAAK,QACLvC,UAAU,UACVa,MAAM,iBACP,EACD,EACG,KACJX,EAAAA,IAACmG,EAAA,CACAC,UAAWA,IACVI,EACCO,EAAcxE,WACd8D,EAAMhE,KACNI,CACD,EAED9B,MAAM,eAENZ,SAAAC,EAAAA,IAACoC,EAAA,CAAKC,KAAK,SAASvC,UAAU,UAAU,CAAA,CACzC,CAAA,CAAA,CACD,CAAA,CAAA,CACD,EACAE,EAAAA,IAACsC,EAAA,CACAC,WAAYwE,EAAcxE,WAC1BC,UAAW6D,EAAMhE,KACjBI,SAAAA,EACAC,SAAU8E,CAAA,CACX,CAAA,GArEKA,CAsEN,CAEF,CAAA,CACD,CAAA,CAAA,CACD,CAAA,CAAA,EAtIanB,EAAMhE,IAuIpB,CAEF,CAAC,CAAA,CACF,CAAA,CAAA,EAhNK0E,EAAcxE,UAiNpB,CACA,CAAA,CACF,CAAA,CAAA,CACD,CAEF,CAAA,EAGA,SAASoF,GAAc,CACtBF,SAAAA,CACD,EAMG,CACF,MAAMM,EAAQC,EAAA,EACR,CAAA,CAAGC,CAAc,EAAI3G,EAAAA,SAAS4G,KAAKC,KAAK,EACxCC,EAAiBC,EAAwBZ,CAAQ,EAGvDa,EAAY,IAAM,CACjBL,EAAeC,KAAKC,KAAK,CAC1B,EAAG,GAAI,EAEP,MAAMI,EAAcR,EAAMN,EAASe,WAAW,EACxCC,EAAgBL,EACnBM,EAAoBN,CAAc,EAClC,CAAEO,KAAM,QAASC,UAAW,GAAOC,eAAgB,IAEtD,OACChJ,EAAAA,KAAC,MAAA,CAAIC,UAAU,oDACdC,SAAA,CAAAF,EAAAA,KAAC,MAAA,CAAIE,SAAA,CAAA,YACMwI,EAAYO,OAAO,sBAAsB,EAAG,UACrDC,EAAA,CAAYhJ,SAAAA,IAAM,IAAIwI,EAAYS,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,CACjD,EACAnJ,EAAAA,KAAC,MAAA,CAAIC,UAAU,oCACbC,SAAA,CAAA0H,EAASwB,MAAQ,QAAaxB,EAASwB,MAAQ,YAC9C,OAAA,CAAKlJ,SAAA,CAAA,OACA,IACJ0H,EAASwB,MAAQC,IACjB,gBAEC,OAAA,CAAKvI,MAAO,GAAG8G,EAASwB,GAAG,KAC1BlJ,SAAAoJ,EAAe1B,EAASwB,GAAG,CAAA,CAC7B,CAAA,CAAA,CAEF,EACG,KACHxB,EAAS2B,MAAQ,OACjBvJ,EAAAA,KAAC,OAAA,CAAKE,SAAA,CAAA,OACA,IACLC,EAAAA,IAAC,OAAA,CAAKW,MAAO,GAAG8G,EAAS2B,GAAG,KAC1BrJ,SAAAoJ,EAAe1B,EAAS2B,GAAG,CAAA,CAC7B,CAAA,EACD,EACG,KACJpJ,EAAAA,IAAC,MAAA,CACAF,UAAW,6DACV2I,EAAcG,UACX,6CACAH,EAAcI,eACb,qCACA,iBACL,GAEC9I,WACAF,EAAAA,KAAA6F,WAAA,CAAE3F,SAAA,CAAA,YACSgI,EAAMK,CAAc,EAAEU,OAAO,sBAAsB,EAAE,KAC/D9I,EAAAA,IAAC,QAAKF,UAAU,eACfC,eAACgJ,EAAA,CAAYhJ,SAAAA,IAAM0I,EAAcE,KAAK,EACvC,EAAO,GAAA,CAAA,CAER,EAEA,gBAAA,CAEF,CAAA,CAAA,CACD,CAAA,CAAA,CACD,CAEF,CAGA,SAASxC,EAAkB,CAC1BC,UAAAA,EACArG,SAAAA,EACAD,UAAAA,EACA,GAAGuJ,CACJ,EAEG,CACF,MAAMC,EAAcC,EAAA,EAEpB,OACCvJ,EAAAA,IAACkC,EAAA,CACC,GAAGoH,EAAYE,eAAe,CAC9BrH,QAASmH,EAAYA,YAAclD,EAAY,OAC/C,GAAGiD,CACJ,CAAC,EACDvJ,UAAWmB,EACVqI,EAAYA,YACT,6CACA,KACHxJ,CACD,EAECC,SAAAuJ,EAAYA,YAAc,IAAMvJ,CAAA,CAClC,CAEF"}
1
+ {"version":3,"file":"cache-Cjjb4Th1.js","sources":["../../../app/routes/admin+/cache.tsx"],"sourcesContent":["import {\n\tdeleteCacheEntry,\n\tdeleteWorkshopCache,\n\tgetAllWorkshopCaches,\n\tgetGlobalCaches,\n\tglobalCacheDirectoryExists,\n\treadWorkshopCacheMetadata,\n\tupdateCacheEntry,\n} from '@epic-web/workshop-utils/cache.server'\nimport { getEnv } from '@epic-web/workshop-utils/env.server'\nimport { getErrorMessage } from '@epic-web/workshop-utils/utils'\nimport { useEffect, useRef, useState } from 'react'\nimport { href, useFetcher, useSearchParams } from 'react-router'\nimport { ClientOnly } from 'remix-utils/client-only'\nimport { z } from 'zod'\nimport {\n\tButton,\n\tIconButton,\n\ticonButtonClassName,\n} from '#app/components/button.tsx'\nimport { Icon } from '#app/components/icons.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport {\n\tcalculateExpirationTime,\n\tcn,\n\tensureUndeployed,\n\tformatDuration,\n\tformatFileSize,\n\tformatTimeRemaining,\n\tuseDayjs,\n\tuseDoubleCheck,\n\tuseInterval,\n} from '#app/utils/misc.tsx'\nimport { type Route } from './+types/cache.ts'\n\nfunction normalizeSearchQuery(query: string) {\n\treturn query.trim().toLowerCase()\n}\n\nfunction entryValueMatches(value: unknown, query: string) {\n\tif (!query) return true\n\tif (value === null || value === undefined) return false\n\tif (typeof value === 'string') {\n\t\treturn value.toLowerCase().includes(query)\n\t}\n\ttry {\n\t\tconst serialized = JSON.stringify(value)\n\t\tif (!serialized) return false\n\t\treturn serialized.toLowerCase().includes(query)\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction isLikelyMd5Hash(value: string) {\n\treturn /^[a-f0-9]{32}$/i.test(value)\n}\n\nfunction getShortHash(value: string) {\n\tif (!isLikelyMd5Hash(value)) return value\n\treturn `${value.slice(0, 7)}...${value.slice(-6)}`\n}\n\ntype WorkshopMeta = {\n\tid: string\n\tdisplayName: string\n\tshortId: string\n\trepoName?: string\n\tsubtitle?: string\n\thasStoredDisplayName: boolean\n}\n\nexport async function loader({ request }: Route.LoaderArgs) {\n\tensureUndeployed()\n\tconst currentWorkshopId = getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID\n\tconst allWorkshopCaches = await getAllWorkshopCaches()\n\tconst globalCaches = await getGlobalCaches()\n\tconst allCaches = [...allWorkshopCaches, ...globalCaches]\n\n\tconst url = new URL(request.url)\n\tconst filterQuery = url.searchParams.get('q') || ''\n\n\t// Ensure 'global' is always in available workshops if global cache directory exists\n\tconst availableWorkshopIds = new Set(allCaches.map((w) => w.workshopId))\n\tconst globalDirExists = await globalCacheDirectoryExists()\n\tif (globalDirExists) {\n\t\tavailableWorkshopIds.add('global')\n\t}\n\t// Always include the current workshop in the UI, even if it has no caches yet.\n\tif (currentWorkshopId) {\n\t\tavailableWorkshopIds.add(currentWorkshopId)\n\t}\n\n\tconst selectedWorkshops = url.searchParams\n\t\t.get('workshops')\n\t\t?.split(',')\n\t\t.filter(Boolean) || [\n\t\tcurrentWorkshopId,\n\t\t...(globalDirExists ? ['global'] : []),\n\t]\n\tconst normalizedQuery = normalizeSearchQuery(filterQuery)\n\n\tconst workshopMetaById: Record<string, WorkshopMeta> = {}\n\n\tif (availableWorkshopIds.has('global')) {\n\t\tworkshopMetaById.global = {\n\t\t\tid: 'global',\n\t\t\tdisplayName: 'Global caches',\n\t\t\tshortId: 'global',\n\t\t\thasStoredDisplayName: true,\n\t\t}\n\t}\n\n\tconst workshopIds = Array.from(availableWorkshopIds).filter(\n\t\t(workshopId) => workshopId !== 'global',\n\t)\n\tconst workshopIdMetas = await Promise.all(\n\t\tworkshopIds.map(async (workshopId) => {\n\t\t\tconst meta = await readWorkshopCacheMetadata(workshopId)\n\t\t\treturn [workshopId, meta] as const\n\t\t}),\n\t)\n\tfor (const [workshopId, meta] of workshopIdMetas) {\n\t\tworkshopMetaById[workshopId] = {\n\t\t\tid: workshopId,\n\t\t\tdisplayName: meta?.displayName ?? workshopId,\n\t\t\tshortId: getShortHash(workshopId),\n\t\t\trepoName: meta?.repoName,\n\t\t\tsubtitle: meta?.subtitle,\n\t\t\thasStoredDisplayName: Boolean(meta?.displayName),\n\t\t}\n\t}\n\n\tconst compareWorkshopIds = (a: string, b: string) => {\n\t\tif (a === currentWorkshopId) return -1\n\t\tif (b === currentWorkshopId) return 1\n\t\tif (a === 'global') return -1\n\t\tif (b === 'global') return 1\n\t\tconst aLabel = workshopMetaById[a]?.displayName ?? a\n\t\tconst bLabel = workshopMetaById[b]?.displayName ?? b\n\t\treturn aLabel.localeCompare(bLabel)\n\t}\n\n\t// Filter caches based on search query and selected workshops\n\tconst filteredCaches = allCaches\n\t\t.filter(\n\t\t\t(workshopCache) =>\n\t\t\t\tselectedWorkshops.includes(workshopCache.workshopId) ||\n\t\t\t\tselectedWorkshops.length === 0,\n\t\t)\n\t\t.map((workshopCache) => {\n\t\t\tconst workshop =\n\t\t\t\tworkshopMetaById[workshopCache.workshopId] ??\n\t\t\t\t({\n\t\t\t\t\tid: workshopCache.workshopId,\n\t\t\t\t\tdisplayName: workshopCache.workshopId,\n\t\t\t\t\tshortId: getShortHash(workshopCache.workshopId),\n\t\t\t\t\thasStoredDisplayName: false,\n\t\t\t\t} satisfies WorkshopMeta)\n\t\t\tconst workshopLabel = workshop.displayName\n\t\t\tconst workshopMatchesQuery =\n\t\t\t\tnormalizedQuery === ''\n\t\t\t\t\t? true\n\t\t\t\t\t: workshopLabel.toLowerCase().includes(normalizedQuery) ||\n\t\t\t\t\t\tworkshopCache.workshopId.toLowerCase().includes(normalizedQuery)\n\t\t\treturn {\n\t\t\t\t...workshopCache,\n\t\t\t\tworkshop,\n\t\t\t\tcaches: workshopCache.caches\n\t\t\t\t\t.map((cache) => {\n\t\t\t\t\t\tconst cacheNameMatches =\n\t\t\t\t\t\t\tnormalizedQuery === ''\n\t\t\t\t\t\t\t\t? true\n\t\t\t\t\t\t\t\t: workshopMatchesQuery ||\n\t\t\t\t\t\t\t\t\tcache.name.toLowerCase().includes(normalizedQuery)\n\t\t\t\t\t\tconst entries = cache.entries\n\t\t\t\t\t\t\t.filter((entry) => {\n\t\t\t\t\t\t\t\tif (normalizedQuery === '') return true\n\t\t\t\t\t\t\t\tif (cacheNameMatches) return true\n\t\t\t\t\t\t\t\tif (entry.key.toLowerCase().includes(normalizedQuery))\n\t\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t\treturn entryValueMatches(entry.entry.value, normalizedQuery)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.map((entry) => ({\n\t\t\t\t\t\t\t\tkey: entry.key,\n\t\t\t\t\t\t\t\tfilename: entry.filename,\n\t\t\t\t\t\t\t\tsize: entry.size,\n\t\t\t\t\t\t\t\tfilepath: entry.filepath,\n\t\t\t\t\t\t\t\tmetadata: entry.entry.metadata,\n\t\t\t\t\t\t\t}))\n\t\t\t\t\t\treturn { ...cache, entries }\n\t\t\t\t\t})\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(cache) => cache.entries.length > 0 || normalizedQuery === '',\n\t\t\t\t\t),\n\t\t\t}\n\t\t})\n\t\t.filter(\n\t\t\t(workshopCache) =>\n\t\t\t\tworkshopCache.caches.length > 0 || normalizedQuery === '',\n\t\t)\n\n\treturn {\n\t\tcurrentWorkshopId,\n\t\tcurrentWorkshop:\n\t\t\tworkshopMetaById[currentWorkshopId] ??\n\t\t\t({\n\t\t\t\tid: currentWorkshopId,\n\t\t\t\tdisplayName: currentWorkshopId,\n\t\t\t\tshortId: getShortHash(currentWorkshopId),\n\t\t\t\thasStoredDisplayName: false,\n\t\t\t} satisfies WorkshopMeta),\n\t\tfilteredCaches: [...filteredCaches].sort((a, b) =>\n\t\t\tcompareWorkshopIds(a.workshopId, b.workshopId),\n\t\t),\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops: Array.from(availableWorkshopIds)\n\t\t\t.map((workshopId) => {\n\t\t\t\treturn (\n\t\t\t\t\tworkshopMetaById[workshopId] ??\n\t\t\t\t\t({\n\t\t\t\t\t\tid: workshopId,\n\t\t\t\t\t\tdisplayName: workshopId,\n\t\t\t\t\t\tshortId: getShortHash(workshopId),\n\t\t\t\t\t\thasStoredDisplayName: false,\n\t\t\t\t\t} satisfies WorkshopMeta)\n\t\t\t\t)\n\t\t\t})\n\t\t\t.sort((a, b) => compareWorkshopIds(a.id, b.id)),\n\t}\n}\n\nconst ActionSchema = z.discriminatedUnion('intent', [\n\tz.object({\n\t\tintent: z.literal('delete-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-cache'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-workshop-cache'),\n\t\tworkshopId: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('update-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t\tnewValue: z.string(),\n\t}),\n])\n\nexport async function action({ request }: Route.ActionArgs) {\n\tensureUndeployed()\n\n\tconst formData = await request.formData()\n\tconst rawData = Object.fromEntries(formData.entries())\n\tconst result = ActionSchema.safeParse(rawData)\n\n\tif (!result.success) {\n\t\treturn { status: 'error', error: 'Invalid request' } as const\n\t}\n\n\tconst data = result.data\n\n\ttry {\n\t\tswitch (data.intent) {\n\t\t\tcase 'delete-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\tawait deleteCacheEntry(path)\n\t\t\t\treturn { status: 'success', message: 'Cache entry deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId, data.cacheName)\n\t\t\t\treturn { status: 'success', message: 'Cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-workshop-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId)\n\t\t\t\treturn { status: 'success', message: 'Workshop cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'update-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\ttry {\n\t\t\t\t\tconst parsedValue = JSON.parse(data.newValue)\n\t\t\t\t\tawait updateCacheEntry(path, parsedValue)\n\t\t\t\t\treturn { status: 'success', message: 'Cache entry updated' } as const\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\terror: getErrorMessage(error, 'Invalid JSON value'),\n\t\t\t\t\t} as const\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error('Cache action error:', error)\n\t\treturn { status: 'error', error: 'Operation failed' } as const\n\t}\n}\n\nfunction WorkshopChooser({\n\tselectedWorkshops,\n\tavailableWorkshops,\n\tcurrentWorkshopId,\n}: {\n\tselectedWorkshops: string[]\n\tavailableWorkshops: Array<WorkshopMeta>\n\tcurrentWorkshopId: string\n}) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\n\tconst handleWorkshopChange = (workshop: string, checked: boolean) => {\n\t\tconst newSelected = checked\n\t\t\t? [...selectedWorkshops, workshop]\n\t\t\t: selectedWorkshops.filter((w) => w !== workshop)\n\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (newSelected.length > 0) {\n\t\t\tparams.set('workshops', newSelected.join(','))\n\t\t} else {\n\t\t\tparams.delete('workshops')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Workshop Filter</h3>\n\t\t\t<div className=\"flex flex-wrap gap-3\">\n\t\t\t\t{availableWorkshops.map((workshop) => {\n\t\t\t\t\tconst label = workshop.displayName\n\t\t\t\t\tconst shortId = workshop.shortId\n\t\t\t\t\tconst secondary = workshop.repoName ?? workshop.subtitle\n\t\t\t\t\tconst hasStoredDisplayName = workshop.hasStoredDisplayName\n\t\t\t\t\tconst showDetailsLine =\n\t\t\t\t\t\thasStoredDisplayName && workshop.id !== 'global'\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tkey={workshop.id}\n\t\t\t\t\t\t\tclassName=\"flex items-center gap-2\"\n\t\t\t\t\t\t\ttitle={secondary ? `${label} (${secondary})` : label}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tchecked={selectedWorkshops.includes(workshop.id)}\n\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\thandleWorkshopChange(workshop.id, e.target.checked)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tclassName=\"rounded\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t'text-sm',\n\t\t\t\t\t\t\t\t\tworkshop.id === currentWorkshopId\n\t\t\t\t\t\t\t\t\t\t? 'text-primary font-bold'\n\t\t\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span className=\"inline-flex max-w-[22rem] flex-col leading-tight\">\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\thasStoredDisplayName\n\t\t\t\t\t\t\t\t\t\t\t\t? 'truncate'\n\t\t\t\t\t\t\t\t\t\t\t\t: 'font-mono text-xs break-all',\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{showDetailsLine ? (\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t'text-muted-foreground font-mono text-xs font-normal',\n\t\t\t\t\t\t\t\t\t\t\t\tworkshop.id === currentWorkshopId\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'text-primary/80'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\ttitle={workshop.id}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{secondary ? `${secondary} • ` : null}\n\t\t\t\t\t\t\t\t\t\t\t{shortId}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t{workshop.id === currentWorkshopId ? '(current)' : null}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t)\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nfunction SearchFilter({ filterQuery }: { filterQuery: string }) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\tconst [inputValue, setInputValue] = useState(filterQuery)\n\tconst inputRef = useRef<HTMLInputElement>(null)\n\n\t// Update input value when filterQuery changes (e.g., from URL)\n\tuseEffect(() => {\n\t\tsetInputValue(filterQuery)\n\t}, [filterQuery])\n\n\tconst handleSearch = (query: string) => {\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (query) {\n\t\t\tparams.set('q', query)\n\t\t} else {\n\t\t\tparams.delete('q')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\tconst handleClear = () => {\n\t\tsetInputValue('')\n\t\thandleSearch('')\n\t\tinputRef.current?.focus()\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Search Cache Entries</h3>\n\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"Search by key, value, cache name, or workshop...\"\n\t\t\t\t\tvalue={inputValue}\n\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\tsetInputValue(e.target.value)\n\t\t\t\t\t\thandleSearch(e.target.value)\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"border-border bg-background text-foreground focus:ring-ring flex-1 rounded-md border px-3 py-2 focus:ring-2 focus:outline-none\"\n\t\t\t\t/>\n\t\t\t\t{inputValue ? (\n\t\t\t\t\t<IconButton onClick={handleClear} title=\"Clear search\">\n\t\t\t\t\t\t<Icon name=\"Close\" className=\"h-4 w-4\" />\n\t\t\t\t\t</IconButton>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Inline entry editor component\nfunction InlineEntryEditor({\n\tworkshopId,\n\tcacheName,\n\tfilename,\n\tentryKey,\n}: {\n\tworkshopId: string\n\tcacheName: string\n\tfilename: string\n\tentryKey: string\n}) {\n\tconst entryFetcher = useFetcher<{\n\t\tentry: {\n\t\t\tvalue: unknown\n\t\t\tmetadata: {\n\t\t\t\tcreatedTime: number\n\t\t\t\tttl?: number | null\n\t\t\t\tswr?: number\n\t\t\t}\n\t\t} | null\n\t}>()\n\tconst updateFetcher = useFetcher<typeof action>()\n\tconst [editValue, setEditValue] = useState<string | null>(null)\n\tconst [baselineValue, setBaselineValue] = useState<string | null>(null)\n\tconst [hasChanges, setHasChanges] = useState(false)\n\tconst [hasRequested, setHasRequested] = useState(false)\n\tconst [hasStartedLoading, setHasStartedLoading] = useState(false)\n\tconst submittedValueRef = useRef<string | null>(null)\n\tconst prevUpdateStateRef = useRef<string>(updateFetcher.state)\n\n\tconst entryPath = `${workshopId}/${cacheName}/${filename}`\n\tconst entryValue = entryFetcher.data?.entry?.value\n\tconst isEntryLoading = entryFetcher.state === 'loading'\n\tconst hasEntry = Boolean(entryFetcher.data?.entry)\n\tconst entryMissing = entryFetcher.data?.entry === null\n\tconst entryFetchFailed =\n\t\thasRequested &&\n\t\thasStartedLoading &&\n\t\t!isEntryLoading &&\n\t\tentryFetcher.data === undefined &&\n\t\tentryFetcher.state === 'idle'\n\n\tuseEffect(() => {\n\t\tif (entryFetcher.state !== 'idle') {\n\t\t\tsetHasStartedLoading(true)\n\t\t}\n\t}, [entryFetcher.state])\n\n\tuseEffect(() => {\n\t\tif (hasEntry) {\n\t\t\tconst formattedValue = JSON.stringify(entryValue, null, 2) ?? ''\n\t\t\tsetEditValue(formattedValue)\n\t\t\tsetBaselineValue(formattedValue)\n\t\t\tsetHasChanges(false)\n\t\t}\n\t}, [hasEntry, entryValue])\n\n\tuseEffect(() => {\n\t\tconst currentUpdateState = updateFetcher.state\n\t\tconst wasSubmitting = prevUpdateStateRef.current !== 'idle'\n\t\tconst nowIdle = currentUpdateState === 'idle'\n\n\t\tif (wasSubmitting && nowIdle && updateFetcher.data?.status === 'success') {\n\t\t\tsetBaselineValue(submittedValueRef.current ?? '')\n\t\t\tsetHasChanges(false)\n\t\t}\n\t\tprevUpdateStateRef.current = currentUpdateState\n\t}, [updateFetcher.state, updateFetcher.data?.status])\n\n\tconst handleSave = () => {\n\t\tif (editValue === null) return\n\t\tsubmittedValueRef.current = editValue\n\t\tvoid updateFetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'update-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t\tnewValue: editValue,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst handleChange = (value: string) => {\n\t\tsetEditValue(value)\n\t\tif (baselineValue === null) {\n\t\t\tsetHasChanges(false)\n\t\t\treturn\n\t\t}\n\t\tsetHasChanges(value !== baselineValue)\n\t}\n\n\tconst handleReset = () => {\n\t\tif (baselineValue === null) return\n\t\tsetEditValue(baselineValue)\n\t\tsetHasChanges(false)\n\t}\n\n\tconst handleToggle = (event: React.SyntheticEvent<HTMLDetailsElement>) => {\n\t\tif (event.currentTarget.open && !hasRequested) {\n\t\t\tvoid entryFetcher.load(\n\t\t\t\thref('/admin/cache/*', {\n\t\t\t\t\t'*': entryPath,\n\t\t\t\t}),\n\t\t\t)\n\t\t\tsetHasRequested(true)\n\t\t} else if (!event.currentTarget.open) {\n\t\t\t// Reset on close to allow retry\n\t\t\tsetHasRequested(false)\n\t\t\tsetHasStartedLoading(false)\n\t\t}\n\t}\n\n\tconst handleRetry = () => {\n\t\tvoid entryFetcher.load(\n\t\t\thref('/admin/cache/*', {\n\t\t\t\t'*': entryPath,\n\t\t\t}),\n\t\t)\n\t}\n\n\treturn (\n\t\t<details className=\"mt-2\" onToggle={handleToggle}>\n\t\t\t<summary className=\"text-muted-foreground hover:text-foreground cursor-pointer text-sm\">\n\t\t\t\tEdit entry details\n\t\t\t</summary>\n\t\t\t<div className=\"border-border bg-muted mt-2 space-y-3 rounded border p-3\">\n\t\t\t\t{isEntryLoading ? (\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">Loading entry...</p>\n\t\t\t\t) : entryFetchFailed ? (\n\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t<p className=\"text-destructive text-sm\">\n\t\t\t\t\t\t\tFailed to load entry details.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Button varient=\"mono\" onClick={handleRetry}>\n\t\t\t\t\t\t\tRetry\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t) : entryMissing ? (\n\t\t\t\t\t<p className=\"text-destructive text-sm\">\n\t\t\t\t\t\tEntry details were not found.\n\t\t\t\t\t</p>\n\t\t\t\t) : hasEntry ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Key:</label>\n\t\t\t\t\t\t\t<code className=\"bg-background rounded border px-2 py-1 text-sm\">\n\t\t\t\t\t\t\t\t{entryKey}\n\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Value:</label>\n\t\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\t\tvalue={editValue ?? ''}\n\t\t\t\t\t\t\t\tonChange={(e) => handleChange(e.target.value)}\n\t\t\t\t\t\t\t\tclassName=\"resize-vertical border-border bg-background text-foreground focus:ring-ring h-32 w-full rounded border p-2 font-mono text-sm focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\t\t\tplaceholder=\"Enter JSON value...\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvarient=\"primary\"\n\t\t\t\t\t\t\t\tonClick={handleSave}\n\t\t\t\t\t\t\t\tdisabled={!hasChanges || updateFetcher.state !== 'idle'}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{updateFetcher.state !== 'idle' ? 'Saving...' : 'Save'}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvarient=\"mono\"\n\t\t\t\t\t\t\t\tonClick={handleReset}\n\t\t\t\t\t\t\t\tdisabled={!hasChanges}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tReset\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\tOpen to load entry details.\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</details>\n\t)\n}\n\nfunction SkippedFilesSection({\n\tskippedFiles,\n\tworkshopId,\n\tcacheName,\n}: {\n\tskippedFiles: Array<{\n\t\tfilename: string\n\t\terror: string\n\t\tsize: number\n\t\tskipped: true\n\t}>\n\tworkshopId: string\n\tcacheName: string\n}) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tif (skippedFiles.length === 0) return null\n\n\treturn (\n\t\t<div className=\"border-warning bg-warning mt-4 rounded border p-3\">\n\t\t\t<div className=\"mb-2 flex items-center gap-2\">\n\t\t\t\t<Icon\n\t\t\t\t\tname=\"TriangleAlert\"\n\t\t\t\t\tclassName=\"text-warning-foreground h-4 w-4\"\n\t\t\t\t/>\n\t\t\t\t<h5 className=\"text-warning-foreground font-medium\">\n\t\t\t\t\tSkipped Files ({skippedFiles.length})\n\t\t\t\t</h5>\n\t\t\t</div>\n\t\t\t<p className=\"text-warning-foreground/80 mb-3 text-sm\">\n\t\t\t\tThese cache files were skipped because they exceed the 3MB size limit:\n\t\t\t</p>\n\t\t\t<div className=\"space-y-2\">\n\t\t\t\t{skippedFiles.map((skippedFile) => (\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={skippedFile.filename}\n\t\t\t\t\t\tclassName=\"border-warning/20 bg-warning/5 flex items-center justify-between rounded border p-2\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground truncate font-mono text-sm font-medium\">\n\t\t\t\t\t\t\t\t{skippedFile.filename}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground/70 text-xs\">\n\t\t\t\t\t\t\t\t{skippedFile.error} • Size:{' '}\n\t\t\t\t\t\t\t\t<span title={`${skippedFile.size} bytes`}>\n\t\t\t\t\t\t\t\t\t{formatFileSize(skippedFile.size)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"ml-2 flex shrink-0\">\n\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\tonConfirm={() => {\n\t\t\t\t\t\t\t\t\tvoid fetcher.submit(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tintent: 'delete-entry',\n\t\t\t\t\t\t\t\t\t\t\tworkshopId,\n\t\t\t\t\t\t\t\t\t\t\tcacheName,\n\t\t\t\t\t\t\t\t\t\t\tfilename: skippedFile.filename,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{ method: 'POST' },\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\ttitle=\"Delete large cache file\"\n\t\t\t\t\t\t\t\tclassName=\"text-destructive-foreground hover:bg-destructive/20 hover:text-destructive-foreground\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport default function CacheManagement({ loaderData }: Route.ComponentProps) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tconst deleteEntry = (\n\t\tworkshopId: string,\n\t\tcacheName: string,\n\t\tfilename: string,\n\t) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteCache = (workshopId: string, cacheName: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-cache',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteWorkshopCache = (workshopId: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-workshop-cache',\n\t\t\t\tworkshopId,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst {\n\t\tcurrentWorkshopId,\n\t\tcurrentWorkshop,\n\t\tfilteredCaches,\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops,\n\t} = loaderData\n\tconst currentWorkshopMeta = currentWorkshop\n\n\treturn (\n\t\t<div className=\"space-y-6\">\n\t\t\t<div>\n\t\t\t\t<h2 className=\"mb-2 text-2xl font-bold\">Cache Management</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">\n\t\t\t\t\tCurrent Workshop:{' '}\n\t\t\t\t\t<span className=\"text-foreground font-semibold\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\tcurrentWorkshopMeta?.hasStoredDisplayName\n\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t: 'font-mono text-xs',\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{currentWorkshopMeta.displayName ?? currentWorkshopId}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</p>\n\t\t\t\t{currentWorkshopMeta?.hasStoredDisplayName ? (\n\t\t\t\t\t<p className=\"text-muted-foreground mt-1 text-xs\">\n\t\t\t\t\t\tInstance ID:{' '}\n\t\t\t\t\t\t<span className=\"font-mono\" title={currentWorkshopId}>\n\t\t\t\t\t\t\t{currentWorkshopMeta.shortId}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</p>\n\t\t\t\t) : null}\n\t\t\t</div>\n\n\t\t\t<WorkshopChooser\n\t\t\t\tselectedWorkshops={selectedWorkshops}\n\t\t\t\tavailableWorkshops={availableWorkshops}\n\t\t\t\tcurrentWorkshopId={currentWorkshopId}\n\t\t\t/>\n\n\t\t\t<SearchFilter filterQuery={filterQuery} />\n\n\t\t\t{fetcher.data?.status === 'success' ? (\n\t\t\t\t<div className=\"border-border bg-success text-success-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.message}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{fetcher.data?.status === 'error' ? (\n\t\t\t\t<div className=\"border-border bg-destructive text-destructive-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.error}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{filteredCaches.length === 0 ? (\n\t\t\t\t<div className=\"text-muted-foreground py-8 text-center\">\n\t\t\t\t\tNo caches found matching your criteria.\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t<div className=\"space-y-6\">\n\t\t\t\t{filteredCaches.map((workshopCache) => (\n\t\t\t\t\t<details\n\t\t\t\t\t\tkey={workshopCache.workshopId}\n\t\t\t\t\t\topen={workshopCache.workshopId === currentWorkshopId}\n\t\t\t\t\t>\n\t\t\t\t\t\t<summary className=\"border-border bg-card hover:bg-accent cursor-pointer rounded-lg border p-4\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between gap-4\">\n\t\t\t\t\t\t\t\t<div className=\"min-w-0\">\n\t\t\t\t\t\t\t\t\t<h3 className=\"text-card-foreground flex min-w-0 items-center gap-2 text-lg font-semibold\">\n\t\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-5 w-5 shrink-0\" />\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t'min-w-0',\n\t\t\t\t\t\t\t\t\t\t\t\tworkshopCache.workshop.hasStoredDisplayName\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'truncate'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'font-mono text-xs break-all',\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\ttitle={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.displayName}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t{workshopCache.workshopId === currentWorkshopId ? (\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"bg-primary text-primary-foreground rounded px-2 py-1 text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t\tCurrent\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t{workshopCache.workshop.hasStoredDisplayName &&\n\t\t\t\t\t\t\t\t\tworkshopCache.workshopId !== 'global' ? (\n\t\t\t\t\t\t\t\t\t\t<div className=\"text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.repoName ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.repoName}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t) : workshopCache.workshop.subtitle ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.subtitle}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"font-mono\"\n\t\t\t\t\t\t\t\t\t\t\t\ttitle={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t{workshopCache.workshop.shortId}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\tdeleteWorkshopCache(workshopCache.workshopId)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttitle=\"Delete all workshop caches\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t<div className=\"mt-4 space-y-4 pl-4\">\n\t\t\t\t\t\t\t{workshopCache.caches.map((cache) => {\n\t\t\t\t\t\t\t\tconst totalSize = cache.entries.reduce(\n\t\t\t\t\t\t\t\t\t(sum, entry) => sum + (entry.size || 0),\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst skippedSize = (cache.skippedFiles || []).reduce(\n\t\t\t\t\t\t\t\t\t(sum, file) => sum + file.size,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst grandTotal = totalSize + skippedSize\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<details key={cache.name} className=\"bg-muted rounded-md\">\n\t\t\t\t\t\t\t\t\t\t<summary className=\"hover:bg-accent cursor-pointer p-3\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<h4 className=\"text-muted-foreground flex items-center gap-2 font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t({cache.entries.length} entr\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 1 ? 'y' : 'ies'})\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{grandTotal > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t•{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${grandTotal} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(grandTotal)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttotal\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{skippedSize > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-warning\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${skippedSize} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(skippedSize)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tskipped)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteCache(workshopCache.workshopId, cache.name)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete cache\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"p-3 pt-0\">\n\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tNo entries match your search.\n\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t{cache.skippedFiles && cache.skippedFiles.length > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<SkippedFilesSection\n\t\t\t\t\t\t\t\t\t\t\t\t\tskippedFiles={cache.skippedFiles}\n\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t({ key, metadata, filename, size, filepath }) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background rounded border p-3\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mb-1 flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"truncate font-mono text-sm font-medium\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{size ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"bg-muted text-muted-foreground inline-flex items-center rounded px-1.5 py-0.5 text-xs whitespace-nowrap\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={`${size} bytes`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(size)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CacheMetadata metadata={metadata} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"ml-4 flex shrink-0 gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={href('/admin/cache/*', {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'*': `${workshopCache.workshopId}/${cache.name}/${filename}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background text-foreground hover:bg-muted focus:ring-ring inline-flex h-8 w-8 items-center justify-center rounded border focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"View JSON\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"ExternalLink\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{filepath ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<LaunchEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfile={filepath}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={iconButtonClassName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"Files\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Open in editor\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</LaunchEditor>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteEntry(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopCache.workshopId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcache.name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete entry\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<InlineEntryEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename={filename}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentryKey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</details>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Component for displaying cache metadata with live countdown\nfunction CacheMetadata({\n\tmetadata,\n}: {\n\tmetadata: {\n\t\tcreatedTime: number\n\t\tttl?: number | null\n\t\tswr?: number\n\t}\n}) {\n\tconst dayjs = useDayjs()\n\tconst [, setCurrentTime] = useState(Date.now())\n\tconst expirationTime = calculateExpirationTime(metadata)\n\n\t// Update time every second for live countdown\n\tuseInterval(() => {\n\t\tsetCurrentTime(Date.now())\n\t}, 1000)\n\n\tconst createdDate = dayjs(metadata.createdTime)\n\tconst timeRemaining = expirationTime\n\t\t? formatTimeRemaining(expirationTime)\n\t\t: { text: 'Never', isExpired: false, isExpiringSoon: false }\n\n\treturn (\n\t\t<div className=\"text-muted-foreground flex flex-col gap-1 text-xs\">\n\t\t\t<div>\n\t\t\t\tCreated: {createdDate.format('MMM D, YYYY HH:mm:ss')}{' '}\n\t\t\t\t<ClientOnly>{() => `(${createdDate.fromNow()})`}</ClientOnly>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-wrap items-center gap-3\">\n\t\t\t\t{metadata.ttl !== undefined && metadata.ttl !== null ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tTTL:{' '}\n\t\t\t\t\t\t{metadata.ttl === Infinity ? (\n\t\t\t\t\t\t\t'Forever'\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<span title={`${metadata.ttl}ms`}>\n\t\t\t\t\t\t\t\t{formatDuration(metadata.ttl)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t{metadata.swr !== undefined ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tSWR:{' '}\n\t\t\t\t\t\t<span title={`${metadata.swr}ms`}>\n\t\t\t\t\t\t\t{formatDuration(metadata.swr)}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<div\n\t\t\t\t\tclassName={`inline-flex w-auto rounded-full px-2 py-[2px] font-medium ${\n\t\t\t\t\t\ttimeRemaining.isExpired\n\t\t\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t\t\t: timeRemaining.isExpiringSoon\n\t\t\t\t\t\t\t\t? 'bg-warning text-warning-foreground'\n\t\t\t\t\t\t\t\t: 'text-foreground'\n\t\t\t\t\t}`}\n\t\t\t\t>\n\t\t\t\t\t{expirationTime ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tExpires: {dayjs(expirationTime).format('MMM D, YYYY HH:mm:ss')} (\n\t\t\t\t\t\t\t<span className=\"tabular-nums\">\n\t\t\t\t\t\t\t\t<ClientOnly>{() => timeRemaining.text}</ClientOnly>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t'Expires: Never'\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Double-check delete button\nfunction DoubleCheckButton({\n\tonConfirm,\n\tchildren,\n\tclassName,\n\t...props\n}: React.ComponentPropsWithoutRef<'button'> & {\n\tonConfirm: () => void\n}) {\n\tconst doubleCheck = useDoubleCheck()\n\n\treturn (\n\t\t<IconButton\n\t\t\t{...doubleCheck.getButtonProps({\n\t\t\t\tonClick: doubleCheck.doubleCheck ? onConfirm : undefined,\n\t\t\t\t...props,\n\t\t\t})}\n\t\t\tclassName={cn(\n\t\t\t\tdoubleCheck.doubleCheck\n\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t: null,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{doubleCheck.doubleCheck ? '✓' : children}\n\t\t</IconButton>\n\t)\n}\n"],"names":["WorkshopChooser","selectedWorkshops","availableWorkshops","currentWorkshopId","searchParams","setSearchParams","useSearchParams","handleWorkshopChange","workshop","checked","newSelected","filter","w","params","URLSearchParams","length","set","join","delete","jsxs","className","children","jsx","map","label","displayName","shortId","secondary","repoName","subtitle","hasStoredDisplayName","showDetailsLine","id","title","type","includes","onChange","e","target","cn","SearchFilter","filterQuery","inputValue","setInputValue","useState","inputRef","useRef","useEffect","handleSearch","query","handleClear","current","focus","ref","placeholder","value","IconButton","onClick","Icon","name","InlineEntryEditor","workshopId","cacheName","filename","entryKey","entryFetcher","useFetcher","updateFetcher","editValue","setEditValue","baselineValue","setBaselineValue","hasChanges","setHasChanges","hasRequested","setHasRequested","hasStartedLoading","setHasStartedLoading","submittedValueRef","prevUpdateStateRef","state","entryPath","entryValue","data","entry","isEntryLoading","hasEntry","Boolean","entryMissing","entryFetchFailed","formattedValue","JSON","stringify","currentUpdateState","status","handleSave","submit","intent","newValue","method","handleChange","handleReset","handleToggle","event","currentTarget","open","load","href","handleRetry","onToggle","Button","varient","Fragment","disabled","SkippedFilesSection","skippedFiles","fetcher","skippedFile","error","size","formatFileSize","DoubleCheckButton","onConfirm","cache","_UNSAFE_withComponentProps","loaderData","deleteEntry","deleteCache","deleteWorkshopCache","currentWorkshop","filteredCaches","currentWorkshopMeta","message","workshopCache","caches","totalSize","entries","reduce","sum","skippedSize","file","grandTotal","key","metadata","filepath","CacheMetadata","rel","LaunchEditor","iconButtonClassName","dayjs","useDayjs","setCurrentTime","Date","now","expirationTime","calculateExpirationTime","useInterval","createdDate","createdTime","timeRemaining","formatTimeRemaining","text","isExpired","isExpiringSoon","format","ClientOnly","fromNow","ttl","Infinity","formatDuration","swr","props","doubleCheck","useDoubleCheck","getButtonProps"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{w as s}from"./chunk-JZWAC4HX-BuixXc9D.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{I as a}from"./misc-D9k_mI0z.js";import{S as i}from"./tooltip-H74tOFSr.js";import{L as n}from"./launch-editor-C8OwTN5L.js";import"./index-Bi6L2ga8.js";import"./index-B2VNpNxJ.js";import"./progress-bar-BCbq1CuC.js";import"./pe-D585gnmh.js";import"./root-loader-DAxaiFMN.js";const g=s(function({loaderData:r}){const{db:o,filepath:t}=r;return e.jsxs("main",{children:[e.jsx("h1",{className:"text-2xl font-bold",children:"Database"}),e.jsx("div",{className:"prose mt-4",children:e.jsxs("callout-danger",{children:[e.jsx("strong",{children:"Warning:"})," Editing the database directly can corrupt your data. Be very careful!"]})}),e.jsxs(n,{file:t,className:"flex max-w-full items-center gap-2",children:[e.jsx(i,{content:"Open the database file in your editor",children:e.jsx(a,{name:"Files",className:"h-4 w-4",title:"Open in editor"})}),e.jsx("span",{className:"truncate",title:t,children:t})]}),e.jsx("pre",{className:"bg-muted text-foreground mt-4 max-h-[400px] w-full overflow-auto rounded border p-4 text-sm",children:JSON.stringify(o,null,2)})]})});export{g as default};
2
- //# sourceMappingURL=db-CPB2ZdAy.js.map
1
+ import{w as s}from"./chunk-JZWAC4HX-BuixXc9D.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{I as a}from"./misc-D9k_mI0z.js";import{S as i}from"./tooltip-H74tOFSr.js";import{L as n}from"./launch-editor-C-8ah8qM.js";import"./index-Bi6L2ga8.js";import"./index-B2VNpNxJ.js";import"./progress-bar-BCbq1CuC.js";import"./pe-D585gnmh.js";import"./root-loader-DAxaiFMN.js";const g=s(function({loaderData:r}){const{db:o,filepath:t}=r;return e.jsxs("main",{children:[e.jsx("h1",{className:"text-2xl font-bold",children:"Database"}),e.jsx("div",{className:"prose mt-4",children:e.jsxs("callout-danger",{children:[e.jsx("strong",{children:"Warning:"})," Editing the database directly can corrupt your data. Be very careful!"]})}),e.jsxs(n,{file:t,className:"flex max-w-full items-center gap-2",children:[e.jsx(i,{content:"Open the database file in your editor",children:e.jsx(a,{name:"Files",className:"h-4 w-4",title:"Open in editor"})}),e.jsx("span",{className:"truncate",title:t,children:t})]}),e.jsx("pre",{className:"bg-muted text-foreground mt-4 max-h-[400px] w-full overflow-auto rounded border p-4 text-sm",children:JSON.stringify(o,null,2)})]})});export{g as default};
2
+ //# sourceMappingURL=db-n83JPsm9.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"db-CPB2ZdAy.js","sources":["../../../app/routes/admin+/db.tsx"],"sourcesContent":["import { loadJSON } from '@epic-web/workshop-utils/data-storage.server'\nimport { readDb } from '@epic-web/workshop-utils/db.server'\nimport { Icon } from '#app/components/icons.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { ensureUndeployed } from '#app/utils/misc.tsx'\nimport { LaunchEditor } from '../launch-editor.tsx'\nimport { type Route } from './+types/db.ts'\n\nexport async function loader() {\n\tensureUndeployed()\n\tconst db = await readDb()\n\tconst { path: filepath } = await loadJSON()\n\n\treturn { db, filepath }\n}\n\nexport default function DbRoute({ loaderData }: Route.ComponentProps) {\n\tconst { db, filepath } = loaderData\n\treturn (\n\t\t<main>\n\t\t\t<h1 className=\"text-2xl font-bold\">Database</h1>\n\t\t\t<div className=\"prose mt-4\">\n\t\t\t\t<callout-danger>\n\t\t\t\t\t<strong>Warning:</strong> Editing the database directly can corrupt\n\t\t\t\t\tyour data. Be very careful!\n\t\t\t\t</callout-danger>\n\t\t\t</div>\n\t\t\t<LaunchEditor\n\t\t\t\tfile={filepath}\n\t\t\t\tclassName=\"flex max-w-full items-center gap-2\"\n\t\t\t>\n\t\t\t\t<SimpleTooltip content=\"Open the database file in your editor\">\n\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" title=\"Open in editor\" />\n\t\t\t\t</SimpleTooltip>\n\t\t\t\t<span className=\"truncate\" title={filepath}>\n\t\t\t\t\t{filepath}\n\t\t\t\t</span>\n\t\t\t</LaunchEditor>\n\t\t\t<pre className=\"bg-muted text-foreground mt-4 max-h-[400px] w-full overflow-auto rounded border p-4 text-sm\">\n\t\t\t\t{JSON.stringify(db, null, 2)}\n\t\t\t</pre>\n\t\t</main>\n\t)\n}\n"],"names":["db","_UNSAFE_withComponentProps","loaderData","filepath","children","jsx","className","jsxs","LaunchEditor","file","SimpleTooltip","content","Icon","name","title","stringify"],"mappings":"sXAgBA,MAAAA,EAAAC,EAAA,SAAgC,CAAEC,WAAAA,CAAW,EAAyB,CACrE,KAAM,CAAEF,GAAAA,EAAIG,SAAAA,CAAS,EAAID,EACzB,cACE,OAAA,CACAE,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAGC,UAAU,qBAAqBF,SAAA,UAAA,CAAQ,EAC3CC,EAAAA,IAAC,MAAA,CAAIC,UAAU,aACdF,gBAAC,iBAAA,CACAA,SAAA,CAAAC,EAAAA,IAAC,UAAOD,SAAA,WAAQ,EAAS,wEAAA,EAE1B,CAAA,CACD,EACAG,EAAAA,KAACC,EAAA,CACAC,KAAMN,EACNG,UAAU,qCAEVF,SAAA,CAAAC,EAAAA,IAACK,EAAA,CAAcC,QAAQ,wCACtBP,SAAAC,EAAAA,IAACO,EAAA,CAAKC,KAAK,QAAQP,UAAU,UAAUQ,MAAM,iBAAiB,CAAA,CAC/D,QACC,OAAA,CAAKR,UAAU,WAAWQ,MAAOX,EAChCC,SAAAD,CAAA,CACF,CAAA,CAAA,CACD,EACAE,EAAAA,IAAC,OAAIC,UAAU,8FACbF,cAAKW,UAAUf,EAAI,KAAM,CAAC,CAAA,CAC5B,CAAA,CAAA,CACD,CAEF,CAAA"}
1
+ {"version":3,"file":"db-n83JPsm9.js","sources":["../../../app/routes/admin+/db.tsx"],"sourcesContent":["import { loadJSON } from '@epic-web/workshop-utils/data-storage.server'\nimport { readDb } from '@epic-web/workshop-utils/db.server'\nimport { Icon } from '#app/components/icons.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { ensureUndeployed } from '#app/utils/misc.tsx'\nimport { LaunchEditor } from '../launch-editor.tsx'\nimport { type Route } from './+types/db.ts'\n\nexport async function loader() {\n\tensureUndeployed()\n\tconst db = await readDb()\n\tconst { path: filepath } = await loadJSON()\n\n\treturn { db, filepath }\n}\n\nexport default function DbRoute({ loaderData }: Route.ComponentProps) {\n\tconst { db, filepath } = loaderData\n\treturn (\n\t\t<main>\n\t\t\t<h1 className=\"text-2xl font-bold\">Database</h1>\n\t\t\t<div className=\"prose mt-4\">\n\t\t\t\t<callout-danger>\n\t\t\t\t\t<strong>Warning:</strong> Editing the database directly can corrupt\n\t\t\t\t\tyour data. Be very careful!\n\t\t\t\t</callout-danger>\n\t\t\t</div>\n\t\t\t<LaunchEditor\n\t\t\t\tfile={filepath}\n\t\t\t\tclassName=\"flex max-w-full items-center gap-2\"\n\t\t\t>\n\t\t\t\t<SimpleTooltip content=\"Open the database file in your editor\">\n\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" title=\"Open in editor\" />\n\t\t\t\t</SimpleTooltip>\n\t\t\t\t<span className=\"truncate\" title={filepath}>\n\t\t\t\t\t{filepath}\n\t\t\t\t</span>\n\t\t\t</LaunchEditor>\n\t\t\t<pre className=\"bg-muted text-foreground mt-4 max-h-[400px] w-full overflow-auto rounded border p-4 text-sm\">\n\t\t\t\t{JSON.stringify(db, null, 2)}\n\t\t\t</pre>\n\t\t</main>\n\t)\n}\n"],"names":["db","_UNSAFE_withComponentProps","loaderData","filepath","children","jsx","className","jsxs","LaunchEditor","file","SimpleTooltip","content","Icon","name","title","stringify"],"mappings":"sXAgBA,MAAAA,EAAAC,EAAA,SAAgC,CAAEC,WAAAA,CAAW,EAAyB,CACrE,KAAM,CAAEF,GAAAA,EAAIG,SAAAA,CAAS,EAAID,EACzB,cACE,OAAA,CACAE,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAGC,UAAU,qBAAqBF,SAAA,UAAA,CAAQ,EAC3CC,EAAAA,IAAC,MAAA,CAAIC,UAAU,aACdF,gBAAC,iBAAA,CACAA,SAAA,CAAAC,EAAAA,IAAC,UAAOD,SAAA,WAAQ,EAAS,wEAAA,EAE1B,CAAA,CACD,EACAG,EAAAA,KAACC,EAAA,CACAC,KAAMN,EACNG,UAAU,qCAEVF,SAAA,CAAAC,EAAAA,IAACK,EAAA,CAAcC,QAAQ,wCACtBP,SAAAC,EAAAA,IAACO,EAAA,CAAKC,KAAK,QAAQP,UAAU,UAAUQ,MAAM,iBAAiB,CAAA,CAC/D,QACC,OAAA,CAAKR,UAAU,WAAWQ,MAAOX,EAChCC,SAAAD,CAAA,CACF,CAAA,CAAA,CACD,EACAE,EAAAA,IAAC,OAAIC,UAAU,8FACbF,cAAKW,UAAUf,EAAI,KAAM,CAAC,CAAA,CAC5B,CAAA,CAAA,CACD,CAEF,CAAA"}