diff options
| author | Aman pandit <[email protected]> | 2025-12-08 11:01:30 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-07 21:31:30 -0800 |
| commit | 08267ffa218fdc77e3b10920e5c40fec99e9d325 (patch) | |
| tree | f05802225f42091fcfa9b5e3671a04714b806543 /apps | |
| parent | add(docs): connections configure, fetch resources (#611) (diff) | |
| download | supermemory-08267ffa218fdc77e3b10920e5c40fec99e9d325.tar.xz supermemory-08267ffa218fdc77e3b10920e5c40fec99e9d325.zip | |
fix: resolve UI accessibility, hydration, and semantic HTML issues (#520)
Co-authored-by: Mahesh Sanikommu <[email protected]>
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/app/global-error.tsx | 38 | ||||
| -rw-r--r-- | apps/web/app/layout.tsx | 5 | ||||
| -rw-r--r-- | apps/web/components/graph-dialog.tsx | 3 | ||||
| -rw-r--r-- | apps/web/components/text-effect.tsx | 500 | ||||
| -rw-r--r-- | apps/web/components/views/add-memory/index.tsx | 21 | ||||
| -rw-r--r-- | apps/web/components/views/chat/index.tsx | 67 | ||||
| -rw-r--r-- | apps/web/globals.css | 1 | ||||
| -rw-r--r-- | apps/web/middleware.ts | 1 |
8 files changed, 322 insertions, 314 deletions
diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx index 9bda5fee..e586eac7 100644 --- a/apps/web/app/global-error.tsx +++ b/apps/web/app/global-error.tsx @@ -1,23 +1,27 @@ -"use client"; +"use client" -import * as Sentry from "@sentry/nextjs"; -import NextError from "next/error"; -import { useEffect } from "react"; +import * as Sentry from "@sentry/nextjs" +import NextError from "next/error" +import { useEffect } from "react" -export default function GlobalError({ error }: { error: Error & { digest?: string } }) { - useEffect(() => { - Sentry.captureException(error); - }, [error]); +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string } +}) { + useEffect(() => { + Sentry.captureException(error) + }, [error]) - return ( - <html> - <body> - {/* `NextError` is the default Next.js error page component. Its type + return ( + <html lang="en"> + <body> + {/* `NextError` is the default Next.js error page component. Its type definition requires a `statusCode` prop. However, since the App Router does not expose status codes for errors, we simply pass 0 to render a generic error message. */} - <NextError statusCode={0} /> - </body> - </html> - ); -}
\ No newline at end of file + <NextError statusCode={0} /> + </body> + </html> + ) +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 622eb2c1..707e4185 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -33,7 +33,10 @@ export default function RootLayout({ }>) { return ( <html lang="en" suppressHydrationWarning> - <body className={`${font.variable} antialiased overflow-x-hidden`}> + <body + className={`${font.variable} antialiased overflow-x-hidden`} + suppressHydrationWarning + > <ThemeProvider attribute="class" defaultTheme="system" diff --git a/apps/web/components/graph-dialog.tsx b/apps/web/components/graph-dialog.tsx index 8849d4ce..ef291d69 100644 --- a/apps/web/components/graph-dialog.tsx +++ b/apps/web/components/graph-dialog.tsx @@ -7,7 +7,7 @@ import { useInfiniteQuery } from "@tanstack/react-query" import { useCallback, useEffect, useMemo, useState } from "react" import type { z } from "zod" import { MemoryGraph } from "@repo/ui/memory-graph" -import { Dialog, DialogContent } from "@repo/ui/components/dialog" +import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog" import { ConnectAIModal } from "@/components/connect-ai-modal" import { AddMemoryView } from "@/components/views/add-memory" import { useChatOpen, useProject, useGraphModal } from "@/stores" @@ -173,6 +173,7 @@ export function GraphDialog() { className="w-[95vw] h-[95vh] p-0 max-w-6xl sm:max-w-6xl" showCloseButton={true} > + <DialogTitle className="sr-only">Memory Graph</DialogTitle> <div className="w-full h-full"> <MemoryGraph documents={allDocuments} diff --git a/apps/web/components/text-effect.tsx b/apps/web/components/text-effect.tsx index 82c6823c..0cbb67ce 100644 --- a/apps/web/components/text-effect.tsx +++ b/apps/web/components/text-effect.tsx @@ -1,294 +1,290 @@ -'use client'; -import { cn } from '@lib/utils'; -import { - AnimatePresence, - motion -} from 'motion/react'; +"use client" +import { cn } from "@lib/utils" +import { AnimatePresence, motion } from "motion/react" import type { - TargetAndTransition, - Transition, - Variant, - Variants, -} from 'motion/react' -import React from 'react'; + TargetAndTransition, + Transition, + Variant, + Variants, +} from "motion/react" +import React from "react" -export type PresetType = 'blur' | 'fade-in-blur' | 'scale' | 'fade' | 'slide'; +export type PresetType = "blur" | "fade-in-blur" | "scale" | "fade" | "slide" -export type PerType = 'word' | 'char' | 'line'; +export type PerType = "word" | "char" | "line" export type TextEffectProps = { - children: string; - per?: PerType; - as?: keyof React.JSX.IntrinsicElements; - variants?: { - container?: Variants; - item?: Variants; - }; - className?: string; - preset?: PresetType; - delay?: number; - speedReveal?: number; - speedSegment?: number; - trigger?: boolean; - onAnimationComplete?: () => void; - onAnimationStart?: () => void; - segmentWrapperClassName?: string; - containerTransition?: Transition; - segmentTransition?: Transition; - style?: React.CSSProperties; -}; + children: string + per?: PerType + as?: keyof React.JSX.IntrinsicElements + variants?: { + container?: Variants + item?: Variants + } + className?: string + preset?: PresetType + delay?: number + speedReveal?: number + speedSegment?: number + trigger?: boolean + onAnimationComplete?: () => void + onAnimationStart?: () => void + segmentWrapperClassName?: string + containerTransition?: Transition + segmentTransition?: Transition + style?: React.CSSProperties +} const defaultStaggerTimes: Record<PerType, number> = { - char: 0.03, - word: 0.05, - line: 0.1, -}; + char: 0.03, + word: 0.05, + line: 0.1, +} const defaultContainerVariants: Variants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { - staggerChildren: 0.05, - }, - }, - exit: { - transition: { staggerChildren: 0.05, staggerDirection: -1 }, - }, -}; + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.05, + }, + }, + exit: { + transition: { staggerChildren: 0.05, staggerDirection: -1 }, + }, +} const defaultItemVariants: Variants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - }, - exit: { opacity: 0 }, -}; + hidden: { opacity: 0 }, + visible: { + opacity: 1, + }, + exit: { opacity: 0 }, +} const presetVariants: Record< - PresetType, - { container: Variants; item: Variants } + PresetType, + { container: Variants; item: Variants } > = { - blur: { - container: defaultContainerVariants, - item: { - hidden: { opacity: 0, filter: 'blur(12px)' }, - visible: { opacity: 1, filter: 'blur(0px)' }, - exit: { opacity: 0, filter: 'blur(12px)' }, - }, - }, - 'fade-in-blur': { - container: defaultContainerVariants, - item: { - hidden: { opacity: 0, y: 20, filter: 'blur(12px)' }, - visible: { opacity: 1, y: 0, filter: 'blur(0px)' }, - exit: { opacity: 0, y: 20, filter: 'blur(12px)' }, - }, - }, - scale: { - container: defaultContainerVariants, - item: { - hidden: { opacity: 0, scale: 0 }, - visible: { opacity: 1, scale: 1 }, - exit: { opacity: 0, scale: 0 }, - }, - }, - fade: { - container: defaultContainerVariants, - item: { - hidden: { opacity: 0 }, - visible: { opacity: 1 }, - exit: { opacity: 0 }, - }, - }, - slide: { - container: defaultContainerVariants, - item: { - hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0 }, - exit: { opacity: 0, y: 20 }, - }, - }, -}; + blur: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, filter: "blur(12px)" }, + visible: { opacity: 1, filter: "blur(0px)" }, + exit: { opacity: 0, filter: "blur(12px)" }, + }, + }, + "fade-in-blur": { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, y: 20, filter: "blur(12px)" }, + visible: { opacity: 1, y: 0, filter: "blur(0px)" }, + exit: { opacity: 0, y: 20, filter: "blur(12px)" }, + }, + }, + scale: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, scale: 0 }, + visible: { opacity: 1, scale: 1 }, + exit: { opacity: 0, scale: 0 }, + }, + }, + fade: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + exit: { opacity: 0 }, + }, + }, + slide: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, y: 20 }, + visible: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 20 }, + }, + }, +} const AnimationComponent: React.FC<{ - segment: string; - variants: Variants; - per: 'line' | 'word' | 'char'; - segmentWrapperClassName?: string; + segment: string + variants: Variants + per: "line" | "word" | "char" + segmentWrapperClassName?: string }> = React.memo(({ segment, variants, per, segmentWrapperClassName }) => { - const content = - per === 'line' ? ( - <motion.span variants={variants} className='block'> - {segment} - </motion.span> - ) : per === 'word' ? ( - <motion.span - aria-hidden='true' - variants={variants} - className='inline-block whitespace-pre' - > - {segment} - </motion.span> - ) : ( - <motion.span className='inline-block whitespace-pre'> - {segment.split('').map((char, charIndex) => ( - <motion.span - key={`char-${charIndex}`} - aria-hidden='true' - variants={variants} - className='inline-block whitespace-pre' - > - {char} - </motion.span> - ))} - </motion.span> - ); + const content = + per === "line" ? ( + <motion.span variants={variants} className="block"> + {segment} + </motion.span> + ) : per === "word" ? ( + <motion.span + aria-hidden="true" + variants={variants} + className="inline-block whitespace-pre" + > + {segment} + </motion.span> + ) : ( + <motion.span className="inline-block whitespace-pre"> + {segment.split("").map((char, charIndex) => ( + <motion.span + // biome-ignore lint/suspicious/noArrayIndexKey: Character position is stable within animation + key={`char-${charIndex}`} + aria-hidden="true" + variants={variants} + className="inline-block whitespace-pre" + > + {char} + </motion.span> + ))} + </motion.span> + ) - if (!segmentWrapperClassName) { - return content; - } + if (!segmentWrapperClassName) { + return content + } - const defaultWrapperClassName = per === 'line' ? 'block' : 'inline-block'; + const defaultWrapperClassName = per === "line" ? "block" : "inline-block" - return ( - <span className={cn(defaultWrapperClassName, segmentWrapperClassName)}> - {content} - </span> - ); -}); + return ( + <span className={cn(defaultWrapperClassName, segmentWrapperClassName)}> + {content} + </span> + ) +}) -AnimationComponent.displayName = 'AnimationComponent'; +AnimationComponent.displayName = "AnimationComponent" const splitText = (text: string, per: PerType) => { - if (per === 'line') return text.split('\n'); - return text.split(/(\s+)/); -}; + if (per === "line") return text.split("\n") + return text.split(/(\s+)/) +} const hasTransition = ( - variant?: Variant + variant?: Variant, ): variant is TargetAndTransition & { transition?: Transition } => { - if (!variant) return false; - return ( - typeof variant === 'object' && 'transition' in variant - ); -}; + if (!variant) return false + return typeof variant === "object" && "transition" in variant +} const createVariantsWithTransition = ( - baseVariants: Variants, - transition?: Transition & { exit?: Transition } + baseVariants: Variants, + transition?: Transition & { exit?: Transition }, ): Variants => { - if (!transition) return baseVariants; + if (!transition) return baseVariants - const { exit: _, ...mainTransition } = transition; + const { exit: _, ...mainTransition } = transition - return { - ...baseVariants, - visible: { - ...baseVariants.visible, - transition: { - ...(hasTransition(baseVariants.visible) - ? baseVariants.visible.transition - : {}), - ...mainTransition, - }, - }, - exit: { - ...baseVariants.exit, - transition: { - ...(hasTransition(baseVariants.exit) - ? baseVariants.exit.transition - : {}), - ...mainTransition, - staggerDirection: -1, - }, - }, - }; -}; + return { + ...baseVariants, + visible: { + ...baseVariants.visible, + transition: { + ...(hasTransition(baseVariants.visible) + ? baseVariants.visible.transition + : {}), + ...mainTransition, + }, + }, + exit: { + ...baseVariants.exit, + transition: { + ...(hasTransition(baseVariants.exit) + ? baseVariants.exit.transition + : {}), + ...mainTransition, + staggerDirection: -1, + }, + }, + } +} export function TextEffect({ - children, - per = 'word', - as = 'p', - variants, - className, - preset = 'fade', - delay = 0, - speedReveal = 1, - speedSegment = 1, - trigger = true, - onAnimationComplete, - onAnimationStart, - segmentWrapperClassName, - containerTransition, - segmentTransition, - style, + children, + per = "word", + as = "p", + variants, + className, + preset = "fade", + delay = 0, + speedReveal = 1, + speedSegment = 1, + trigger = true, + onAnimationComplete, + onAnimationStart, + segmentWrapperClassName, + containerTransition, + segmentTransition, + style, }: TextEffectProps) { - const segments = splitText(children, per); - const MotionTag = motion[as as keyof typeof motion] as typeof motion.div; + const segments = splitText(children, per) + const MotionTag = motion[as as keyof typeof motion] as typeof motion.div - const baseVariants = preset - ? presetVariants[preset] - : { container: defaultContainerVariants, item: defaultItemVariants }; + const baseVariants = preset + ? presetVariants[preset] + : { container: defaultContainerVariants, item: defaultItemVariants } - const stagger = defaultStaggerTimes[per] / speedReveal; + const stagger = defaultStaggerTimes[per] / speedReveal - const baseDuration = 0.3 / speedSegment; + const baseDuration = 0.3 / speedSegment - const customStagger = hasTransition(variants?.container?.visible ?? {}) - ? (variants?.container?.visible as TargetAndTransition).transition - ?.staggerChildren - : undefined; + const customStagger = hasTransition(variants?.container?.visible ?? {}) + ? (variants?.container?.visible as TargetAndTransition).transition + ?.staggerChildren + : undefined - const customDelay = hasTransition(variants?.container?.visible ?? {}) - ? (variants?.container?.visible as TargetAndTransition).transition - ?.delayChildren - : undefined; + const customDelay = hasTransition(variants?.container?.visible ?? {}) + ? (variants?.container?.visible as TargetAndTransition).transition + ?.delayChildren + : undefined - const computedVariants = { - container: createVariantsWithTransition( - variants?.container || baseVariants.container, - { - staggerChildren: customStagger ?? stagger, - delayChildren: customDelay ?? delay, - ...containerTransition, - exit: { - staggerChildren: customStagger ?? stagger, - staggerDirection: -1, - }, - } - ), - item: createVariantsWithTransition(variants?.item || baseVariants.item, { - duration: baseDuration, - ...segmentTransition, - }), - }; + const computedVariants = { + container: createVariantsWithTransition( + variants?.container || baseVariants.container, + { + staggerChildren: customStagger ?? stagger, + delayChildren: customDelay ?? delay, + ...containerTransition, + exit: { + staggerChildren: customStagger ?? stagger, + staggerDirection: -1, + }, + }, + ), + item: createVariantsWithTransition(variants?.item || baseVariants.item, { + duration: baseDuration, + ...segmentTransition, + }), + } - return ( - <AnimatePresence mode='popLayout'> - {trigger && ( - <MotionTag - initial='hidden' - animate='visible' - exit='exit' - variants={computedVariants.container} - className={className} - onAnimationComplete={onAnimationComplete} - onAnimationStart={onAnimationStart} - style={style} - > - {per !== 'line' ? <span className='sr-only'>{children}</span> : null} - {segments.map((segment, index) => ( - <AnimationComponent - key={`${per}-${index}-${segment}`} - segment={segment} - variants={computedVariants.item} - per={per} - segmentWrapperClassName={segmentWrapperClassName} - /> - ))} - </MotionTag> - )} - </AnimatePresence> - ); + return ( + <AnimatePresence mode="popLayout"> + {trigger && ( + <MotionTag + initial="hidden" + animate="visible" + exit="exit" + variants={computedVariants.container} + className={className} + onAnimationComplete={onAnimationComplete} + onAnimationStart={onAnimationStart} + style={style} + > + {per !== "line" ? <span className="sr-only">{children}</span> : null} + {segments.map((segment, index) => ( + <AnimationComponent + key={`${per}-${index}-${segment}`} + segment={segment} + variants={computedVariants.item} + per={per} + segmentWrapperClassName={segmentWrapperClassName} + /> + ))} + </MotionTag> + )} + </AnimatePresence> + ) } diff --git a/apps/web/components/views/add-memory/index.tsx b/apps/web/components/views/add-memory/index.tsx index f7940bce..4e250b53 100644 --- a/apps/web/components/views/add-memory/index.tsx +++ b/apps/web/components/views/add-memory/index.tsx @@ -361,7 +361,7 @@ export function AddMemoryView({ return { previousMemories, optimisticId: optimisticMemory.id } }, // If the mutation fails, roll back to the previous value - onError: (error, variables, context) => { + onError: (_error, variables, context) => { if (context?.previousMemories) { queryClient.setQueryData( ["documents-with-memories", variables.project], @@ -551,6 +551,7 @@ export function AddMemoryView({ className="w-[100vw] max-w-4xl sm:max-w-4xl backdrop-blur-xl border-white/10 z-[80] h-[52vh] overflow-y-auto p-4" showCloseButton={false} > + <DialogTitle className="sr-only">Add Memory</DialogTitle> <Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as typeof activeTab)} @@ -638,9 +639,13 @@ export function AddMemoryView({ > {({ state, handleChange, handleBlur }) => ( <> - <div className={addContentMutation.isPending ? "opacity-50" : ""}> - <TextEditor - containerClassName="bg-white/5 border-white/10 rounded-md" + <div + className={ + addContentMutation.isPending ? "opacity-50" : "" + } + > + <TextEditor + containerClassName="bg-white/5 border-white/10 rounded-md" disabled={addContentMutation.isPending} onBlur={handleBlur} onChange={handleChange} @@ -843,14 +848,14 @@ export function AddMemoryView({ </TabsContent> <TabsContent value="file" className="space-y-4"> - <form + <form onSubmit={(e) => { e.preventDefault() e.stopPropagation() fileUploadForm.handleSubmit() }} - className="h-full flex flex-col" - > + className="h-full flex flex-col" + > <div className="grid gap-4"> <motion.div animate={{ opacity: 1, y: 0 }} @@ -938,7 +943,7 @@ export function AddMemoryView({ </fileUploadForm.Field> </motion.div> </div> - <div className="mt-6 flex flex-col sm:flex-row sm:justify-between sm:items-end w-full gap-4 mt-auto"> + <div className="flex flex-col sm:flex-row sm:justify-between sm:items-end w-full gap-4 mt-auto"> <div className="flex items-end gap-4"> {/* Left side - Project Selection */} <motion.div diff --git a/apps/web/components/views/chat/index.tsx b/apps/web/components/views/chat/index.tsx index 2b7befa1..6fabadc8 100644 --- a/apps/web/components/views/chat/index.tsx +++ b/apps/web/components/views/chat/index.tsx @@ -1,7 +1,7 @@ -"use client"; +"use client" -import { cn } from "@lib/utils"; -import { Button } from "@ui/components/button"; +import { cn } from "@lib/utils" +import { Button } from "@ui/components/button" import { Dialog, DialogContent, @@ -9,38 +9,38 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@ui/components/dialog"; -import { ScrollArea } from "@ui/components/scroll-area"; -import { formatDistanceToNow } from "date-fns"; -import { HistoryIcon, Plus, Trash2, X } from "lucide-react"; -import { useMemo, useState } from "react"; -import { analytics } from "@/lib/analytics"; -import { useChatOpen, usePersistentChat, useProject } from "@/stores"; -import { ChatMessages } from "./chat-messages"; +} from "@ui/components/dialog" +import { ScrollArea } from "@ui/components/scroll-area" +import { formatDistanceToNow } from "date-fns" +import { HistoryIcon, Plus, Trash2, X } from "lucide-react" +import { useMemo, useState } from "react" +import { analytics } from "@/lib/analytics" +import { useChatOpen, usePersistentChat, useProject } from "@/stores" +import { ChatMessages } from "./chat-messages" export function ChatRewrite() { - const { setIsOpen } = useChatOpen(); - const { selectedProject } = useProject(); + const { setIsOpen } = useChatOpen() + const { selectedProject } = useProject() const { conversations, currentChatId, setCurrentChatId, getCurrentChat } = - usePersistentChat(); + usePersistentChat() - const [isDialogOpen, setIsDialogOpen] = useState(false); + const [isDialogOpen, setIsDialogOpen] = useState(false) const sorted = useMemo(() => { return [...conversations].sort((a, b) => a.lastUpdated < b.lastUpdated ? 1 : -1, - ); - }, [conversations]); + ) + }, [conversations]) function handleNewChat() { - analytics.newChatStarted(); - const newId = crypto.randomUUID(); - setCurrentChatId(newId); - setIsDialogOpen(false); + analytics.newChatStarted() + const newId = crypto.randomUUID() + setCurrentChatId(newId) + setIsDialogOpen(false) } function formatRelativeTime(isoString: string): string { - return formatDistanceToNow(new Date(isoString), { addSuffix: true }); + return formatDistanceToNow(new Date(isoString), { addSuffix: true }) } return ( @@ -74,18 +74,17 @@ export function ChatRewrite() { <ScrollArea className="max-h-96"> <div className="flex flex-col gap-1"> {sorted.map((c) => { - const isActive = c.id === currentChatId; + const isActive = c.id === currentChatId return ( - <div + <button + type="button" key={c.id} - role="button" - tabIndex={0} onClick={() => { - setCurrentChatId(c.id); - setIsDialogOpen(false); + setCurrentChatId(c.id) + setIsDialogOpen(false) }} className={cn( - "flex items-center justify-between rounded-md px-3 py-2 outline-none", + "flex items-center justify-between rounded-md px-3 py-2 outline-none w-full text-left", "transition-colors", isActive ? "bg-primary/10" : "hover:bg-muted", )} @@ -111,15 +110,15 @@ export function ChatRewrite() { variant="ghost" size="icon" onClick={(e) => { - e.stopPropagation(); - analytics.chatDeleted(); + e.stopPropagation() + analytics.chatDeleted() }} aria-label="Delete conversation" > <Trash2 className="size-4 text-muted-foreground" /> </Button> - </div> - ); + </button> + ) })} {sorted.length === 0 && ( <div className="text-xs text-muted-foreground px-3 py-2"> @@ -152,5 +151,5 @@ export function ChatRewrite() { </div> <ChatMessages /> </div> - ); + ) } diff --git a/apps/web/globals.css b/apps/web/globals.css index 2c150cf0..f95da3a2 100644 --- a/apps/web/globals.css +++ b/apps/web/globals.css @@ -1,7 +1,6 @@ @import "tailwindcss"; @plugin "@tailwindcss/typography"; - .sm-tweet-theme .react-tweet-theme { --tweet-container-margin: 0px; } diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index bbb63c42..7bab1d21 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -4,6 +4,7 @@ import { NextResponse } from "next/server" export default async function proxy(request: Request) { console.debug("[PROXY] === PROXY START ===") const url = new URL(request.url) + console.debug("[PROXY] Path:", url.pathname) console.debug("[PROXY] Method:", request.method) |