Просмотр исходного кода

feat: enable type-aware no-base-to-string rule, fix 56 violations (#22750)

Kit Langton 4 месяцев назад
Родитель
Сommit
8aa0f9fe95
26 измененных файлов с 87 добавлено и 55 удалено
  1. 4 0
      .oxlintrc.json
  2. 2 2
      packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
  3. 2 2
      packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
  4. 11 11
      packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
  5. 2 2
      packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
  6. 4 4
      packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
  7. 11 11
      packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
  8. 4 4
      packages/console/app/src/routes/workspace/[id]/model-section.tsx
  9. 10 7
      packages/console/app/src/routes/workspace/[id]/provider-section.tsx
  10. 2 2
      packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
  11. 1 1
      packages/opencode/script/schema.ts
  12. 2 0
      packages/opencode/src/effect/logger.ts
  13. 1 1
      packages/opencode/src/plugin/github-copilot/copilot.ts
  14. 2 2
      packages/opencode/src/provider/transform.ts
  15. 1 1
      packages/opencode/src/pty/service.ts
  16. 1 1
      packages/opencode/src/snapshot/snapshot.ts
  17. 9 1
      packages/opencode/src/tool/apply_patch.ts
  18. 1 0
      packages/opencode/src/util/error.ts
  19. 1 0
      packages/opencode/src/v2/session-entry.ts
  20. 2 2
      packages/opencode/test/config/config.test.ts
  21. 1 0
      packages/shared/src/util/retry.ts
  22. 1 0
      packages/shared/test/util/effect-flock.test.ts
  23. 1 0
      packages/storybook/.storybook/mocks/app/context/language.ts
  24. 3 0
      packages/ui/src/components/file.tsx
  25. 1 0
      packages/ui/src/components/session-turn.tsx
  26. 7 1
      packages/web/src/components/share/part.tsx

+ 4 - 0
.oxlintrc.json

@@ -1,9 +1,13 @@
 {
   "$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
+  "options": {
+    "typeAware": true
+  },
   "categories": {
     "suspicious": "warn"
   },
   "rules": {
+    "typescript/no-base-to-string": "warn",
     // Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
     "require-yield": "off",
     // SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime

+ 2 - 2
packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx

@@ -116,9 +116,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
 
 const setUseBalance = action(async (form: FormData) => {
   "use server"
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
-  const useBalance = form.get("useBalance")?.toString() === "true"
+  const useBalance = (form.get("useBalance") as string | null) === "true"
 
   return json(
     await withActor(async () => {

+ 2 - 2
packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx

@@ -10,11 +10,11 @@ import { formError, localizeError } from "~/lib/form-error"
 
 const setMonthlyLimit = action(async (form: FormData) => {
   "use server"
-  const limit = form.get("limit")?.toString()
+  const limit = form.get("limit") as string | null
   if (!limit) return { error: formError.limitRequired }
   const numericLimit = parseInt(limit)
   if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(
     await withActor(

+ 11 - 11
packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx

@@ -12,7 +12,7 @@ import { formError, formErrorReloadAmountMin, formErrorReloadTriggerMin, localiz
 
 const reload = action(async (form: FormData) => {
   "use server"
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(await withActor(() => Billing.reload(), workspaceID), {
     revalidate: queryBillingInfo.key,
@@ -21,11 +21,11 @@ const reload = action(async (form: FormData) => {
 
 const setReload = action(async (form: FormData) => {
   "use server"
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
-  const reloadValue = form.get("reload")?.toString() === "true"
-  const amountStr = form.get("reloadAmount")?.toString()
-  const triggerStr = form.get("reloadTrigger")?.toString()
+  const reloadValue = (form.get("reload") as string | null) === "true"
+  const amountStr = form.get("reloadAmount") as string | null
+  const triggerStr = form.get("reloadTrigger") as string | null
 
   const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
   const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
@@ -91,8 +91,8 @@ export function ReloadSection() {
     const info = billingInfo()!
     setStore("show", true)
     setStore("reload", true)
-    setStore("reloadAmount", info.reloadAmount.toString())
-    setStore("reloadTrigger", info.reloadTrigger.toString())
+    setStore("reloadAmount", String(info.reloadAmount))
+    setStore("reloadTrigger", String(info.reloadTrigger))
   }
 
   function hide() {
@@ -152,11 +152,11 @@ export function ReloadSection() {
                 data-component="input"
                 name="reloadAmount"
                 type="number"
-                min={billingInfo()?.reloadAmountMin.toString()}
+                min={String(billingInfo()?.reloadAmountMin ?? "")}
                 step="1"
                 value={store.reloadAmount}
                 onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
-                placeholder={billingInfo()?.reloadAmount.toString()}
+                placeholder={String(billingInfo()?.reloadAmount ?? "")}
                 disabled={!store.reload}
               />
             </div>
@@ -166,11 +166,11 @@ export function ReloadSection() {
                 data-component="input"
                 name="reloadTrigger"
                 type="number"
-                min={billingInfo()?.reloadTriggerMin.toString()}
+                min={String(billingInfo()?.reloadTriggerMin ?? "")}
                 step="1"
                 value={store.reloadTrigger}
                 onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
-                placeholder={billingInfo()?.reloadTrigger.toString()}
+                placeholder={String(billingInfo()?.reloadTrigger ?? "")}
                 disabled={!store.reload}
               />
             </div>

+ 2 - 2
packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx

@@ -120,9 +120,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
 
 const setLiteUseBalance = action(async (form: FormData) => {
   "use server"
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
-  const useBalance = form.get("useBalance")?.toString() === "true"
+  const useBalance = (form.get("useBalance") as string | null) === "true"
 
   return json(
     await withActor(async () => {

+ 4 - 4
packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx

@@ -12,18 +12,18 @@ import { formError, localizeError } from "~/lib/form-error"
 
 const removeKey = action(async (form: FormData) => {
   "use server"
-  const id = form.get("id")?.toString()
+  const id = form.get("id") as string | null
   if (!id) return { error: formError.idRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
 }, "key.remove")
 
 const createKey = action(async (form: FormData) => {
   "use server"
-  const name = form.get("name")?.toString().trim()
+  const name = (form.get("name") as string | null)?.trim()
   if (!name) return { error: formError.nameRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(
     await withActor(

+ 11 - 11
packages/console/app/src/routes/workspace/[id]/members/member-section.tsx

@@ -24,13 +24,13 @@ const listMembers = query(async (workspaceID: string) => {
 
 const inviteMember = action(async (form: FormData) => {
   "use server"
-  const email = form.get("email")?.toString().trim()
+  const email = (form.get("email") as string | null)?.trim()
   if (!email) return { error: formError.emailRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
-  const role = form.get("role")?.toString() as (typeof UserRole)[number]
+  const role = form.get("role") as (typeof UserRole)[number] | null
   if (!role) return { error: formError.roleRequired }
-  const limit = form.get("limit")?.toString()
+  const limit = form.get("limit") as string | null
   const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
   if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
   return json(
@@ -47,9 +47,9 @@ const inviteMember = action(async (form: FormData) => {
 
 const removeMember = action(async (form: FormData) => {
   "use server"
-  const id = form.get("id")?.toString()
+  const id = form.get("id") as string | null
   if (!id) return { error: formError.idRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(
     await withActor(
@@ -66,13 +66,13 @@ const removeMember = action(async (form: FormData) => {
 const updateMember = action(async (form: FormData) => {
   "use server"
 
-  const id = form.get("id")?.toString()
+  const id = form.get("id") as string | null
   if (!id) return { error: formError.idRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
-  const role = form.get("role")?.toString() as (typeof UserRole)[number]
+  const role = form.get("role") as (typeof UserRole)[number] | null
   if (!role) return { error: formError.roleRequired }
-  const limit = form.get("limit")?.toString()
+  const limit = form.get("limit") as string | null
   const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
   if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
 
@@ -118,7 +118,7 @@ function MemberRow(props: {
     }
     setStore("editing", true)
     setStore("selectedRole", props.member.role)
-    setStore("limit", props.member.monthlyLimit?.toString() ?? "")
+    setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "")
   }
 
   function hide() {

+ 4 - 4
packages/console/app/src/routes/workspace/[id]/model-section.tsx

@@ -67,11 +67,11 @@ const getModelsInfo = query(async (workspaceID: string) => {
 
 const updateModel = action(async (form: FormData) => {
   "use server"
-  const model = form.get("model")?.toString()
+  const model = form.get("model") as string | null
   if (!model) return { error: formError.modelRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
-  const enabled = form.get("enabled")?.toString() === "true"
+  const enabled = (form.get("enabled") as string | null) === "true"
   return json(
     withActor(async () => {
       if (enabled) {
@@ -163,7 +163,7 @@ export function ModelSection() {
                           <form action={updateModel} method="post">
                             <input type="hidden" name="model" value={id} />
                             <input type="hidden" name="workspaceID" value={params.id} />
-                            <input type="hidden" name="enabled" value={isEnabled().toString()} />
+                            <input type="hidden" name="enabled" value={String(isEnabled())} />
                             <label data-slot="model-toggle-label">
                               <input
                                 type="checkbox"

+ 10 - 7
packages/console/app/src/routes/workspace/[id]/provider-section.tsx

@@ -21,9 +21,9 @@ function maskCredentials(credentials: string) {
 
 const removeProvider = action(async (form: FormData) => {
   "use server"
-  const provider = form.get("provider")?.toString()
+  const provider = form.get("provider") as string | null
   if (!provider) return { error: formError.providerRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
     revalidate: listProviders.key,
@@ -32,11 +32,11 @@ const removeProvider = action(async (form: FormData) => {
 
 const saveProvider = action(async (form: FormData) => {
   "use server"
-  const provider = form.get("provider")?.toString()
-  const credentials = form.get("credentials")?.toString()
+  const provider = form.get("provider") as string | null
+  const credentials = form.get("credentials") as string | null
   if (!provider) return { error: formError.providerRequired }
   if (!credentials) return { error: formError.apiKeyRequired }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(
     await withActor(
@@ -59,10 +59,13 @@ function ProviderRow(props: { provider: Provider }) {
   const params = useParams()
   const i18n = useI18n()
   const providers = createAsync(() => listProviders(params.id!))
-  const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
+  const saveSubmission = useSubmission(
+    saveProvider,
+    ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
+  )
   const removeSubmission = useSubmission(
     removeProvider,
-    ([fd]) => fd.get("provider")?.toString() === props.provider.key,
+    ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
   )
   const [store, setStore] = createStore({ editing: false })
 

+ 2 - 2
packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx

@@ -30,10 +30,10 @@ const getWorkspaceInfo = query(async (workspaceID: string) => {
 
 const updateWorkspace = action(async (form: FormData) => {
   "use server"
-  const name = form.get("name")?.toString().trim()
+  const name = (form.get("name") as string | null)?.trim()
   if (!name) return { error: formError.workspaceNameRequired }
   if (name.length > 255) return { error: formError.nameTooLong }
-  const workspaceID = form.get("workspaceID")?.toString()
+  const workspaceID = form.get("workspaceID") as string | null
   if (!workspaceID) return { error: formError.workspaceRequired }
   return json(
     await withActor(

+ 1 - 1
packages/opencode/script/schema.ts

@@ -33,7 +33,7 @@ function generate(schema: z.ZodType) {
           schema.examples = [schema.default]
         }
 
-        schema.description = [schema.description || "", `default: \`${schema.default}\``]
+        schema.description = [schema.description || "", `default: \`${String(schema.default)}\``]
           .filter(Boolean)
           .join("\n\n")
           .trim()

+ 2 - 0
packages/opencode/src/effect/logger.ts

@@ -15,7 +15,9 @@ const clean = (input?: Fields): Fields =>
   Object.fromEntries(Object.entries(input ?? {}).filter((entry) => entry[1] !== undefined && entry[1] !== null))
 
 const text = (input: unknown): string => {
+  // oxlint-disable-next-line no-base-to-string
   if (Array.isArray(input)) return input.map((item) => String(item)).join(" ")
+  // oxlint-disable-next-line no-base-to-string
   return input === undefined ? "" : String(input)
 }
 

+ 1 - 1
packages/opencode/src/plugin/github-copilot/copilot.ts

@@ -93,7 +93,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
             const info = await getAuth()
             if (info.type !== "oauth") return fetch(request, init)
 
-            const url = request instanceof URL ? request.href : request.toString()
+            const url = request instanceof URL ? request.href : typeof request === "string" ? request : request.url
             const { isVision, isAgent } = iife(() => {
               try {
                 const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body

+ 2 - 2
packages/opencode/src/provider/transform.ts

@@ -274,7 +274,7 @@ export namespace ProviderTransform {
 
         // Check for empty base64 image data
         if (part.type === "image") {
-          const imageStr = part.image.toString()
+          const imageStr = String(part.image)
           if (imageStr.startsWith("data:")) {
             const match = imageStr.match(/^data:([^;]+);base64,(.*)$/)
             if (match && (!match[2] || match[2].length === 0)) {
@@ -286,7 +286,7 @@ export namespace ProviderTransform {
           }
         }
 
-        const mime = part.type === "image" ? part.image.toString().split(";")[0].replace("data:", "") : part.mediaType
+        const mime = part.type === "image" ? String(part.image).split(";")[0].replace("data:", "") : part.mediaType
         const filename = part.type === "file" ? part.filename : undefined
         const modality = mimeToModality(mime)
         if (!modality) return part

+ 1 - 1
packages/opencode/src/pty/service.ts

@@ -346,7 +346,7 @@ export const layer = Layer.effect(
 
       return {
         onMessage: (message: string | ArrayBuffer) => {
-          session.process.write(String(message))
+          session.process.write(typeof message === "string" ? message : new TextDecoder().decode(message))
         },
         onClose: () => {
           log.info("client disconnected from session", { id })

+ 1 - 1
packages/opencode/src/snapshot/snapshot.ts

@@ -116,7 +116,7 @@ export const layer: Layer.Layer<
             Effect.succeed({
               code: ChildProcessSpawner.ExitCode(1),
               text: "",
-              stderr: String(err),
+              stderr: err instanceof Error ? err.message : String(err),
             }),
           ),
         )

+ 9 - 1
packages/opencode/src/tool/apply_patch.ts

@@ -145,7 +145,15 @@ export const ApplyPatchTool = Tool.define(
           case "delete": {
             const contentToDelete = yield* afs
               .readFileString(filePath)
-              .pipe(Effect.catch((error) => Effect.fail(new Error(`apply_patch verification failed: ${error}`))))
+              .pipe(
+                Effect.catch((error) =>
+                  Effect.fail(
+                    new Error(
+                      `apply_patch verification failed: ${error instanceof Error ? error.message : String(error)}`,
+                    ),
+                  ),
+                ),
+              )
             const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, ""))
 
             const deletions = contentToDelete.split("\n").length

+ 1 - 0
packages/opencode/src/util/error.ts

@@ -60,6 +60,7 @@ export function errorData(error: unknown) {
       acc[key] = value
       return acc
     }
+    // oxlint-disable-next-line no-base-to-string -- intentional coercion of arbitrary error properties
     acc[key] = value instanceof Error ? value.message : String(value)
     return acc
   }, {})

+ 1 - 0
packages/opencode/src/v2/session-entry.ts

@@ -202,6 +202,7 @@ export namespace SessionEntry {
         case "tool.input.delta": {
           if (!pendingAssistant) break
           const match = pendingAssistant.content.findLast((x) => x.type === "tool")
+          // oxlint-disable-next-line no-base-to-string -- event.delta is a Schema.String (runtime string)
           if (match) match.state.input += event.delta
           break
         }

+ 2 - 2
packages/opencode/test/config/config.test.ts

@@ -1801,7 +1801,7 @@ test("project config overrides remote well-known config", async () => {
   const originalFetch = globalThis.fetch
   let fetchedUrl: string | undefined
   globalThis.fetch = mock((url: string | URL | Request) => {
-    const urlStr = url.toString()
+    const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
     if (urlStr.includes(".well-known/opencode")) {
       fetchedUrl = urlStr
       return Promise.resolve(
@@ -1858,7 +1858,7 @@ test("wellknown URL with trailing slash is normalized", async () => {
   const originalFetch = globalThis.fetch
   let fetchedUrl: string | undefined
   globalThis.fetch = mock((url: string | URL | Request) => {
-    const urlStr = url.toString()
+    const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
     if (urlStr.includes(".well-known/opencode")) {
       fetchedUrl = urlStr
       return Promise.resolve(

+ 1 - 0
packages/shared/src/util/retry.ts

@@ -19,6 +19,7 @@ const TRANSIENT_MESSAGES = [
 
 function isTransientError(error: unknown): boolean {
   if (!error) return false
+  // oxlint-disable-next-line no-base-to-string -- error is unknown, intentional coercion for message matching
   const message = String(error instanceof Error ? error.message : error).toLowerCase()
   return TRANSIENT_MESSAGES.some((m) => message.includes(m))
 }

+ 1 - 0
packages/shared/test/util/effect-flock.test.ts

@@ -317,6 +317,7 @@ describe("util.effect-flock", () => {
       })
 
       const result = yield* flock.withLock(Effect.void, "eflock:perm", dir).pipe(Effect.exit)
+      // oxlint-disable-next-line no-base-to-string -- Exit has a useful toString for test assertions
       expect(String(result)).toContain("PermissionDenied")
       yield* Effect.promise(() => fs.chmod(dir, 0o700).then(() => fs.rm(tmp, { recursive: true, force: true })))
     }),

+ 1 - 0
packages/storybook/.storybook/mocks/app/context/language.ts

@@ -60,6 +60,7 @@ function render(template: string, params?: Record<string, unknown>) {
   return template.replace(/\{\{([^}]+)\}\}/g, (_, key: string) => {
     const value = params[key.trim()]
     if (value === undefined || value === null) return ""
+    // oxlint-disable-next-line no-base-to-string -- value is Record<string, unknown>, always coerced intentionally
     return String(value)
   })
 }

+ 3 - 0
packages/ui/src/components/file.tsx

@@ -698,6 +698,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
     if (typeof value === "string") return value
     if (Array.isArray(value)) return value.join("\n")
     if (value == null) return ""
+    // oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
     return String(value)
   }
 
@@ -712,11 +713,13 @@ function TextViewer<T>(props: TextFileProps<T>) {
     if (typeof value === "string") return value.length
     if (Array.isArray(value)) {
       return value.reduce(
+        // oxlint-disable-next-line no-base-to-string -- array parts coerced intentionally
         (sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
         0,
       )
     }
     if (value == null) return 0
+    // oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
     return String(value).length
   })
 

+ 1 - 0
packages/ui/src/components/session-turn.tsx

@@ -313,6 +313,7 @@ export function SessionTurn(
     const msg = error()?.data?.message
     if (typeof msg === "string") return unwrap(msg)
     if (msg === undefined || msg === null) return ""
+    // oxlint-disable-next-line no-base-to-string -- msg is unknown from error data, coercion is intentional
     return unwrap(String(msg))
   })
 

+ 7 - 1
packages/web/src/components/share/part.tsx

@@ -730,7 +730,13 @@ export function FallbackTool(props: ToolProps) {
             <>
               <div></div>
               <div>{arg[0]}</div>
-              <div>{String(arg[1] ?? "")}</div>
+              <div>
+                {typeof arg[1] === "string" || typeof arg[1] === "number" || typeof arg[1] === "boolean"
+                  ? String(arg[1])
+                  : arg[1] == null
+                    ? ""
+                    : JSON.stringify(arg[1])}
+              </div>
             </>
           )}
         </For>