atmosphere explorer pds.ls
tool typescript atproto

Compare changes

Choose any two refs to compare.

+2656 -1661
+12 -7
src/components/hover-card/base.tsx
··· 1 + import { A } from "@solidjs/router"; 2 + import { createSignal, JSX, onCleanup, Show } from "solid-js"; 3 + import { Portal } from "solid-js/web"; 4 + import { canHover } from "../../layout"; 1 5 2 - 3 - 4 - 5 - 6 - 7 - 6 + interface HoverCardProps { 7 + /** Link href - if provided, renders an A tag */ 8 8 9 9 10 10 ··· 107 107 <A 108 108 class={`text-blue-500 hover:underline active:underline dark:text-blue-400 ${props.labelClass || ""}`} 109 109 href={props.href!} 110 - target={props.newTab ? "_blank" : "_self"} 110 + target={props.newTab ? "_blank" : undefined} 111 111 > 112 112 {props.label} 113 113 </A> 114 + )} 115 + <Show when={show() && canHover}> 116 + <Portal> 117 + <div 118 + ref={setupResizeObserver}
+363 -315
src/views/stream/index.tsx
··· 3 3 import { A, useLocation, useSearchParams } from "@solidjs/router"; 4 4 import { createSignal, For, onCleanup, onMount, Show } from "solid-js"; 5 5 import { Button } from "../../components/button"; 6 + import DidHoverCard from "../../components/hover-card/did"; 6 7 import { JSONValue } from "../../components/json"; 7 - import { StickyOverlay } from "../../components/sticky"; 8 8 import { TextInput } from "../../components/text-input"; 9 + import { addToClipboard } from "../../utils/copy"; 10 + import { getStreamType, STREAM_CONFIGS, STREAM_TYPES, StreamType } from "./config"; 9 11 import { StreamStats, StreamStatsPanel } from "./stats"; 10 12 11 13 const LIMIT = 20; 12 - type Parameter = { name: string; param: string | string[] | undefined }; 13 14 14 - const StreamView = () => { 15 + const TYPE_COLORS: Record<string, string> = { 16 + create: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300", 17 + update: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300", 18 + delete: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300", 19 + identity: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300", 20 + account: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", 21 + sync: "bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300", 22 + }; 23 + 24 + const StreamRecordItem = (props: { record: any; streamType: StreamType }) => { 25 + const [expanded, setExpanded] = createSignal(false); 26 + const config = () => STREAM_CONFIGS[props.streamType]; 27 + const info = () => config().parseRecord(props.record); 28 + 29 + const displayType = () => { 30 + const i = info(); 31 + return i.type === "commit" || i.type === "link" ? i.action : i.type; 32 + }; 33 + 34 + const copyRecord = (e: MouseEvent) => { 35 + e.stopPropagation(); 36 + addToClipboard(JSON.stringify(props.record, null, 2)); 37 + }; 38 + 39 + return ( 40 + <div class="flex flex-col gap-2"> 41 + <div class="flex items-start gap-1"> 42 + <button 43 + type="button" 44 + onclick={() => setExpanded(!expanded())} 45 + class="dark:hover:bg-dark-200 flex min-w-0 flex-1 items-start gap-2 rounded p-1 text-left hover:bg-neutral-200/70" 46 + > 47 + <span class="mt-0.5 shrink-0 text-neutral-400 dark:text-neutral-500"> 48 + {expanded() ? 49 + <span class="iconify lucide--chevron-down"></span> 50 + : <span class="iconify lucide--chevron-right"></span>} 51 + </span> 52 + <div class="flex min-w-0 flex-1 flex-col gap-0.5"> 53 + <div class="flex items-center gap-x-1.5 sm:gap-x-2"> 54 + <span 55 + class={`shrink-0 rounded px-1.5 py-0.5 text-xs font-medium ${TYPE_COLORS[displayType()!] || "bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300"}`} 56 + > 57 + {displayType()} 58 + </span> 59 + <Show when={info().collection && info().collection !== info().type}> 60 + <span class="min-w-0 truncate text-neutral-600 dark:text-neutral-300"> 61 + {info().collection} 62 + </span> 63 + </Show> 64 + <Show when={info().rkey}> 65 + <span class="truncate text-neutral-400 dark:text-neutral-500">{info().rkey}</span> 66 + </Show> 67 + </div> 68 + <div class="flex flex-col gap-x-2 gap-y-0.5 text-xs text-neutral-500 sm:flex-row sm:items-center dark:text-neutral-400"> 69 + <Show when={info().did}> 70 + <span class="w-fit" onclick={(e) => e.stopPropagation()}> 71 + <DidHoverCard newTab did={info().did!} /> 72 + </span> 73 + </Show> 74 + <Show when={info().time}> 75 + <span>{info().time}</span> 76 + </Show> 77 + </div> 78 + </div> 79 + </button> 80 + <Show when={expanded()}> 81 + <button 82 + type="button" 83 + onclick={copyRecord} 84 + class="flex size-6 shrink-0 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-200 hover:text-neutral-600 active:bg-neutral-300 sm:size-7 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:active:bg-neutral-600" 85 + > 86 + <span class="iconify lucide--copy"></span> 87 + </button> 88 + </Show> 89 + </div> 90 + <Show when={expanded()}> 91 + <div class="ml-6.5"> 92 + <div class="w-full text-xs wrap-anywhere whitespace-pre-wrap md:w-2xl"> 93 + <JSONValue newTab data={props.record} repo={info().did ?? ""} hideBlobs /> 94 + </div> 95 + </div> 96 + </Show> 97 + </div> 98 + ); 99 + }; 100 + 101 + export const StreamView = () => { 15 102 const [searchParams, setSearchParams] = useSearchParams(); 16 - const [parameters, setParameters] = createSignal<Parameter[]>([]); 17 - const streamType = useLocation().pathname === "/firehose" ? "firehose" : "jetstream"; 18 - const [records, setRecords] = createSignal<Array<any>>([]); 103 + const streamType = getStreamType(useLocation().pathname); 104 + const config = () => STREAM_CONFIGS[streamType]; 105 + 106 + const [records, setRecords] = createSignal<any[]>([]); 19 107 const [connected, setConnected] = createSignal(false); 20 108 const [paused, setPaused] = createSignal(false); 21 109 const [notice, setNotice] = createSignal(""); 110 + const [parameters, setParameters] = createSignal<{ name: string; value?: string }[]>([]); 111 + const [stats, setStats] = createSignal<StreamStats>({ 112 + totalEvents: 0, 113 + eventsPerSecond: 0, 22 114 115 + collections: {}, 116 + }); 117 + const [currentTime, setCurrentTime] = createSignal(Date.now()); 23 118 119 + let socket: WebSocket; 120 + let firehose: Firehose; 121 + let formRef!: HTMLFormElement; 24 122 123 + let rafId: number | null = null; 124 + let statsIntervalId: number | null = null; 125 + let statsUpdateIntervalId: number | null = null; 126 + let currentSecondEventCount = 0; 127 + let totalEventsCount = 0; 128 + let eventTypesMap: Record<string, number> = {}; 129 + let collectionsMap: Record<string, number> = {}; 25 130 131 + const addRecord = (record: any) => { 132 + currentSecondEventCount++; 133 + totalEventsCount++; 26 134 135 + const rawEventType = record.kind || record.$type || "unknown"; 136 + const eventType = rawEventType.includes("#") ? rawEventType.split("#").pop() : rawEventType; 137 + eventTypesMap[eventType] = (eventTypesMap[eventType] || 0) + 1; 27 138 139 + if (eventType !== "account" && eventType !== "identity") { 140 + const collection = 141 + record.commit?.collection || 142 + record.op?.path?.split("/")[0] || 143 + record.link?.source || 144 + "unknown"; 145 + collectionsMap[collection] = (collectionsMap[collection] || 0) + 1; 146 + } 28 147 29 148 30 149 ··· 36 155 37 156 38 157 158 + }; 39 159 160 + const disconnect = () => { 161 + if (!config().useFirehoseLib) socket?.close(); 162 + else firehose?.close(); 40 163 164 + if (rafId !== null) { 165 + cancelAnimationFrame(rafId); 166 + rafId = null; 41 167 42 168 43 169 44 170 45 171 46 172 173 + clearInterval(statsUpdateIntervalId); 174 + statsUpdateIntervalId = null; 175 + } 47 176 177 + pendingRecords = []; 178 + totalEventsCount = 0; 179 + eventTypesMap = {}; 180 + collectionsMap = {}; 181 + setConnected(false); 182 + setPaused(false); 183 + setStats((prev) => ({ ...prev, eventsPerSecond: 0 })); 184 + }; 48 185 186 + const connectStream = async (formData: FormData) => { 187 + setNotice(""); 188 + if (connected()) { 189 + disconnect(); 49 190 191 + } 192 + setRecords([]); 50 193 194 + const instance = formData.get("instance")?.toString() ?? config().defaultInstance; 195 + const url = config().buildUrl(instance, formData); 51 196 197 + // Save all form fields to URL params 198 + const params: Record<string, string | undefined> = { instance }; 199 + config().fields.forEach((field) => { 200 + params[field.searchParam] = formData.get(field.name)?.toString(); 201 + }); 202 + setSearchParams(params); 52 203 204 + // Build parameters display 205 + setParameters([ 206 + { name: "Instance", value: instance }, 207 + ...config() 208 + .fields.filter((f) => f.type !== "checkbox") 209 + .map((f) => ({ name: f.label, value: formData.get(f.name)?.toString() })), 210 + ...config() 211 + .fields.filter((f) => f.type === "checkbox" && formData.get(f.name) === "on") 212 + .map((f) => ({ name: f.label, value: "on" })), 213 + ]); 53 214 215 + setConnected(true); 216 + const now = Date.now(); 217 + setCurrentTime(now); 54 218 219 + totalEventsCount = 0; 220 + eventTypesMap = {}; 221 + collectionsMap = {}; 55 222 56 223 57 224 ··· 67 234 68 235 69 236 237 + })); 238 + }, 50); 70 239 240 + statsIntervalId = window.setInterval(() => { 241 + setStats((prev) => ({ ...prev, eventsPerSecond: currentSecondEventCount })); 242 + currentSecondEventCount = 0; 243 + setCurrentTime(Date.now()); 244 + }, 1000); 71 245 246 + if (!config().useFirehoseLib) { 247 + socket = new WebSocket(url); 248 + socket.addEventListener("message", (event) => { 249 + const rec = JSON.parse(event.data); 250 + const isFilteredEvent = rec.kind === "account" || rec.kind === "identity"; 251 + if (!isFilteredEvent || streamType !== "jetstream" || searchParams.allEvents === "on") 252 + addRecord(rec); 253 + }); 254 + socket.addEventListener("error", () => { 72 255 256 + disconnect(); 257 + }); 258 + } else { 259 + const cursor = formData.get("cursor")?.toString(); 260 + firehose = new Firehose({ 261 + relay: url, 262 + cursor: cursor, 73 263 74 264 75 265 ··· 77 267 78 268 79 269 270 + }); 271 + firehose.on("commit", (commit) => { 272 + for (const op of commit.ops) { 273 + addRecord({ 274 + $type: commit.$type, 275 + repo: commit.repo, 276 + seq: commit.seq, 80 277 278 + rev: commit.rev, 279 + since: commit.since, 280 + op: op, 281 + }); 282 + } 283 + }); 284 + firehose.on("identity", (identity) => addRecord(identity)); 285 + firehose.on("account", (account) => addRecord(account)); 286 + firehose.on("sync", (sync) => { 287 + addRecord({ 288 + $type: sync.$type, 289 + did: sync.did, 290 + rev: sync.rev, 291 + seq: sync.seq, 292 + time: sync.time, 293 + }); 294 + }); 295 + firehose.start(); 296 + } 297 + }; 81 298 299 + onMount(() => { 300 + if (searchParams.instance) { 301 + const formData = new FormData(); 302 + formData.append("instance", searchParams.instance.toString()); 303 + config().fields.forEach((field) => { 304 + const value = searchParams[field.searchParam]; 305 + if (value) formData.append(field.name, value.toString()); 306 + }); 307 + connectStream(formData); 308 + } 309 + }); 82 310 83 - 84 - 85 - 86 - 87 - 88 - 89 - 90 - 91 - 92 - 93 - 94 - 95 - 96 - 97 - 98 - 99 - 100 - 101 - 102 - 103 - 104 - 105 - 106 - 107 - 108 - 109 - 110 - 111 - 112 - 113 - 114 - 115 - 116 - 117 - 118 - 119 - 120 - 121 - 122 - 123 - 124 - 125 - 126 - 127 - 128 - 129 - 130 - 131 - 132 - 133 - 134 - 135 - 136 - 137 - 138 - 139 - 140 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 149 - 150 - 151 - 152 - 153 - 154 - 155 - 156 - 157 - 158 - 159 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 171 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 184 - 185 - 186 - 187 - 188 - 189 - 190 - 191 - 192 - 193 - 194 - 195 - 196 - 197 - 198 - 199 - 200 - 201 - 202 - 203 - 204 - 205 - 206 - 207 - 208 - 209 - 210 - 211 - 212 - 213 - 214 - 215 - 216 - 217 - 218 - 219 - 220 - 221 - 222 - 223 - 224 - 225 - 226 - 227 - 228 - 229 - 230 - 231 - 232 - 233 - 234 - 235 - 236 - 237 - 238 - 239 - 240 - 241 - 242 - 243 - 244 - 245 - 246 - 247 - 248 - 249 - 250 - 251 - 252 - 253 - 254 - 255 - 256 - 257 - 258 - 259 - 260 - 261 - 262 - 263 - 264 - 311 + onCleanup(() => { 312 + socket?.close(); 313 + firehose?.close(); 314 + if (rafId !== null) cancelAnimationFrame(rafId); 315 + if (statsIntervalId !== null) clearInterval(statsIntervalId); 316 + if (statsUpdateIntervalId !== null) clearInterval(statsUpdateIntervalId); 317 + }); 265 318 266 319 return ( 267 320 <> 268 - <Title>{streamType === "firehose" ? "Firehose" : "Jetstream"} - PDSls</Title> 269 - <div class="flex w-full flex-col items-center"> 321 + <Title>{config().label} - PDSls</Title> 322 + <div class="flex w-full flex-col items-center gap-2"> 323 + {/* Tab Navigation */} 270 324 <div class="flex gap-4 font-medium"> 271 - <A 272 - class="flex items-center gap-1 border-b-2" 273 - 274 - 275 - 276 - 277 - 278 - 279 - 280 - 281 - 282 - 325 + <For each={STREAM_TYPES}> 326 + {(type) => ( 327 + <A 328 + class="flex items-center gap-1 border-b-2 transition-colors" 329 + inactiveClass="border-transparent text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100" 330 + href={`/${type}`} 331 + > 332 + {STREAM_CONFIGS[type].label} 333 + </A> 334 + )} 335 + </For> 336 + </div> 283 337 284 - </A> 338 + {/* Stream Description */} 339 + <div class="w-full px-2 text-center"> 340 + <p class="text-sm text-neutral-600 dark:text-neutral-400">{config().description}</p> 285 341 </div> 342 + 343 + {/* Connection Form */} 286 344 <Show when={!connected()}> 287 - <form ref={formRef} class="mt-4 mb-4 flex w-full flex-col gap-1.5 px-2 text-sm"> 345 + <form ref={formRef} class="flex w-full flex-col gap-2 p-2 text-sm"> 288 346 <label class="flex items-center justify-end gap-x-1"> 289 - <span class="min-w-20">Instance</span> 347 + <span class="min-w-21 select-none">Instance</span> 290 348 <TextInput 349 + name="instance" 350 + value={searchParams.instance ?? config().defaultInstance} 351 + class="grow" 352 + /> 353 + </label> 354 + 355 + <For each={config().fields}> 356 + {(field) => ( 357 + <label class="flex items-center justify-end gap-x-1"> 358 + <Show when={field.type === "checkbox"}> 359 + <input 360 + type="checkbox" 361 + name={field.name} 362 + id={field.name} 363 + checked={searchParams[field.searchParam] === "on"} 364 + /> 365 + </Show> 366 + <span class="min-w-21 select-none">{field.label}</span> 367 + <Show when={field.type === "textarea"}> 368 + <textarea 369 + name={field.name} 370 + spellcheck={false} 371 + placeholder={field.placeholder} 372 + value={(searchParams[field.searchParam] as string) ?? ""} 373 + class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1 outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400" 374 + /> 375 + </Show> 376 + <Show when={field.type === "text"}> 377 + <TextInput 378 + name={field.name} 379 + placeholder={field.placeholder} 380 + value={(searchParams[field.searchParam] as string) ?? ""} 381 + class="grow" 382 + /> 383 + </Show> 384 + </label> 385 + )} 386 + </For> 291 387 292 - 293 - 294 - 295 - 296 - 297 - 298 - 299 - 300 - 301 - 302 - 303 - 304 - 305 - 306 - 307 - 308 - 309 - 310 - 311 - 312 - 313 - 314 - 315 - 316 - 317 - 318 - 319 - 320 - 321 - 322 - 323 - 324 - 325 - 326 - 327 - 328 - 329 - 330 - 331 - 332 - 333 - 334 - 335 - 336 - 337 - 338 - 339 - 340 - 341 - 342 - 343 - 344 - 345 - 346 - 347 - 348 - 349 - 388 + <div class="flex justify-end gap-2"> 389 + <Button onClick={() => connectStream(new FormData(formRef))}>Connect</Button> 390 + </div> 350 391 </form> 351 392 </Show> 393 + 394 + {/* Connected State */} 352 395 <Show when={connected()}> 353 - <StickyOverlay> 354 - <div class="flex w-full flex-col gap-2 p-1"> 355 - <div class="flex flex-col gap-1 text-sm wrap-anywhere"> 356 - <div class="font-semibold">Parameters</div> 357 - <For each={parameters()}> 358 - {(param) => ( 359 - <Show when={param.param}> 360 - <div class="text-sm"> 361 - <div class="text-xs text-neutral-500 dark:text-neutral-400"> 362 - {param.name} 363 - </div> 364 - <div class="text-neutral-700 dark:text-neutral-300">{param.param}</div> 365 - </div> 366 - </Show> 367 - )} 368 - </For> 369 - </div> 370 - <StreamStatsPanel stats={stats()} currentTime={currentTime()} /> 371 - <div class="flex justify-end gap-2"> 372 - <button 373 - type="button" 374 - ontouchstart={(e) => { 375 - e.preventDefault(); 376 - requestAnimationFrame(() => togglePause()); 377 - }} 378 - onclick={togglePause} 379 - class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 380 - > 381 - {paused() ? "Resume" : "Pause"} 382 - </button> 383 - <button 384 - type="button" 385 - ontouchstart={(e) => { 386 - e.preventDefault(); 387 - requestAnimationFrame(() => disconnect()); 388 - }} 389 - onclick={disconnect} 390 - class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 391 - > 392 - Disconnect 393 - </button> 394 - </div> 396 + <div class="flex w-full flex-col gap-2 p-2"> 397 + <div class="flex flex-col gap-1 text-sm wrap-anywhere"> 398 + <div class="font-semibold">Parameters</div> 399 + <For each={parameters()}> 400 + {(param) => ( 401 + <Show when={param.value}> 402 + <div class="text-sm"> 403 + <div class="text-xs text-neutral-500 dark:text-neutral-400">{param.name}</div> 404 + <div class="text-neutral-700 dark:text-neutral-300">{param.value}</div> 405 + </div> 406 + </Show> 407 + )} 408 + </For> 409 + </div> 410 + <StreamStatsPanel 411 + stats={stats()} 412 + currentTime={currentTime()} 413 + streamType={streamType} 414 + showAllEvents={searchParams.allEvents === "on"} 415 + /> 416 + <div class="flex justify-end gap-2"> 417 + <Button 418 + ontouchstart={(e) => { 419 + e.preventDefault(); 420 + requestAnimationFrame(() => setPaused(!paused())); 421 + }} 422 + onClick={() => setPaused(!paused())} 423 + > 424 + {paused() ? "Resume" : "Pause"} 425 + </Button> 426 + <Button 427 + ontouchstart={(e) => { 428 + e.preventDefault(); 429 + requestAnimationFrame(() => disconnect()); 430 + }} 431 + onClick={disconnect} 432 + > 433 + Disconnect 434 + </Button> 395 435 </div> 396 - </StickyOverlay> 436 + </div> 397 437 </Show> 438 + 439 + {/* Error Notice */} 398 440 <Show when={notice().length}> 399 441 <div class="text-red-500 dark:text-red-400">{notice()}</div> 400 442 </Show> 401 - <div class="flex w-full flex-col gap-2 divide-y-[0.5px] divide-neutral-500 font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:text-sm md:w-3xl"> 402 - <For each={records().toReversed()}> 403 - {(rec) => ( 404 - <div class="pb-2"> 405 - <JSONValue data={rec} repo={rec.did ?? rec.repo} hideBlobs /> 406 - </div> 407 - )} 408 - </For> 409 - </div> 443 + 444 + {/* Records List */} 445 + <Show when={connected() || records().length > 0}> 446 + <div class="flex min-h-280 w-full flex-col gap-2 font-mono text-xs [overflow-anchor:auto] sm:text-sm"> 447 + <For each={records().toReversed()}> 448 + {(rec) => ( 449 + <div class="[overflow-anchor:none]"> 450 + <StreamRecordItem record={rec} streamType={streamType} /> 451 + </div> 452 + )} 453 + </For> 454 + <div class="h-px [overflow-anchor:auto]" /> 455 + </div> 456 + </Show> 410 457 </div> 411 458 </> 412 459 ); 460 + };
+1 -1
src/index.tsx
··· 19 19 () => ( 20 20 <Router root={Layout}> 21 21 <Route path="/" component={Home} /> 22 - <Route path={["/jetstream", "/firehose"]} component={StreamView} /> 22 + <Route path={["/jetstream", "/firehose", "/spacedust"]} component={StreamView} /> 23 23 <Route path="/labels" component={LabelView} /> 24 24 <Route path="/car" component={CarView} /> 25 25 <Route path="/car/explore" component={ExploreToolView} />
+221
src/views/stream/config.ts
··· 1 + import { localDateFromTimestamp } from "../../utils/date"; 2 + 3 + export type StreamType = "jetstream" | "firehose" | "spacedust"; 4 + 5 + export type FormField = { 6 + name: string; 7 + label: string; 8 + type: "text" | "textarea" | "checkbox"; 9 + placeholder?: string; 10 + searchParam: string; 11 + }; 12 + 13 + export type RecordInfo = { 14 + type: string; 15 + did?: string; 16 + collection?: string; 17 + rkey?: string; 18 + action?: string; 19 + time?: string; 20 + }; 21 + 22 + export type StreamConfig = { 23 + label: string; 24 + description: string; 25 + icon: string; 26 + defaultInstance: string; 27 + fields: FormField[]; 28 + useFirehoseLib: boolean; 29 + buildUrl: (instance: string, formData: FormData) => string; 30 + parseRecord: (record: any) => RecordInfo; 31 + showEventTypes: boolean; 32 + collectionsLabel: string; 33 + }; 34 + 35 + export const STREAM_CONFIGS: Record<StreamType, StreamConfig> = { 36 + jetstream: { 37 + label: "Jetstream", 38 + description: "A simplified event stream with support for collection and DID filtering.", 39 + icon: "lucide--radio-tower", 40 + defaultInstance: "wss://jetstream1.us-east.bsky.network/subscribe", 41 + useFirehoseLib: false, 42 + showEventTypes: true, 43 + collectionsLabel: "Top Collections", 44 + fields: [ 45 + { 46 + name: "collections", 47 + label: "Collections", 48 + type: "textarea", 49 + placeholder: "Comma-separated list of collections", 50 + searchParam: "collections", 51 + }, 52 + { 53 + name: "dids", 54 + label: "DIDs", 55 + type: "textarea", 56 + placeholder: "Comma-separated list of DIDs", 57 + searchParam: "dids", 58 + }, 59 + { 60 + name: "cursor", 61 + label: "Cursor", 62 + type: "text", 63 + placeholder: "Leave empty for live-tail", 64 + searchParam: "cursor", 65 + }, 66 + { 67 + name: "allEvents", 68 + label: "Show account and identity events", 69 + type: "checkbox", 70 + searchParam: "allEvents", 71 + }, 72 + ], 73 + buildUrl: (instance, formData) => { 74 + let url = instance + "?"; 75 + 76 + const collections = formData.get("collections")?.toString().split(","); 77 + collections?.forEach((c) => { 78 + if (c.trim().length) url += `wantedCollections=${c.trim()}&`; 79 + }); 80 + 81 + const dids = formData.get("dids")?.toString().split(","); 82 + dids?.forEach((d) => { 83 + if (d.trim().length) url += `wantedDids=${d.trim()}&`; 84 + }); 85 + 86 + const cursor = formData.get("cursor")?.toString(); 87 + if (cursor?.length) url += `cursor=${cursor}&`; 88 + 89 + return url.replace(/[&?]$/, ""); 90 + }, 91 + parseRecord: (rec) => { 92 + const collection = rec.commit?.collection || rec.kind; 93 + const rkey = rec.commit?.rkey; 94 + const action = rec.commit?.operation; 95 + const time = rec.time_us ? localDateFromTimestamp(rec.time_us / 1000) : undefined; 96 + return { type: rec.kind, did: rec.did, collection, rkey, action, time }; 97 + }, 98 + }, 99 + 100 + firehose: { 101 + label: "Firehose", 102 + description: "The raw event stream from a relay or PDS.", 103 + icon: "lucide--rss", 104 + defaultInstance: "wss://bsky.network", 105 + useFirehoseLib: true, 106 + showEventTypes: true, 107 + collectionsLabel: "Top Collections", 108 + fields: [ 109 + { 110 + name: "cursor", 111 + label: "Cursor", 112 + type: "text", 113 + placeholder: "Leave empty for live-tail", 114 + searchParam: "cursor", 115 + }, 116 + ], 117 + buildUrl: (instance, _formData) => { 118 + let url = instance; 119 + url = url.replace("/xrpc/com.atproto.sync.subscribeRepos", ""); 120 + if (!(url.startsWith("wss://") || url.startsWith("ws://"))) { 121 + url = "wss://" + url; 122 + } 123 + return url; 124 + }, 125 + parseRecord: (rec) => { 126 + const type = rec.$type?.split("#").pop() || rec.$type; 127 + const did = rec.repo ?? rec.did; 128 + const pathParts = rec.op?.path?.split("/") || []; 129 + const collection = pathParts[0]; 130 + const rkey = pathParts[1]; 131 + const time = rec.time ? localDateFromTimestamp(Date.parse(rec.time)) : undefined; 132 + return { type, did, collection, rkey, action: rec.op?.action, time }; 133 + }, 134 + }, 135 + 136 + spacedust: { 137 + label: "Spacedust", 138 + description: "A stream of links showing interactions across the network.", 139 + icon: "lucide--link", 140 + defaultInstance: "wss://spacedust.microcosm.blue/subscribe", 141 + useFirehoseLib: false, 142 + showEventTypes: false, 143 + collectionsLabel: "Top Sources", 144 + fields: [ 145 + { 146 + name: "sources", 147 + label: "Sources", 148 + type: "textarea", 149 + placeholder: "e.g. app.bsky.graph.follow:subject", 150 + searchParam: "sources", 151 + }, 152 + { 153 + name: "subjectDids", 154 + label: "Subject DIDs", 155 + type: "textarea", 156 + placeholder: "Comma-separated list of DIDs", 157 + searchParam: "subjectDids", 158 + }, 159 + { 160 + name: "subjects", 161 + label: "Subjects", 162 + type: "textarea", 163 + placeholder: "Comma-separated list of AT URIs", 164 + searchParam: "subjects", 165 + }, 166 + { 167 + name: "instant", 168 + label: "Instant mode (bypass 21s delay buffer)", 169 + type: "checkbox", 170 + searchParam: "instant", 171 + }, 172 + ], 173 + buildUrl: (instance, formData) => { 174 + let url = instance + "?"; 175 + 176 + const sources = formData.get("sources")?.toString().split(","); 177 + sources?.forEach((s) => { 178 + if (s.trim().length) url += `wantedSources=${s.trim()}&`; 179 + }); 180 + 181 + const subjectDids = formData.get("subjectDids")?.toString().split(","); 182 + subjectDids?.forEach((d) => { 183 + if (d.trim().length) url += `wantedSubjectDids=${d.trim()}&`; 184 + }); 185 + 186 + const subjects = formData.get("subjects")?.toString().split(","); 187 + subjects?.forEach((s) => { 188 + if (s.trim().length) url += `wantedSubjects=${encodeURIComponent(s.trim())}&`; 189 + }); 190 + 191 + const instant = formData.get("instant")?.toString(); 192 + if (instant === "on") url += `instant=true&`; 193 + 194 + return url.replace(/[&?]$/, ""); 195 + }, 196 + parseRecord: (rec) => { 197 + const source = rec.link?.source; 198 + const sourceRecord = rec.link?.source_record; 199 + const uriParts = sourceRecord?.replace("at://", "").split("/") || []; 200 + const did = uriParts[0]; 201 + const collection = uriParts[1]; 202 + const rkey = uriParts[2]; 203 + return { 204 + type: rec.kind, 205 + did, 206 + collection: source || collection, 207 + rkey, 208 + action: rec.link?.operation, 209 + time: undefined, 210 + }; 211 + }, 212 + }, 213 + }; 214 + 215 + export const STREAM_TYPES = Object.keys(STREAM_CONFIGS) as StreamType[]; 216 + 217 + export const getStreamType = (pathname: string): StreamType => { 218 + if (pathname === "/firehose") return "firehose"; 219 + if (pathname === "/spacedust") return "spacedust"; 220 + return "jetstream"; 221 + };
+23 -6
src/views/stream/stats.tsx
··· 1 1 import { For, Show } from "solid-js"; 2 + import { STREAM_CONFIGS, StreamType } from "./config"; 2 3 3 4 export type StreamStats = { 4 5 connectedAt?: number; ··· 22 23 } 23 24 }; 24 25 25 - export const StreamStatsPanel = (props: { stats: StreamStats; currentTime: number }) => { 26 + export const StreamStatsPanel = (props: { 27 + stats: StreamStats; 28 + currentTime: number; 29 + streamType: StreamType; 30 + showAllEvents?: boolean; 31 + }) => { 32 + const config = () => STREAM_CONFIGS[props.streamType]; 26 33 const uptime = () => (props.stats.connectedAt ? props.currentTime - props.stats.connectedAt : 0); 27 34 28 - const topCollections = () => 29 - 35 + const shouldShowEventTypes = () => { 36 + if (!config().showEventTypes) return false; 37 + if (props.streamType === "jetstream") return props.showAllEvents === true; 38 + return true; 39 + }; 30 40 41 + const topCollections = () => 42 + Object.entries(props.stats.collections) 43 + .sort(([, a], [, b]) => b - a) 31 44 32 45 33 46 ··· 60 73 </div> 61 74 </div> 62 75 63 - <Show when={topEventTypes().length > 0}> 76 + <Show when={topEventTypes().length > 0 && shouldShowEventTypes()}> 64 77 <div class="mt-2"> 65 78 <div class="mb-1 text-xs text-neutral-500 dark:text-neutral-400">Event Types</div> 66 79 <div class="grid grid-cols-[1fr_5rem_3rem] gap-x-1 gap-y-0.5 font-mono text-xs sm:gap-x-4"> ··· 86 99 87 100 <Show when={topCollections().length > 0}> 88 101 <div class="mt-2"> 89 - <div class="mb-1 text-xs text-neutral-500 dark:text-neutral-400">Top Collections</div> 102 + <div class="mb-1 text-xs text-neutral-500 dark:text-neutral-400"> 103 + {config().collectionsLabel} 104 + </div> 90 105 <div class="grid grid-cols-[1fr_5rem_3rem] gap-x-1 gap-y-0.5 font-mono text-xs sm:gap-x-4"> 91 106 <For each={topCollections()}> 92 107 {([collection, count]) => { 93 108 const percentage = ((count / props.stats.totalEvents) * 100).toFixed(1); 94 109 return ( 95 110 <> 96 - <span class="text-neutral-700 dark:text-neutral-300">{collection}</span> 111 + <span class="min-w-0 truncate text-neutral-700 dark:text-neutral-300"> 112 + {collection} 113 + </span> 97 114 <span class="text-right text-neutral-600 tabular-nums dark:text-neutral-400"> 98 115 {count.toLocaleString()} 99 116 </span>
+7 -3
src/styles/index.css
··· 47 47 48 48 49 49 50 + --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037c-.856 3.061-3.978 3.842-6.755 3.37c4.854.826 6.089 3.562 3.422 6.299c-5.065 5.196-7.28-1.304-7.847-2.97c-.104-.305-.152-.448-.153-.327c0-.121-.05.022-.153.327c-.568 1.666-2.782 8.166-7.847 2.97c-2.667-2.737-1.432-5.473 3.422-6.3c-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026'/%3E%3C/svg%3E"); 51 + } 50 52 53 + .i-raycast-light { 54 + --svg: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22228%22%20height%3D%22228%22%20viewBox%3D%220%200%20228%20228%22%20fill%3D%22none%22%3E%3Cg%20clip-path%3D%22url(%23a)%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22m227.987%20113.987-11.89%2011.903-45.11-45.11V56.987zM114.039%200l-11.89%2011.89%2045.097%2045.097h23.793zM88.521%2025.518%2076.618%2037.42l19.58%2019.566h23.792zm82.518%2082.53v23.794l19.501%2019.566%2011.903-11.89zm-6.859%2044.19%206.807-6.808h-88.47V56.987l-6.807%206.82L62.94%2051.1%2051.05%2062.99l12.758%2012.772-6.82%206.756v13.627L37.421%2076.566%2025.518%2088.47l31.469%2031.47v27.229L11.89%20102.097%200%20113.987l114.039%20114%2011.903-11.89-45.11-45.11h27.229l31.47%2031.482%2011.903-11.903-19.579-19.579h13.627l6.808-6.807%2012.771%2012.759%2011.891-11.891z%22%20fill%3D%22%23ff6363%22%2F%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22a%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M0%200h228v228H0z%22%2F%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E"); 55 + } 51 56 52 - 53 - 54 - --svg: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M1.468%2010.977c-.55%201.061-.961%201.751-1.359%202.741a1.508%201.508%200%201%200%202.8%201.124l.227-.574v-.002c.28-.71.52-1.316.81-1.862.328-.018.702-.02%201.125-.023h.053c.77-.005%201.697-.01%202.497-.172s1.791-.545%202.229-1.57c.119-.278.239-.688.134-1.105h.151c.422%200%201.017.001%201.548-.143.62-.17%201.272-.569%201.558-1.41a1.52%201.52%200%200%200%20.034-.925l.084-.015.042-.007c.363-.063.849-.148%201.264-.304.404-.15%201.068-.488%201.267-1.262.113-.44.1-.908-.154-1.33a1.7%201.7%200%200%200-.36-.414c.112-.14.253-.333.35-.547.17-.371.257-.916-.089-1.45-.393-.604-1.066-.71-1.4-.737a6%206%200%200%200-.985.026%201.2%201.2%200%200%200-.156-.275c-.371-.496-.947-.538-1.272-.53-.655.018-1.167.31-1.538.61-.194.159-.657.806-.808.974%200-.603-.581-.91-.99-.973-.794-.123-1.285.388-1.742.973-.57.73-1.01%201.668-1.531%202.373-.18-.117-.393-.39-.733-.375-.56.026-.932.406-1.173.666-.419.452-.685%201.273-.867%201.885-.197.885-.332%201.258-.491%202.228a9.4%209.4%200%200%200-.144%201.677c-.109.213-.234.443-.381.728%22%20fill%3D%22%23639431%22%2F%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M4.714%204.78c.16.14.349.306.755.165.266-.093.61-.695.993-1.367.56-.982%201.205-2.114%201.816-2.02.738.114.693.523.658.837-.025.22-.044.394.216.387.264-.006.521-.317.82-.678.413-.498.904-1.092%201.602-1.11.492-.014.484.198.476.413-.005.138-.01.276.116.358.123.08.434.053.79.02.573-.052%201.265-.114%201.497.243.204.314-.056.626-.305.925-.21.254-.414.498-.321.726.076.186.231.291.383.394.25.168.491.33.361.834-.136.533-.96.677-1.732.812-.646.113-1.257.22-1.397.544-.088.203.058.297.222.403.195.127.415.27.292.633-.29.85-1.254.85-2.16.85-.777%200-1.51%200-1.735.537-.13.31.067.365.282.425.264.074.557.155.315.723-.464%201.087-2.195%201.096-3.78%201.105-.58.004-1.141.007-1.613.063a.18.18%200%200%200-.13.083c-.434.713-.742%201.496-1.07%202.332l-.221.559a.486.486%200%201%201-.903-.363c.373-.928.803-1.781%201.273-2.564.767-1.413%202.28-3.147%203.88-4.45%201.423-1.184%202.782-2.071%204.364-2.744.198-.084.139-.316-.068-.256-1.403.405-2.643%201.21-3.928%202.02-1.399.881-2.57%202.073-3.291%202.94-.127.153-.405.027-.365-.168.313-1.523.636-2.92%201.11-3.432.45-.485.603-.35.798-.18%22%20fill%3D%22%23d9ea72%22%2F%3E%3C%2Fsvg%3E"); 57 + .i-raycast-dark { 58 + --svg: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22228%22%20height%3D%22228%22%20viewBox%3D%220%200%20228%20228%22%20fill%3D%22none%22%3E%3Cg%20clip-path%3D%22url(%23a)%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M57%20147.207V171L-.052%20113.948l11.955-11.864zM80.793%20171H57l57.052%2057.052%2011.903-11.903zm135.317-45.11L228%20114%20114%200l-11.89%2011.89L147.155%2057h-27.229L88.482%2025.583%2076.58%2037.473l19.58%2019.579H82.53v88.469H171v-13.614l19.579%2019.579%2011.89-11.903L171%20108.048V80.819zM62.952%2051.049l-11.89%2011.903L63.82%2075.71l11.89-11.903zM164.193%20152.29l-11.852%2011.89%2012.759%2012.759%2011.903-11.891zM37.421%2076.58%2025.53%2088.47%2057%20119.951V96.145zM131.842%20171h-23.794l31.483%2031.469%2011.89-11.89z%22%20fill%3D%22%23ff6363%22%2F%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22a%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M0%200h228v228H0z%22%2F%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E"); 55 59 } 56 60 57 61 @keyframes slideIn {
+212 -132
src/views/home.tsx
··· 1 1 import { A } from "@solidjs/router"; 2 - import { setOpenManager } from "../auth/state.js"; 2 + import { For, JSX } from "solid-js"; 3 + import { setOpenManager, setShowAddAccount } from "../auth/state"; 4 + import { Button } from "../components/button"; 5 + import { SearchButton } from "../components/search"; 6 + 7 + type ProfileData = { 8 + did: string; 9 + handle: string; 10 + }; 3 11 4 12 export const Home = () => { 5 - return ( 6 - <div class="flex w-full flex-col gap-6 px-2 wrap-break-word"> 7 - <div class="flex flex-col gap-3"> 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - </span> 29 - </a> 30 - <a 31 - href="https://constellation.microcosm.blue" 32 - target="_blank" 33 - class="group grid grid-cols-[auto_1fr] items-center gap-x-2.5 gap-y-0.5 hover:text-blue-500 dark:hover:text-blue-400" 34 - > 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 - 48 - 49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 57 - 58 - 59 - 60 - 61 - 62 - 63 - 64 - 65 - 66 - 67 - 68 - 69 - 70 - 71 - 72 - 73 - 74 - 75 - 76 - 77 - 78 - 79 - 80 - 81 - 82 - 83 - 84 - 13 + const FooterLink = (props: { 14 + href: string; 15 + color: string; 16 + darkColor?: string; 17 + children: JSX.Element; 18 + }) => ( 19 + <a 20 + href={props.href} 21 + class={`relative flex items-center gap-1.5 after:absolute after:bottom-0 after:left-0 after:h-px after:w-0 after:bg-current ${props.color} after:transition-[width] after:duration-300 after:ease-out hover:after:w-full ${props.darkColor ?? ""}`} 22 + target="_blank" 23 + > 24 + {props.children} 25 + </a> 26 + ); 85 27 28 + const allExampleProfiles: ProfileData[] = [ 29 + { did: "did:plc:7vimlesenouvuaqvle42yhvo", handle: "juli.ee" }, 30 + { did: "did:plc:oisofpd7lj26yvgiivf3lxsi", handle: "hailey.at" }, 31 + { did: "did:plc:vwzwgnygau7ed7b7wt5ux7y2", handle: "retr0.id" }, 32 + { did: "did:plc:vc7f4oafdgxsihk4cry2xpze", handle: "jcsalterego.bsky.social" }, 33 + { did: "did:plc:uu5axsmbm2or2dngy4gwchec", handle: "futur.blue" }, 34 + { did: "did:plc:ia76kvnndjutgedggx2ibrem", handle: "mary.my.id" }, 35 + { did: "did:plc:hdhoaan3xa3jiuq4fg4mefid", handle: "bad-example.com" }, 36 + { did: "did:plc:q6gjnaw2blty4crticxkmujt", handle: "jaz.sh" }, 37 + { did: "did:plc:jrtgsidnmxaen4offglr5lsh", handle: "quilling.dev" }, 38 + { did: "did:plc:3c6vkaq7xf5kz3va3muptjh5", handle: "aylac.top" }, 39 + { did: "did:plc:gwd5r7dbg3zv6dhv75hboa3f", handle: "mofu.run" }, 40 + { did: "did:plc:tzrpqyerzt37pyj54hh52xrz", handle: "rainy.pet" }, 41 + { did: "did:plc:qx7in36j344d7qqpebfiqtew", handle: "futanari.observer" }, 42 + { did: "did:plc:ucaezectmpny7l42baeyooxi", handle: "sapphic.moe" }, 43 + { did: "did:plc:6v6jqsy7swpzuu53rmzaybjy", handle: "computer.fish" }, 44 + { did: "did:plc:w4nvvt6feq2l3qgnwl6a7g7d", handle: "emilia.wtf" }, 45 + { did: "did:plc:xwhsmuozq3mlsp56dyd7copv", handle: "paizuri.moe" }, 46 + { did: "did:plc:aokggmp5jzj4nc5jifhiplqc", handle: "dreary.blacksky.app" }, 47 + { did: "did:plc:k644h4rq5bjfzcetgsa6tuby", handle: "natalie.sh" }, 48 + { did: "did:plc:ttdrpj45ibqunmfhdsb4zdwq", handle: "nekomimi.pet" }, 49 + { did: "did:plc:fz2tul67ziakfukcwa3vdd5d", handle: "nullekko.moe" }, 50 + { did: "did:plc:qxichs7jsycphrsmbujwqbfb", handle: "isabelroses.com" }, 51 + { did: "did:plc:fnvdhaoe7b5abgrtvzf4ttl5", handle: "isuggest.selfce.st" }, 52 + { did: "did:plc:p5yjdr64h7mk5l3kh6oszryk", handle: "blooym.dev" }, 53 + { did: "did:plc:hvakvedv6byxhufjl23mfmsd", handle: "number-one-warned.rat.mom" }, 54 + { did: "did:plc:6if5m2yo6kroprmmency3gt5", handle: "olaren.dev" }, 55 + { did: "did:plc:w7adfxpixpi77e424cjjxnxy", handle: "anyaustin.bsky.social" }, 56 + { did: "did:plc:h6as5sk7tfqvvnqvfrlnnwqn", handle: "cwonus.org" }, 57 + { did: "did:plc:mo7bk6gblylupvhetkqmndrv", handle: "claire.on-her.computer" }, 58 + { did: "did:plc:73gqgbnvpx5syidcponjrics", handle: "coil-habdle.ebil.club" }, 59 + { did: "did:plc:gy5roooborfiyvl2xadsam3e", handle: "slug.moe" }, 60 + { did: "did:plc:dadnngq7hpnuglhxm556wgzi", handle: "drunk.moe" }, 61 + { did: "did:plc:ra3gxl2udc22odfbvcfslcn3", handle: "notnite.com" }, 62 + { did: "did:plc:h5wsnqetncv6lu2weom35lg2", handle: "nel.pet" }, 63 + { did: "did:plc:irs2tcoeuvuwj3m4yampbuco", handle: "shi.gg" }, 64 + { did: "did:plc:vafqb3yhndyawabm2t2zhw5z", handle: "neko.moe.observer" }, 65 + ]; 86 66 67 + const profiles = [...allExampleProfiles].sort(() => Math.random() - 0.5).slice(0, 3); 87 68 69 + return ( 70 + <div class="flex w-full flex-col gap-5 px-2 wrap-break-word"> 71 + {/* Welcome Section */} 72 + <div class="flex flex-col gap-4"> 73 + <div class="flex flex-col gap-1"> 74 + <h1 class="text-lg font-medium">Atmosphere Explorer</h1> 75 + <div class="text-sm text-neutral-600 dark:text-neutral-300"> 76 + <p> 77 + Browse the public data on the{" "} 78 + <a 79 + href="https://atproto.com" 80 + target="_blank" 81 + class="underline decoration-neutral-400 transition-colors hover:text-blue-500 hover:decoration-blue-500 dark:decoration-neutral-500 dark:hover:text-blue-400" 82 + > 83 + AT Protocol 84 + </a> 85 + </p> 86 + </div> 87 + </div> 88 88 89 - Raw repository event stream 90 - </span> 91 - </A> 89 + {/* Example Repos */} 90 + <section class="mb-1 flex flex-col gap-3"> 91 + <div class="flex justify-between"> 92 + <For each={profiles}> 93 + {(profile) => ( 94 + <A 95 + href={`/at://${profile.did}`} 96 + class="group flex min-w-0 basis-1/3 flex-col items-center gap-1.5 transition-transform hover:scale-105 active:scale-105" 97 + > 98 + <img 99 + src={`/avatar/${profile.handle}.jpg`} 100 + alt={`Bluesky profile picture of ${profile.handle}`} 101 + class="size-16 rounded-full ring-2 ring-transparent transition-all group-hover:ring-blue-500 active:ring-blue-500 dark:group-hover:ring-blue-400 dark:active:ring-blue-400" 102 + classList={{ 103 + "animate-[spin_5s_linear_infinite] [animation-play-state:paused] group-hover:[animation-play-state:running]": 104 + profile.handle === "coil-habdle.ebil.club", 105 + }} 106 + /> 107 + <span class="w-full truncate px-0.5 text-center text-xs text-neutral-600 dark:text-neutral-300"> 108 + @{profile.handle} 109 + </span> 110 + </A> 111 + )} 112 + </For> 92 113 </div> 93 114 </section> 115 + <div class="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400"> 116 + <SearchButton /> 117 + <span>to find any account</span> 118 + </div> 119 + <div class="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400"> 120 + <Button 121 + onClick={() => { 122 + setOpenManager(true); 123 + setShowAddAccount(true); 124 + }} 125 + > 126 + <span class="iconify lucide--user-round"></span> 127 + Sign in 128 + </Button> 129 + <span>to manage records</span> 130 + </div> 131 + </div> 94 132 95 - 96 - 97 - 98 - 99 - 100 - 101 - 102 - 103 - 104 - 105 - 106 - 107 - 108 - 109 - 110 - 111 - 112 - 113 - 114 - 115 - 116 - 117 - 118 - 119 - 120 - 121 - 122 - 123 - 124 - 125 - 126 - 127 - 128 - </section> 133 + <div class="flex flex-col gap-4 text-sm"> 134 + <div class="flex flex-col gap-2"> 135 + <A 136 + href="/jetstream" 137 + class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400" 138 + > 139 + <div class="iconify lucide--radio-tower" /> 140 + <span class="underline decoration-transparent group-hover:decoration-current"> 141 + Jetstream 142 + </span> 143 + <div /> 144 + <span class="text-xs text-neutral-500 dark:text-neutral-400"> 145 + Event stream with filtering 146 + </span> 147 + </A> 148 + <A 149 + href="/firehose" 150 + class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400" 151 + > 152 + <div class="iconify lucide--rss" /> 153 + <span class="underline decoration-transparent group-hover:decoration-current"> 154 + Firehose 155 + </span> 156 + <div /> 157 + <span class="text-xs text-neutral-500 dark:text-neutral-400"> 158 + Raw relay event stream 159 + </span> 160 + </A> 161 + <A 162 + href="/spacedust" 163 + class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400" 164 + > 165 + <div class="iconify lucide--orbit" /> 166 + <span class="underline decoration-transparent group-hover:decoration-current"> 167 + Spacedust 168 + </span> 169 + <div /> 170 + <span class="text-xs text-neutral-500 dark:text-neutral-400"> 171 + Interaction links stream 172 + </span> 173 + </A> 174 + </div> 175 + 176 + <div class="flex flex-col gap-2"> 177 + <A 178 + href="/labels" 179 + class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400" 180 + > 181 + <div class="iconify lucide--tag" /> 182 + <span class="underline decoration-transparent group-hover:decoration-current"> 183 + Labels 184 + </span> 185 + <div /> 186 + <span class="text-xs text-neutral-500 dark:text-neutral-400"> 187 + Query labeler services 188 + </span> 189 + </A> 190 + <A 191 + href="/car" 192 + class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400" 193 + > 194 + <div class="iconify lucide--folder-archive" /> 195 + <span class="underline decoration-transparent group-hover:decoration-current"> 196 + Archive 197 + </span> 198 + <div /> 199 + <span class="text-xs text-neutral-500 dark:text-neutral-400"> 200 + Explore and unpack CAR files 201 + </span> 202 + </A> 203 + </div> 129 204 </div> 130 205 131 - <div class="flex justify-center gap-2 text-sm text-neutral-600 dark:text-neutral-300"> 132 - <a 206 + <div class="flex justify-center gap-1.5 text-sm text-neutral-600 sm:gap-2 dark:text-neutral-300"> 207 + <FooterLink 133 208 href="https://juli.ee" 134 - target="_blank" 135 - class="relative flex items-center gap-1.5 after:absolute after:bottom-0 after:left-0 after:h-px after:w-0 after:bg-current after:text-rose-400 after:transition-[width] after:duration-300 after:ease-out hover:after:w-full dark:after:text-rose-300" 209 + color="after:text-rose-400" 210 + darkColor="dark:after:text-rose-300" 136 211 > 137 212 <span class="iconify lucide--terminal text-rose-400 dark:text-rose-300"></span> 138 213 <span class="font-pecita">juliet</span> 139 - </a> 214 + </FooterLink> 215 + โ€ข 216 + <FooterLink href="https://raycast.com/juliet_philippe/pdsls" color="after:text-[#FF6363]"> 217 + <span class="iconify-color i-raycast-light block dark:hidden"></span> 218 + <span class="iconify-color i-raycast-dark hidden dark:block"></span> 219 + Raycast 220 + </FooterLink> 140 221 โ€ข 141 - <a 222 + <FooterLink 142 223 href="https://bsky.app/profile/did:plc:6q5daed5gutiyerimlrnojnz" 143 - class="relative flex items-center gap-1.5 after:absolute after:bottom-0 after:left-0 after:h-px after:w-0 after:bg-current after:text-[#0085ff] after:transition-[width] after:duration-300 after:ease-out hover:after:w-full" 144 - target="_blank" 224 + color="after:text-[#0085ff]" 145 225 > 146 226 <span class="simple-icons--bluesky iconify text-[#0085ff]"></span> 147 227 Bluesky 148 - </a> 228 + </FooterLink> 149 229 โ€ข 150 - <a 151 - href="https://tangled.org/@pdsls.dev/pdsls/" 152 - class="relative flex items-center gap-1.5 after:absolute after:bottom-0 after:left-0 after:h-px after:w-0 after:bg-current after:text-black after:transition-[width] after:duration-300 after:ease-out hover:after:w-full dark:after:text-white" 153 - target="_blank" 230 + <FooterLink 231 + href="https://tangled.org/did:plc:6q5daed5gutiyerimlrnojnz/pdsls/" 232 + color="after:text-black" 233 + darkColor="dark:after:text-white" 154 234 > 155 235 <span class="iconify i-tangled text-black dark:text-white"></span> 156 236 Source 157 - </a> 237 + </FooterLink> 158 238 </div> 159 239 </div> 160 240 );
+99 -97
src/auth/account.tsx
··· 1 + import { Did } from "@atcute/lexicons"; 2 + import { deleteStoredSession, getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 3 + import { A } from "@solidjs/router"; 4 + import { createEffect, For, onMount, Show } from "solid-js"; 5 + import { createStore, produce } from "solid-js/store"; 6 + import { ActionMenu, DropdownMenu, MenuProvider, NavMenu } from "../components/dropdown.jsx"; 7 + import { Modal } from "../components/modal.jsx"; 1 8 2 9 3 10 ··· 16 23 17 24 18 25 26 + setOpenManager, 27 + setPendingPermissionEdit, 28 + setSessions, 29 + setShowAddAccount, 30 + showAddAccount, 31 + } from "./state.js"; 19 32 33 + const AccountDropdown = (props: { did: Did; onEditPermissions: (did: Did) => void }) => { 20 34 21 35 22 36 ··· 37 51 38 52 39 53 54 + <DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-md p-2"> 55 + <NavMenu 56 + href={`/at://${props.did}`} 57 + label="Go to repo" 58 + icon="lucide--user-round" 59 + shortcut={agent()?.sub === props.did ? "G" : undefined} 60 + /> 61 + <ActionMenu 62 + icon="lucide--settings" 40 63 41 64 42 65 ··· 50 73 51 74 52 75 76 + export const AccountManager = () => { 77 + const [avatars, setAvatars] = createStore<Record<Did, string>>(); 53 78 79 + const getThumbnailUrl = (avatarUrl: string) => { 80 + return avatarUrl.replace("img/avatar/", "img/avatar_thumbnail/"); 54 81 55 82 56 83 ··· 94 121 95 122 96 123 97 - 98 - 99 - 100 - 101 - 102 - 103 - 104 - 105 - 106 - 107 - 108 - 109 - 110 - 111 - 112 - 113 - 114 - 115 - 116 - 117 - 118 - 119 - 120 - 121 - 122 - 123 - 124 - 125 - 124 + open={openManager()} 125 + onClose={() => { 126 + setOpenManager(false); 127 + setShowAddAccount(false); 126 128 scopeFlow.cancel(); 127 129 }} 128 130 alignTop 131 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-full max-w-sm rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 mx-3 shadow-md dark:border-neutral-700" 129 132 > 130 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-88 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 131 - <Show when={!scopeFlow.showScopeSelector() && !showingAddAccount()}> 132 - <div class="mb-2 px-1 font-semibold"> 133 - <span>Manage accounts</span> 134 - </div> 135 - <div class="mb-3 max-h-80 overflow-y-auto md:max-h-100"> 136 - <For each={Object.keys(sessions)}> 137 - {(did) => ( 138 - <div class="flex w-full items-center justify-between"> 139 - <A 140 - href={`/at://${did}`} 141 - onClick={() => setOpenManager(false)} 142 - class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 143 - > 144 - <Show 145 - when={avatars[did as Did]} 146 - fallback={<span class="iconify lucide--user-round m-0.5 size-5"></span>} 147 - > 148 - <img 149 - src={getThumbnailUrl(avatars[did as Did])} 150 - class="size-6 rounded-full" 151 - /> 152 - </Show> 153 - </A> 154 - <button 155 - class="flex grow items-center justify-between gap-1 truncate rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 156 - onclick={() => handleAccountClick(did as Did)} 133 + <Show when={!scopeFlow.showScopeSelector() && !showAddAccount()}> 134 + <div class="mb-2 px-1 font-semibold"> 135 + <span>Switch account</span> 136 + </div> 137 + <div class="mb-3 max-h-80 overflow-y-auto md:max-h-100"> 138 + <For each={Object.keys(sessions)}> 139 + {(did) => ( 140 + <div class="flex w-full items-center justify-between"> 141 + <A 142 + href={`/at://${did}`} 143 + onClick={() => setOpenManager(false)} 144 + class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 145 + > 146 + <Show 147 + when={avatars[did as Did]} 148 + fallback={<span class="iconify lucide--user-round m-0.5 size-5"></span>} 157 149 > 158 - <span class="truncate">{sessions[did]?.handle || did}</span> 159 - <Show when={did === agent()?.sub && sessions[did].signedIn}> 160 - <span class="iconify lucide--circle-check shrink-0 text-blue-500 dark:text-blue-400"></span> 161 - </Show> 162 - <Show when={!sessions[did].signedIn}> 163 - <span class="iconify lucide--circle-alert shrink-0 text-red-500 dark:text-red-400"></span> 164 - </Show> 165 - </button> 166 - <AccountDropdown 167 - did={did as Did} 168 - onEditPermissions={(accountDid) => scopeFlow.initiateWithRedirect(accountDid)} 169 - /> 170 - </div> 171 - )} 172 - </For> 173 - </div> 174 - <button 175 - onclick={() => setShowingAddAccount(true)} 176 - class="flex w-full items-center justify-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100 px-3 py-2 hover:bg-neutral-200 active:bg-neutral-300 dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 177 - > 178 - <span class="iconify lucide--plus"></span> 179 - <span>Add account</span> 180 - </button> 181 - </Show> 182 - 183 - <Show when={showingAddAccount() && !scopeFlow.showScopeSelector()}> 184 - <Login onCancel={() => setShowingAddAccount(false)} /> 185 - </Show> 186 - 187 - <Show when={scopeFlow.showScopeSelector()}> 188 - <ScopeSelector 189 - initialScopes={parseScopeString( 190 - sessions[scopeFlow.pendingAccount()]?.grantedScopes || "", 150 + <img src={getThumbnailUrl(avatars[did as Did])} class="size-6 rounded-full" /> 151 + </Show> 152 + </A> 153 + <button 154 + class="flex grow items-center justify-between gap-1 truncate rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 155 + onclick={() => handleAccountClick(did as Did)} 156 + > 157 + <span class="truncate">{sessions[did]?.handle || did}</span> 158 + <Show when={did === agent()?.sub && sessions[did].signedIn}> 159 + <span class="iconify lucide--circle-check shrink-0 text-blue-500 dark:text-blue-400"></span> 160 + </Show> 161 + <Show when={!sessions[did].signedIn}> 162 + <span class="iconify lucide--circle-alert shrink-0 text-red-500 dark:text-red-400"></span> 163 + </Show> 164 + </button> 165 + <AccountDropdown 166 + did={did as Did} 167 + onEditPermissions={(accountDid) => scopeFlow.initiateWithRedirect(accountDid)} 168 + /> 169 + </div> 191 170 )} 192 - onConfirm={scopeFlow.complete} 193 - onCancel={() => { 194 - scopeFlow.cancel(); 195 - setShowingAddAccount(false); 196 - }} 197 - /> 198 - </Show> 199 - </div> 171 + </For> 172 + </div> 173 + <button 174 + onclick={() => setShowAddAccount(true)} 175 + class="dark:hover:bg-dark-200 dark:active:bg-dark-100 flex w-full items-center justify-center gap-2 rounded-lg border border-neutral-200 px-3 py-2 hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 176 + > 177 + <span class="iconify lucide--plus"></span> 178 + <span>Add account</span> 179 + </button> 180 + </Show> 181 + 182 + <Show when={showAddAccount() && !scopeFlow.showScopeSelector()}> 183 + <Login onCancel={() => setShowAddAccount(false)} /> 184 + </Show> 185 + 186 + <Show when={scopeFlow.showScopeSelector()}> 187 + <ScopeSelector 188 + initialScopes={parseScopeString( 189 + sessions[scopeFlow.pendingAccount()]?.grantedScopes || "", 190 + )} 191 + onConfirm={scopeFlow.complete} 192 + onCancel={() => { 193 + scopeFlow.cancel(); 194 + setShowAddAccount(false); 195 + }} 196 + /> 197 + </Show> 200 198 </Modal> 201 199 <button 202 200 onclick={() => setOpenManager(true)} 201 + class={`flex items-center rounded-md ${agent() && avatars[agent()!.sub] ? "p-1.25" : "p-1.5"} hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600`} 202 + > 203 + {agent() && avatars[agent()!.sub] ? 204 + <img src={getThumbnailUrl(avatars[agent()!.sub])} class="size-5 rounded-full" />
+10 -8
src/components/create/confirm-submit.tsx
··· 27 27 }; 28 28 29 29 return ( 30 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[24rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 30 + <> 31 31 <div class="flex flex-col gap-3 text-sm"> 32 32 <h2 class="font-semibold">{props.isCreate ? "Create" : "Edit"} record</h2> 33 33 <div class="flex flex-col gap-1.5"> ··· 81 81 82 82 83 83 84 - 85 - 86 - 87 - 88 - 89 - 84 + <Button onClick={props.onClose}>Cancel</Button> 85 + <Button 86 + onClick={() => props.onConfirm(validate(), recreate())} 87 + classList={{ 88 + "bg-blue-500! text-white! border-none! hover:bg-blue-600! active:bg-blue-700! dark:bg-blue-600! dark:hover:bg-blue-500! dark:active:bg-blue-400!": true, 89 + }} 90 + > 91 + {props.isCreate ? "Create" : "Edit"} 90 92 </Button> 91 93 </div> 92 94 </div> 93 - </div> 95 + </> 94 96 ); 95 97 };
+11 -9
src/components/create/file-upload.tsx
··· 50 50 }; 51 51 52 52 return ( 53 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[20rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 53 + <> 54 54 <h2 class="mb-2 font-semibold">Upload blob</h2> 55 55 <div class="flex flex-col gap-2 text-sm"> 56 56 <div class="flex flex-col gap-1"> ··· 87 87 88 88 89 89 90 - 91 - 92 - 93 - 94 - 95 - 96 - 90 + <Show when={!uploading()}> 91 + <Button 92 + onClick={uploadBlob} 93 + classList={{ 94 + "bg-blue-500! text-white! border-none! hover:bg-blue-600! active:bg-blue-700! dark:bg-blue-600! dark:hover:bg-blue-500! dark:active:bg-blue-400!": true, 95 + }} 96 + > 97 + Upload 98 + </Button> 97 99 </Show> 98 100 </div> 99 101 </div> 100 - </div> 102 + </> 101 103 ); 102 104 };
+11 -9
src/components/create/handle-input.tsx
··· 40 40 }; 41 41 42 42 return ( 43 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[20rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 43 + <> 44 44 <h2 class="mb-2 font-semibold">Insert DID from handle</h2> 45 45 <form ref={handleFormRef} onSubmit={resolveDid} class="flex flex-col gap-2 text-sm"> 46 46 <div class="flex flex-col gap-1"> ··· 72 72 73 73 74 74 75 - 76 - 77 - 78 - 79 - 80 - 81 - 75 + <Show when={!resolving()}> 76 + <Button 77 + type="submit" 78 + classList={{ 79 + "bg-blue-500! text-white! border-none! hover:bg-blue-600! active:bg-blue-700! dark:bg-blue-600! dark:hover:bg-blue-500! dark:active:bg-blue-400!": true, 80 + }} 81 + > 82 + Insert 83 + </Button> 82 84 </Show> 83 85 </div> 84 86 </form> 85 - </div> 87 + </> 86 88 ); 87 89 };
+13 -8
src/components/modal.tsx
··· 6 6 closeOnClick?: boolean; 7 7 nonBlocking?: boolean; 8 8 alignTop?: boolean; 9 + contentClass?: string; 9 10 } 10 11 11 12 export const Modal = (props: ModalProps) => { ··· 14 15 15 16 16 17 17 - 18 - 19 - 20 - 21 - 22 - 23 - 18 + classList={{ 19 + "pointer-events-none": props.nonBlocking, 20 + "items-start pt-18": props.alignTop, 21 + "items-start pt-[20vh]": !props.alignTop, 22 + }} 23 + ref={(node) => { 24 + const handleEscape = (e: KeyboardEvent) => { 24 25 25 26 26 27 ··· 54 55 } 55 56 }} 56 57 > 57 - {props.children} 58 + <div 59 + class={`transition-all starting:scale-95 starting:opacity-0 ${props.contentClass ?? ""}`} 60 + > 61 + {props.children} 62 + </div> 58 63 </div> 59 64 </Show> 60 65 );
+19 -15
src/components/permission-prompt.tsx
··· 27 27 }; 28 28 29 29 return ( 30 - <Modal open={requestedScope() !== null} onClose={() => setRequestedScope(null)}> 31 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[calc(100%-2rem)] max-w-md rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 32 - <h2 class="mb-2 font-semibold">Permission required</h2> 33 - <p class="mb-4 text-sm text-neutral-600 dark:text-neutral-400"> 34 - You need the "{scopeLabel()}" permission to perform this action. 35 - </p> 36 - <div class="flex justify-end gap-2"> 37 - <Button onClick={() => setRequestedScope(null)}>Cancel</Button> 38 - <Button 39 - onClick={handleEditPermissions} 40 - class="dark:shadow-dark-700 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400" 41 - > 42 - Edit permissions 43 - </Button> 44 - </div> 30 + <Modal 31 + open={requestedScope() !== null} 32 + onClose={() => setRequestedScope(null)} 33 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[calc(100%-2rem)] max-w-md rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 34 + > 35 + <h2 class="mb-2 font-semibold">Permission required</h2> 36 + <p class="mb-4 text-sm text-neutral-600 dark:text-neutral-400"> 37 + You need the "{scopeLabel()}" permission to perform this action. 38 + </p> 39 + <div class="flex justify-end gap-2"> 40 + <Button onClick={() => setRequestedScope(null)}>Cancel</Button> 41 + <Button 42 + onClick={handleEditPermissions} 43 + classList={{ 44 + "bg-blue-500! text-white! hover:bg-blue-600! active:bg-blue-700! dark:bg-blue-600! dark:hover:bg-blue-500! dark:active:bg-blue-400! border-none!": true, 45 + }} 46 + > 47 + Edit permissions 48 + </Button> 45 49 </div> 46 50 </Modal> 47 51 );
+82 -19
src/views/collection.tsx
··· 37 37 38 38 39 39 40 + class="flex w-full min-w-0 items-baseline rounded px-1 py-0.5" 41 + trigger={ 42 + <> 43 + <span class="max-w-full shrink-0 truncate text-sm text-blue-500 dark:text-blue-400"> 44 + {props.record.rkey} 45 + </span> 46 + <span class="ml-1 truncate text-xs text-neutral-500 dark:text-neutral-400" dir="rtl"> 47 + {props.record.cid} 48 + </span> 40 49 41 50 42 51 ··· 290 299 291 300 292 301 302 + /> 303 + </Show> 304 + </div> 305 + <Modal 306 + open={openDelete()} 307 + onClose={() => setOpenDelete(false)} 308 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 309 + > 310 + <h2 class="mb-2 font-semibold"> 311 + {recreate() ? "Recreate" : "Delete"}{" "} 312 + {records.filter((r) => r.toDelete).length} records? 313 + </h2> 314 + <div class="flex justify-end gap-2"> 315 + <Button onClick={() => setOpenDelete(false)}>Cancel</Button> 316 + <Button 317 + onClick={deleteRecords} 318 + classList={{ 319 + "bg-blue-500! text-white! hover:bg-blue-600! active:bg-blue-700! dark:bg-blue-600! dark:hover:bg-blue-500! dark:active:bg-blue-400! border-none!": 320 + recreate(), 321 + "text-white! border-none! bg-red-500! hover:bg-red-600! active:bg-red-700!": 322 + !recreate(), 323 + }} 324 + > 325 + {recreate() ? "Recreate" : "Delete"} 326 + </Button> 327 + </div> 328 + </Modal> 329 + </Show> 293 330 294 331 295 332 ··· 297 334 298 335 299 336 300 - /> 337 + 338 + 339 + 340 + 341 + 342 + 343 + 344 + 345 + 346 + 347 + 348 + 349 + 350 + 351 + 352 + 353 + 354 + 355 + 356 + 357 + 358 + 359 + 360 + 361 + 362 + 363 + 364 + 365 + 366 + 367 + 368 + 369 + 370 + 371 + 372 + 373 + 374 + 375 + 376 + 377 + <Button onClick={() => refetch()}>Load more</Button> 378 + </Show> 379 + <Show when={response.loading}> 380 + <div class="iconify lucide--loader-circle w-20 animate-spin text-lg" /> 381 + </Show> 301 382 </Show> 302 383 </div> 303 - <Modal open={openDelete()} onClose={() => setOpenDelete(false)}> 304 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 305 - <h2 class="mb-2 font-semibold"> 306 - {recreate() ? "Recreate" : "Delete"}{" "} 307 - {records.filter((r) => r.toDelete).length} records? 308 - </h2> 309 - <div class="flex justify-end gap-2"> 310 - <Button onClick={() => setOpenDelete(false)}>Cancel</Button> 311 - <Button 312 - onClick={deleteRecords} 313 - class={`dark:shadow-dark-700 rounded-lg px-2 py-1.5 text-xs text-white shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-600 active:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700 dark:active:bg-green-800" : "bg-red-500 hover:bg-red-600 active:bg-red-700"}`} 314 - > 315 - {recreate() ? "Recreate" : "Delete"} 316 - </Button> 317 - </div> 318 - </div> 319 - </Modal> 320 - </Show>
+27 -24
src/layout.tsx
··· 11 11 12 12 13 13 14 + import { resolveHandle } from "./utils/api.js"; 15 + import { plcDirectory } from "./views/settings.jsx"; 14 16 17 + export const canHover = window.matchMedia("(hover: hover) and (pointer: fine)").matches; 15 18 19 + const headers: Record<string, string> = { 20 + "did:plc:ia76kvnndjutgedggx2ibrem": "bunny.jpg", 16 21 17 22 18 23 19 24 20 25 26 + "did:plc:p2cp5gopk7mgjegy6wadk3ep": "aurora.jpg", 27 + "did:plc:ucaezectmpny7l42baeyooxi": "almaty.webp", 28 + "did:plc:7rfssi44thh6f4ywcl3u5nvt": "sonic.jpg", 29 + "did:plc:6if5m2yo6kroprmmency3gt5": "montreal.webp", 30 + }; 21 31 32 + const Layout = (props: RouteSectionProps<unknown>) => { 22 33 23 34 24 35 ··· 117 128 118 129 119 130 131 + style={{ 132 + "background-image": 133 + props.params.repo && props.params.repo in headers ? 134 + `linear-gradient(to left, transparent 20%, var(--header-bg) 85%), url(/headers/${headers[props.params.repo]})` 135 + : undefined, 136 + }} 137 + > 120 138 121 139 122 140 ··· 130 148 131 149 132 150 133 - 134 - 135 - 136 - 137 - 138 - 139 - 140 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 149 - 150 - 151 - 152 - 153 - 154 - 155 - 151 + </Show> 152 + </A> 153 + <div class="relative flex items-center gap-0.5 rounded-lg bg-neutral-50/60 p-1 dark:bg-neutral-800/60"> 154 + <div class="mr-1"> 155 + <SearchButton /> 156 + </div> 157 + <Show when={agent()}> 158 + <RecordEditor create={true} scope="create" /> 156 159 </Show> 157 160 <AccountManager /> 158 161 <MenuProvider> 159 - <DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-lg p-1.5"> 162 + <DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-md p-1.5"> 160 163 <NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" /> 161 164 <NavMenu href="/firehose" label="Firehose" icon="lucide--rss" /> 162 165 <NavMenu href="/spacedust" label="Spacedust" icon="lucide--orbit" />
+3 -9
src/utils/app-urls.ts
··· 3 3 export enum App { 4 4 Bluesky, 5 5 Tangled, 6 - Frontpage, 7 6 Pinksea, 8 - Linkat, 7 + Frontpage, 9 8 } 10 9 11 10 export const appName = { 12 11 [App.Bluesky]: "Bluesky", 13 12 [App.Tangled]: "Tangled", 14 - [App.Frontpage]: "Frontpage", 15 13 [App.Pinksea]: "Pinksea", 16 - [App.Linkat]: "Linkat", 14 + [App.Frontpage]: "Frontpage", 17 15 }; 18 16 19 17 export const appList: Record<AppUrl, App> = { ··· 22 20 "bsky.app": App.Bluesky, 23 21 "catsky.social": App.Bluesky, 24 22 "deer.aylac.top": App.Bluesky, 25 - "deer-social-ayla.pages.dev": App.Bluesky, 26 23 "deer.social": App.Bluesky, 27 24 "main.bsky.dev": App.Bluesky, 28 - "social.daniela.lol": App.Bluesky, 29 25 "witchsky.app": App.Bluesky, 30 26 "tangled.org": App.Tangled, 31 27 "frontpage.fyi": App.Frontpage, 32 28 "pinksea.art": App.Pinksea, 33 - "linkat.blue": App.Linkat, 34 29 }; 35 30 36 31 export const appHandleLink: Record<App, (url: string[]) => string> = { ··· 53 48 return `at://${user}/app.bsky.graph.follow/${rkey}`; 54 49 } 55 50 } else { 56 - return `at://${user}`; 51 + return `at://${user}/app.bsky.actor.profile/self`; 57 52 } 58 53 } else if (baseType === "starter-pack") { 59 54 return `at://${user}/app.bsky.graph.starterpack/${path[2]}`; ··· 106 101 107 102 return `at://${path[0]}`; 108 103 }, 109 - [App.Linkat]: (path) => `at://${path[0]}/blue.linkat.board/self`, 110 104 };
+4 -4
src/auth/login.tsx
··· 32 32 }; 33 33 34 34 return ( 35 - <div class="flex flex-col gap-y-2 px-1"> 35 + <div class="flex flex-col gap-y-3"> 36 36 <Show when={!scopeFlow.showScopeSelector()}> 37 37 <Show when={props.onCancel}> 38 - <div class="mb-1 flex items-center gap-2"> 38 + <div class="flex items-center gap-2"> 39 39 <button 40 40 onclick={handleCancel} 41 41 class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" ··· 45 45 <div class="font-semibold">Add account</div> 46 46 </div> 47 47 </Show> 48 - <form class="flex flex-col gap-2" onsubmit={(e) => e.preventDefault()}> 48 + <form class="flex flex-col gap-3" onsubmit={(e) => e.preventDefault()}> 49 49 <label for="username" class="hidden"> 50 50 Add account 51 51 </label> ··· 69 69 </div> 70 70 <button 71 71 onclick={() => initiateLogin(loginInput())} 72 - class="grow rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100 px-3 py-2 hover:bg-neutral-200 active:bg-neutral-300 dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 72 + class="dark:hover:bg-dark-200 dark:active:bg-dark-100 flex w-full items-center justify-center gap-2 rounded-lg border border-neutral-200 px-3 py-2 hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 73 73 > 74 74 Continue 75 75 </button>
+36 -26
src/auth/scope-selector.tsx
··· 44 44 }; 45 45 46 46 return ( 47 - <div class="flex flex-col gap-y-2"> 48 - <div class="mb-1 flex items-center gap-2"> 47 + <div class="flex flex-col gap-y-3"> 48 + <div class="flex items-center gap-2"> 49 49 <button 50 50 onclick={props.onCancel} 51 51 class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 52 52 53 53 54 - 55 - 56 - 57 - 58 - 59 - 60 - 61 - 62 - 63 - 64 - 65 - 66 - 67 - 68 - 69 - 70 - 71 - 72 - 73 - 74 - 75 - 76 - 54 + </button> 55 + <div class="font-semibold">Select permissions</div> 56 + </div> 57 + <div class="flex flex-col px-1"> 58 + <For each={GRANULAR_SCOPES}> 59 + {(scope) => { 60 + const isSelected = () => selectedScopes().has(scope.id); 61 + const isDisabled = () => scope.id === "blob" && isBlobDisabled(); 62 + 63 + return ( 64 + <button 65 + onclick={() => !isDisabled() && toggleScope(scope.id)} 66 + disabled={isDisabled()} 67 + class="group flex items-center gap-3 py-1.5" 68 + classList={{ "opacity-50": isDisabled() }} 69 + > 70 + <div 71 + class="flex size-5 items-center justify-center rounded border-2" 72 + classList={{ 73 + "bg-blue-500 border-transparent group-hover:bg-blue-600 group-active:bg-blue-400": 74 + isSelected() && !isDisabled(), 75 + "border-neutral-400 dark:border-neutral-500 group-hover:border-neutral-500 dark:group-hover:border-neutral-400 group-hover:bg-neutral-100 dark:group-hover:bg-neutral-800": 76 + !isSelected() && !isDisabled(), 77 + "border-neutral-300 dark:border-neutral-600": isDisabled(), 78 + }} 79 + > 80 + {isSelected() && <span class="iconify lucide--check text-sm text-white"></span>} 81 + </div> 82 + <span>{scope.label}</span> 83 + </button> 84 + ); 85 + }} 86 + </For> 77 87 </div> 78 88 <button 79 89 onclick={handleConfirm} 80 - class="mt-2 grow rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100 px-3 py-2 hover:bg-neutral-200 active:bg-neutral-300 dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 90 + class="dark:hover:bg-dark-200 dark:active:bg-dark-100 flex w-full items-center justify-center gap-2 rounded-lg border border-neutral-200 px-3 py-2 hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 81 91 > 82 92 Continue 83 93 </button>
-14
src/utils/templates.ts
··· 37 37 link: `https://pinksea.art/${uri.repo}`, 38 38 icon: "i-pinksea", 39 39 }), 40 - "blue.linkat.board": (uri) => ({ 41 - label: "Linkat", 42 - link: `https://linkat.blue/${uri.repo}`, 43 - }), 44 40 "sh.tangled.actor.profile": (uri) => ({ 45 41 label: "Tangled", 46 42 link: `https://tangled.org/${uri.repo}`, ··· 51 47 link: `https://tangled.org/${uri.repo}/${record.name}`, 52 48 icon: "i-tangled", 53 49 }), 54 - "pub.leaflet.document": (uri) => ({ 55 - label: "Leaflet", 56 - link: `https://leaflet.pub/p/${uri.repo}/${uri.rkey}`, 57 - icon: "iconify-color i-leaflet", 58 - }), 59 - "pub.leaflet.publication": (uri) => ({ 60 - label: "Leaflet", 61 - link: `https://leaflet.pub/lish/${uri.repo}/${uri.rkey}`, 62 - icon: "iconify-color i-leaflet", 63 - }), 64 50 };
-12
src/utils/types/lexicons.ts
··· 17 17 AppBskyLabelerService, 18 18 ChatBskyActorDeclaration, 19 19 } from "@atcute/bluesky"; 20 - import { 21 - PubLeafletComment, 22 - PubLeafletDocument, 23 - PubLeafletGraphSubscription, 24 - PubLeafletPublication, 25 - } from "@atcute/leaflet"; 26 20 import { 27 21 ShTangledActorProfile, 28 22 ShTangledFeedStar, ··· 85 79 "sh.tangled.repo.pull.status.merged": ShTangledRepoPullStatusMerged.mainSchema, 86 80 "sh.tangled.repo.pull.status.open": ShTangledRepoPullStatusOpen.mainSchema, 87 81 "sh.tangled.knot": ShTangledKnot.mainSchema, 88 - 89 - // Leaflet 90 - "pub.leaflet.comment": PubLeafletComment.mainSchema, 91 - "pub.leaflet.document": PubLeafletDocument.mainSchema, 92 - "pub.leaflet.graph.subscription": PubLeafletGraphSubscription.mainSchema, 93 - "pub.leaflet.publication": PubLeafletPublication.mainSchema, 94 82 };
+2
.gitignore
··· 2 2 dist 3 3 .env 4 4 .DS_Store 5 + public/oauth-client-metadata.json 6 + public/opensearch.xml
-13
public/oauth-client-metadata.json
··· 1 - { 2 - "client_id": "https://pdsls.dev/oauth-client-metadata.json", 3 - "client_name": "PDSls", 4 - "client_uri": "https://pdsls.dev", 5 - "logo_uri": "https://pdsls.dev/favicon.ico", 6 - "redirect_uris": ["https://pdsls.dev/"], 7 - "scope": "atproto repo:*?action=create repo:*?action=update repo:*?action=delete blob:*/*", 8 - "grant_types": ["authorization_code", "refresh_token"], 9 - "response_types": ["code"], 10 - "token_endpoint_auth_method": "none", 11 - "application_type": "web", 12 - "dpop_bound_access_tokens": true 13 - }
public/avatar/bad-example.com.jpg

This is a binary file and will not be displayed.

public/avatar/futur.blue.jpg

This is a binary file and will not be displayed.

public/avatar/hailey.at.jpg

This is a binary file and will not be displayed.

public/avatar/jaz.sh.jpg

This is a binary file and will not be displayed.

public/avatar/jcsalterego.bsky.social.jpg

This is a binary file and will not be displayed.

public/avatar/juli.ee.jpg

This is a binary file and will not be displayed.

public/avatar/mary.my.id.jpg

This is a binary file and will not be displayed.

public/avatar/retr0.id.jpg

This is a binary file and will not be displayed.

+21 -18
src/components/favicon.tsx
··· 1 - import { createSignal, JSX, Show } from "solid-js"; 1 + import { createSignal, JSX, Match, Show, Switch } from "solid-js"; 2 2 3 3 export const Favicon = (props: { 4 4 authority: string; ··· 8 8 const domain = () => props.authority.split(".").reverse().join("."); 9 9 10 10 const content = ( 11 - <> 12 - <Show when={!loaded()}> 13 - <span class="iconify lucide--globe size-4 text-neutral-400 dark:text-neutral-500" /> 14 - </Show> 15 - <img 16 - src={ 17 - ["bsky.app", "bsky.chat"].includes(domain()) ? 18 - "https://web-cdn.bsky.app/static/apple-touch-icon.png" 19 - : `https://${domain()}/favicon.ico` 20 - } 21 - alt="" 22 - class="h-4 w-4" 23 - classList={{ hidden: !loaded() }} 24 - onLoad={() => setLoaded(true)} 25 - onError={() => setLoaded(false)} 26 - /> 27 - </> 11 + <Switch> 12 + <Match when={domain() === "tangled.sh"}> 13 + <span class="iconify i-tangled size-4" /> 14 + </Match> 15 + <Match when={["bsky.app", "bsky.chat"].includes(domain())}> 16 + <img src="https://web-cdn.bsky.app/static/apple-touch-icon.png" class="size-4" /> 17 + </Match> 18 + <Match when={true}> 19 + <Show when={!loaded()}> 20 + <span class="iconify lucide--globe size-4 text-neutral-400 dark:text-neutral-500" /> 21 + </Show> 22 + <img 23 + src={`https://${domain()}/favicon.ico`} 24 + class="size-4" 25 + classList={{ hidden: !loaded() }} 26 + onLoad={() => setLoaded(true)} 27 + onError={() => setLoaded(false)} 28 + /> 29 + </Match> 30 + </Switch> 28 31 ); 29 32 30 33 return props.wrapper ?
+84
src/views/labels.tsx
··· 200 200 <> 201 201 <Title>Labels - PDSls</Title> 202 202 <div class="flex w-full flex-col items-center"> 203 + <div class="flex w-full flex-col gap-y-1 px-3 pb-3"> 204 + <h1 class="text-lg font-semibold">Labels</h1> 205 + <p class="text-sm text-neutral-600 dark:text-neutral-400"> 206 + Query labels applied by labelers to accounts and records. 207 + </p> 208 + </div> 203 209 <form 204 210 ref={formRef} 205 211 class="flex w-full max-w-3xl flex-col gap-y-3 px-3 pb-2" 212 + 213 + 214 + 215 + 216 + 217 + 218 + 219 + 220 + 221 + 222 + 223 + 224 + 225 + 226 + 227 + 228 + 229 + 230 + 231 + 232 + 233 + 234 + 235 + 236 + 237 + 238 + 239 + 240 + 241 + 242 + 243 + 244 + 245 + 246 + 247 + 248 + 249 + 250 + 251 + 252 + 253 + 254 + 255 + 256 + 257 + 258 + 259 + 260 + 261 + 262 + 263 + 264 + 265 + 266 + 267 + 268 + 269 + 270 + 271 + 272 + 273 + 274 + 275 + 276 + 277 + <Button 278 + onClick={handleLoadMore} 279 + disabled={loading()} 280 + classList={{ "w-20 h-7.5 justify-center": true }} 281 + > 282 + <Show 283 + when={!loading()} 284 + fallback={ 285 + <span class="iconify lucide--loader-circle animate-spin text-base" /> 286 + } 287 + > 288 + Load more 289 + </Show>
public/avatar/aylac.top.jpg

This is a binary file and will not be displayed.

public/avatar/computer.fish.jpg

This is a binary file and will not be displayed.

public/avatar/dreary.blacksky.app.jpg

This is a binary file and will not be displayed.

public/avatar/emilia.wtf.jpg

This is a binary file and will not be displayed.

public/avatar/futanari.observer.jpg

This is a binary file and will not be displayed.

public/avatar/mofu.run.jpg

This is a binary file and will not be displayed.

public/avatar/natalie.sh.jpg

This is a binary file and will not be displayed.

public/avatar/nekomimi.pet.jpg

This is a binary file and will not be displayed.

public/avatar/nullekko.moe.jpg

This is a binary file and will not be displayed.

public/avatar/paizuri.moe.jpg

This is a binary file and will not be displayed.

public/avatar/quilling.dev.jpg

This is a binary file and will not be displayed.

public/avatar/rainy.pet.jpg

This is a binary file and will not be displayed.

public/avatar/sapphic.moe.jpg

This is a binary file and will not be displayed.

public/avatar/blooym.dev.jpg

This is a binary file and will not be displayed.

public/avatar/isabelroses.com.jpg

This is a binary file and will not be displayed.

public/avatar/isuggest.selfce.st.jpg

This is a binary file and will not be displayed.

public/avatar/anyaustin.bsky.social.jpg

This is a binary file and will not be displayed.

public/avatar/claire.on-her.computer.jpg

This is a binary file and will not be displayed.

public/avatar/cwonus.org.jpg

This is a binary file and will not be displayed.

public/avatar/number-one-warned.rat.mom.jpg

This is a binary file and will not be displayed.

public/avatar/olaren.dev.jpg

This is a binary file and will not be displayed.

public/avatar/coil-habdle.ebil.club.jpg

This is a binary file and will not be displayed.

+37 -42
package.json
··· 1 1 2 2 3 3 4 - 5 - 6 - 7 - 8 - 9 - 4 + "type": "module", 5 + "scripts": { 6 + "start": "vite", 7 + "predev": "node scripts/generate-metadata.js", 8 + "dev": "vite", 9 + "prebuild": "node scripts/generate-metadata.js", 10 10 "build": "vite build", 11 11 "serve": "vite preview" 12 12 }, 13 - "pnpm": { 14 - "overrides": { 15 - "seroval": "^1.4.1" 16 - } 17 - }, 18 13 "devDependencies": { 19 - "@iconify-json/lucide": "^1.2.86", 14 + "@iconify-json/lucide": "^1.2.89", 20 15 "@iconify/tailwind4": "^1.2.1", 21 16 "@tailwindcss/vite": "^4.1.18", 22 - "prettier": "^3.8.0", 17 + "prettier": "^3.8.1", 23 18 "prettier-plugin-organize-imports": "^4.3.0", 24 19 "prettier-plugin-tailwindcss": "^0.7.2", 25 20 "tailwindcss": "^4.1.18", 26 21 27 22 28 23 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 - 48 - 49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 24 + }, 25 + "dependencies": { 26 + "@atcute/atproto": "^3.1.10", 27 + "@atcute/bluesky": "^3.2.17", 28 + "@atcute/car": "^5.1.0", 29 + "@atcute/cbor": "^2.3.0", 30 + "@atcute/cid": "^2.4.0", 31 + 32 + 33 + "@atcute/did-plc": "^0.3.1", 34 + "@atcute/identity": "^1.1.3", 35 + "@atcute/identity-resolver": "^1.2.2", 36 + "@atcute/lexicon-doc": "^2.1.0", 37 + "@atcute/lexicon-resolver": "^0.1.6", 38 + "@atcute/lexicons": "^1.2.7", 39 + "@atcute/multibase": "^1.1.7", 40 + "@atcute/oauth-browser-client": "^3.0.0", 41 + "@atcute/repo": "^0.1.1", 42 + "@atcute/tangled": "^1.0.16", 43 + "@atcute/tid": "^1.1.1", 44 + "@codemirror/commands": "^6.10.1", 45 + "@codemirror/lang-json": "^6.0.2", 46 + "@codemirror/lint": "^6.9.3", 47 + "@codemirror/state": "^6.5.4", 48 + "@codemirror/view": "^6.39.12", 49 + "@fsegurai/codemirror-theme-basic-dark": "^6.2.3", 50 + "@fsegurai/codemirror-theme-basic-light": "^6.2.3", 51 + "@mary/exif-rm": "jsr:^0.2.2", 57 52 58 53 59 54 60 55 "@solidjs/router": "^0.15.4", 61 56 "codemirror": "^6.0.2", 62 57 "native-file-system-adapter": "^3.0.1", 63 - "solid-js": "^1.9.10" 58 + "solid-js": "^1.9.11" 64 59 }, 65 60 "packageManager": "pnpm@10.17.1+sha512.17c560fca4867ae9473a3899ad84a88334914f379be46d455cbf92e5cf4b39d34985d452d2583baf19967fa76cb5c17bc9e245529d0b98745721aa7200ecaf7a" 66 61 }
+2 -2
src/components/backlinks.tsx
··· 46 46 }); 47 47 48 48 return ( 49 - <Show when={links()} fallback={<p class="px-3 py-2 text-neutral-500">Loadingโ€ฆ</p>}> 49 + <Show when={links()} fallback={<p class="px-3 py-2 text-center text-neutral-500">Loadingโ€ฆ</p>}> 50 50 <For each={links()!.linking_records}> 51 51 {({ did, collection, rkey }) => { 52 52 const timestamp = ··· 91 91 <div class="p-2"> 92 92 <Button 93 93 onClick={() => setMore(true)} 94 - class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 w-full items-center justify-center gap-1 rounded border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 94 + class="dark:hover:bg-dark-200 dark:active:bg-dark-100 w-full rounded-md border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 95 95 > 96 96 Load more 97 97 </Button>
+3 -1
src/components/button.tsx
··· 6 6 class?: string; 7 7 classList?: Record<string, boolean | undefined>; 8 8 onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>; 9 + ontouchstart?: (e: TouchEvent) => void; 9 10 children?: JSX.Element; 10 11 } 11 12 ··· 16 17 disabled={props.disabled ?? false} 17 18 class={ 18 19 props.class ?? 19 - "dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 20 + "dark:bg-dark-300 dark:hover:bg-dark-200 dark:active:bg-dark-100 flex items-center gap-1 rounded-md border border-neutral-200 bg-neutral-50 px-2.5 py-1.5 text-xs text-neutral-700 transition-colors select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:text-neutral-300" 20 21 } 21 22 classList={props.classList} 22 23 onClick={props.onClick} 24 + ontouchstart={props.ontouchstart} 23 25 > 24 26 {props.children} 25 27 </button>
+82 -40
src/components/json.tsx
··· 4 4 5 5 6 6 7 + ErrorBoundary, 8 + For, 9 + on, 10 + onCleanup, 11 + onMount, 12 + Show, 13 + useContext, 14 + } from "solid-js"; 7 15 8 16 9 17 ··· 251 259 252 260 253 261 262 + !ctx.hideBlobs && 263 + (blob.mimeType.startsWith("image/") || blob.mimeType === "video/mp4"); 254 264 265 + const MediaDisplay = () => { 266 + const [imageObjectUrl, setImageObjectUrl] = createSignal<string>(); 255 267 268 + onMount(() => { 269 + if (blob.mimeType.startsWith("image/")) { 270 + const fetchImage = async () => { 271 + const res = await fetch( 272 + `https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${ctx.repo}&cid=${blob.ref.$link}`, 273 + ); 274 + if (!res.ok) throw new Error(res.statusText); 275 + const blobData = await res.blob(); 276 + const url = URL.createObjectURL(blobData); 277 + setImageObjectUrl(url); 278 + }; 279 + fetchImage().catch((err) => console.error("Failed to load image:", err)); 280 + } 281 + }); 256 282 283 + onCleanup(() => { 284 + if (imageObjectUrl()) URL.revokeObjectURL(imageObjectUrl()!); 285 + }); 257 286 287 + return ( 288 + <div> 289 + <span class="group/media relative flex w-fit"> 290 + <Show when={!hide()}> 291 + <Show when={blob.mimeType.startsWith("image/")}> 292 + <Show 293 + when={imageObjectUrl()} 294 + fallback={ 295 + <div class="flex h-48 w-48 items-center justify-center rounded bg-neutral-200 dark:bg-neutral-800"> 296 + <span class="iconify lucide--loader-circle animate-spin text-xl text-neutral-400 dark:text-neutral-500"></span> 297 + </div> 298 + } 299 + > 300 + <img 301 + class="h-auto max-h-48 max-w-64 object-contain" 302 + src={imageObjectUrl()} 303 + onLoad={() => setMediaLoaded(true)} 304 + /> 305 + </Show> 306 + </Show> 307 + <Show when={blob.mimeType === "video/mp4"}> 308 + <ErrorBoundary fallback={() => <span>Failed to load video</span>}> 309 + <VideoPlayer 310 + did={ctx.repo} 311 + cid={blob.ref.$link} 312 + onLoad={() => setMediaLoaded(true)} 313 + /> 314 + </ErrorBoundary> 315 + </Show> 316 + <Show when={mediaLoaded()}> 317 + <button 318 + onclick={() => setHide(true)} 319 + class="absolute top-1 right-1 flex items-center rounded-lg bg-neutral-700/70 p-1.5 text-white opacity-0 backdrop-blur-sm transition-opacity group-hover/media:opacity-100 hover:bg-neutral-700 active:bg-neutral-800 dark:bg-neutral-100/70 dark:text-neutral-900 dark:hover:bg-neutral-100 dark:active:bg-neutral-200" 320 + > 321 + <span class="iconify lucide--eye-off text-base"></span> 322 + </button> 323 + </Show> 324 + </Show> 325 + <Show when={hide()}> 326 + <button 327 + onclick={() => setHide(false)} 328 + class="flex items-center gap-1 rounded-md bg-neutral-200 px-2 py-1.5 text-sm transition-colors hover:bg-neutral-300 active:bg-neutral-400 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:active:bg-neutral-500" 329 + > 330 + <span class="iconify lucide--image"></span> 331 + <span class="font-sans">Show media</span> 332 + </button> 333 + </Show> 334 + </span> 335 + </div> 336 + ); 337 + }; 258 338 259 - 260 - 261 - 262 - 263 - 264 - 265 - 266 - 267 - 268 - 269 - 270 - 271 - 272 - 273 - 274 - 275 - 276 - 277 - 278 - 279 - 280 - 281 - 282 - 283 - 284 - 285 - 286 - 287 - 288 - 289 - 290 - 291 - 292 - <Show when={hide()}> 293 - <button 294 - onclick={() => setHide(false)} 295 - class="flex items-center gap-1 rounded-lg bg-neutral-200 px-2 py-1.5 text-sm transition-colors hover:bg-neutral-300 active:bg-neutral-400 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:active:bg-neutral-500" 296 - > 297 - <span class="iconify lucide--image"></span> 298 - <span class="font-sans">Show media</span> 339 + if (blob.$type === "blob") { 340 + return (
+1 -1
src/components/notification.tsx
··· 87 87 </Show> 88 88 <Show when={notification.onCancel}> 89 89 <button 90 - class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 mt-1 rounded border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 90 + class="dark:hover:bg-dark-200 dark:active:bg-dark-100 dark:bg-dark-300 mt-1 rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 91 91 onClick={(e) => { 92 92 e.stopPropagation(); 93 93 notification.onCancel?.();
+1 -1
src/components/text-input.tsx
··· 25 25 disabled={props.disabled} 26 26 required={props.required} 27 27 class={ 28 - "dark:bg-dark-100 rounded-lg bg-white px-2 py-1 outline-1 outline-neutral-200 select-none placeholder:text-sm focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400 " + 28 + "dark:bg-dark-100 rounded-md bg-white px-2 py-1 outline-1 outline-neutral-200 select-none placeholder:text-sm focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400 " + 29 29 props.class 30 30 } 31 31 onInput={props.onInput}
+24 -13
src/views/blob.tsx
··· 2 2 3 3 4 4 5 + const LIMIT = 1000; 5 6 7 + export const BlobView = (props: { pds: string; repo: string }) => { 8 + const [cursor, setCursor] = createSignal<string>(); 9 + let rpc: Client; 6 10 7 11 8 12 ··· 39 43 40 44 41 45 42 - 43 - 44 - 45 - 46 - 47 - 48 - 49 - 46 + </div> 47 + </Show> 48 + <div class="dark:bg-dark-500 fixed bottom-0 z-5 flex w-screen justify-center bg-neutral-100 pt-2 pb-4"> 49 + <div class="flex min-w-50 items-center justify-around gap-3 pb-2"> 50 50 <p> 51 51 {blobs()?.length} blob{(blobs()?.length ?? 0 > 1) ? "s" : ""} 52 52 </p> 53 - <Show when={!response.loading && cursor()}> 54 - <Button onClick={() => refetch()}>Load more</Button> 55 - </Show> 56 - <Show when={response.loading}> 57 - <span class="iconify lucide--loader-circle animate-spin py-3.5 text-xl"></span> 53 + <Show when={cursor()}> 54 + <Button 55 + onClick={() => refetch()} 56 + disabled={response.loading} 57 + classList={{ "w-20 h-7.5 justify-center": true }} 58 + > 59 + <Show 60 + when={!response.loading} 61 + fallback={<span class="iconify lucide--loader-circle animate-spin text-base" />} 62 + > 63 + Load more 64 + </Show> 65 + </Button> 58 66 </Show> 59 67 </div> 60 68 </div> 69 + </div> 70 + ); 71 + };
+1 -1
src/views/car/shared.tsx
··· 123 123 </p> 124 124 <p class="text-xs text-neutral-500 dark:text-neutral-400">or</p> 125 125 </div> 126 - <label class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-8 items-center justify-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-3 py-1.5 text-sm shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"> 126 + <label class="dark:bg-dark-300 dark:hover:bg-dark-200 dark:active:bg-dark-100 flex items-center gap-1 rounded-md border border-neutral-300 bg-neutral-50 px-2.5 py-1.5 text-sm text-neutral-700 transition-colors select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:text-neutral-300"> 127 127 <input 128 128 type="file" 129 129 accept={isIOS ? undefined : ".car,application/vnd.ipld.car"}
+87 -36
src/views/car/explore.tsx
··· 2 2 3 3 4 4 5 + import { fromStream, isCommit } from "@atcute/repo"; 6 + import * as TID from "@atcute/tid"; 7 + import { Title } from "@solidjs/meta"; 8 + import { useLocation, useNavigate } from "@solidjs/router"; 9 + import { 10 + createEffect, 11 + createMemo, 5 12 6 13 7 14 ··· 24 31 25 32 26 33 34 + WelcomeView, 35 + } from "./shared.jsx"; 27 36 37 + const viewToHash = (view: View): string => { 38 + switch (view.type) { 39 + case "repo": 40 + return ""; 41 + case "collection": 42 + return `#${view.collection.name}`; 43 + case "record": 44 + return `#${view.collection.name}/${view.record.key}`; 45 + } 46 + }; 28 47 48 + const hashToView = (hash: string, archive: Archive): View => { 49 + if (!hash || hash === "#") return { type: "repo" }; 29 50 51 + const raw = hash.startsWith("#") ? hash.slice(1) : hash; 52 + const slashIdx = raw.indexOf("/"); 30 53 54 + if (slashIdx === -1) { 55 + const collection = archive.entries.find((e) => e.name === raw); 56 + if (collection) return { type: "collection", collection }; 57 + return { type: "repo" }; 58 + } 31 59 60 + const collectionName = raw.slice(0, slashIdx); 61 + const recordKey = raw.slice(slashIdx + 1); 62 + const collection = archive.entries.find((e) => e.name === collectionName); 63 + if (collection) { 64 + const record = collection.entries.find((r) => r.key === recordKey); 65 + if (record) return { type: "record", collection, record }; 66 + return { type: "collection", collection }; 67 + } 32 68 69 + return { type: "repo" }; 70 + }; 33 71 72 + export const ExploreToolView = () => { 73 + const location = useLocation(); 74 + const navigate = useNavigate(); 34 75 76 + const [archive, setArchive] = createSignal<Archive | null>(null); 77 + const [loading, setLoading] = createSignal(false); 78 + const [progress, setProgress] = createSignal(0); 79 + const [error, setError] = createSignal<string>(); 35 80 81 + const view = createMemo((): View => { 82 + const arch = archive(); 83 + if (!arch) return { type: "repo" }; 84 + return hashToView(location.hash, arch); 85 + }); 36 86 87 + const navigateToView = (newView: View) => { 88 + const hash = viewToHash(newView); 89 + navigate(`${location.pathname}${hash}`); 90 + }; 37 91 92 + const parseCarFile = async (file: File) => { 93 + setLoading(true); 38 94 39 95 40 96 ··· 111 167 112 168 113 169 170 + } 114 171 172 + setArchive(result); 173 + if (location.hash) navigate(location.pathname, { replace: true }); 174 + } catch (err) { 175 + console.error("Failed to parse CAR file:", err); 176 + setError(err instanceof Error ? err.message : "Failed to parse CAR file"); 115 177 116 178 117 179 ··· 120 182 121 183 122 184 185 + const reset = () => { 186 + setArchive(null); 187 + setError(undefined); 188 + if (location.hash) navigate(location.pathname, { replace: true }); 189 + }; 123 190 191 + return ( 124 192 125 193 126 194 ··· 135 203 136 204 137 205 206 + /> 207 + } 208 + > 209 + {(arch) => ( 210 + <ExploreView archive={arch()} view={view} setView={navigateToView} onClose={reset} /> 211 + )} 212 + </Show> 213 + </> 214 + ); 138 215 139 216 140 217 ··· 491 568 492 569 493 570 571 + return ( 572 + <HoverCard 573 + class="flex w-full items-baseline gap-1 rounded hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 574 + trigger={ 575 + <button 576 + onClick={() => { 494 577 495 578 496 579 497 - 498 - 499 - 500 - 501 - 502 - 503 - 504 - 505 - 506 - 507 - 508 - 509 - 510 - 511 - 512 - 513 - 514 - 515 - 516 - 517 - 518 - 519 - 520 - 521 - 522 - 523 - 524 - 525 - 526 - 527 - 528 - 529 - 530 - 580 + record: entry, 581 + }); 531 582 }} 532 - class="flex w-full items-baseline gap-1 text-left" 583 + class="flex w-full items-baseline gap-1 px-1 py-0.5" 533 584 > 534 - <span class="shrink-0 text-sm text-blue-500 dark:text-blue-400"> 585 + <span class="max-w-full shrink-0 truncate text-sm text-blue-500 dark:text-blue-400"> 535 586 {entry.key} 536 587 </span> 537 588 <span class="truncate text-xs text-neutral-500 dark:text-neutral-400" dir="rtl">
+183 -2
src/utils/api.ts
··· 62 62 throw new Error("Not a valid DID identifier"); 63 63 } 64 64 65 - const doc = await didDocumentResolver().resolve(did); 66 - didDocCache[did] = doc; 65 + let doc: DidDocument; 66 + try { 67 + doc = await didDocumentResolver().resolve(did); 68 + didDocCache[did] = doc; 69 + } catch (e) { 70 + console.error(e); 71 + throw new Error("Error during did document resolution"); 72 + } 67 73 68 74 const pds = getPdsEndpoint(doc); 69 75 const labeler = getLabelerEndpoint(doc); 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 100 + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 + 120 + 121 + 122 + 123 + 124 + 125 + 126 + 127 + 128 + 129 + 130 + 131 + 132 + 133 + 134 + 135 + 136 + 137 + 138 + 139 + 140 + 141 + 142 + 143 + 144 + 145 + 146 + 147 + 148 + 149 + 150 + 151 + 152 + 153 + 154 + 155 + 156 + 157 + 158 + 159 + 160 + 161 + 162 + 163 + 164 + 165 + 166 + 167 + 168 + 169 + 170 + 171 + 172 + 173 + 174 + 175 + 176 + 177 + 178 + 179 + 180 + 181 + 182 + 183 + 184 + 185 + 186 + 187 + 188 + 189 + 190 + 191 + 192 + 193 + 194 + 195 + 196 + 197 + 198 + 199 + 200 + 201 + 202 + 203 + 204 + 205 + 206 + 207 + 208 + 209 + 210 + 211 + 212 + 213 + 214 + 215 + ): Promise<LinksWithRecords> => 216 + getConstellation("/links", target, collection, path, cursor, limit || 100); 217 + 218 + export interface HandleResolveResult { 219 + success: boolean; 220 + did?: string; 221 + error?: string; 222 + } 223 + 224 + export const resolveHandleDetailed = async (handle: Handle) => { 225 + const dnsResolver = new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }); 226 + const httpResolver = new WellKnownHandleResolver(); 227 + 228 + const tryResolve = async ( 229 + resolver: DohJsonHandleResolver | WellKnownHandleResolver, 230 + timeoutMs: number = 5000, 231 + ): Promise<HandleResolveResult> => { 232 + try { 233 + const timeoutPromise = new Promise<never>((_, reject) => 234 + setTimeout(() => reject(new Error("Request timed out")), timeoutMs), 235 + ); 236 + const did = await Promise.race([resolver.resolve(handle), timeoutPromise]); 237 + return { success: true, did }; 238 + } catch (err: any) { 239 + return { success: false, error: err.message ?? String(err) }; 240 + } 241 + }; 242 + 243 + const [dns, http] = await Promise.all([tryResolve(dnsResolver), tryResolve(httpResolver)]); 244 + 245 + return { dns, http }; 246 + }; 247 + 248 + export { 249 + didDocCache, 250 + getAllBacklinks,
+10 -1
index.html
··· 6 6 <link rel="icon" href="/favicon.ico" /> 7 7 <meta property="og:title" content="PDSls" /> 8 8 <meta property="og:type" content="website" /> 9 - <meta property="og:url" content="https://pdsls.dev" /> 9 + <meta property="og:url" content="https://pds.ls" /> 10 10 <meta property="og:description" content="Browse the public data on atproto" /> 11 11 <meta property="description" content="Browse the public data on atproto" /> 12 12 <link rel="manifest" href="/manifest.json" /> 13 + <link 14 + rel="search" 15 + type="application/opensearchdescription+xml" 16 + href="/opensearch.xml" 17 + title="PDSls" 18 + /> 19 + <link rel="preconnect" href="https://fonts.bunny.net" /> 20 + <link href="https://fonts.bunny.net/css?family=roboto-mono:400" rel="stylesheet" /> 21 + <link href="https://fonts.cdnfonts.com/css/pecita" rel="stylesheet" />
+87 -41
src/components/navbar.tsx
··· 1 + import * as TID from "@atcute/tid"; 2 + import { A, Params } from "@solidjs/router"; 3 + import { createEffect, createMemo, createSignal, Show } from "solid-js"; 4 + import { canHover } from "../layout"; 5 + import { didDocCache } from "../utils/api"; 6 + import { addToClipboard } from "../utils/copy"; 7 + import { localDateFromTimestamp } from "../utils/date"; 1 8 2 9 3 10 4 11 12 + const CopyButton = (props: { content: string; label: string }) => { 13 + return ( 14 + <Show when={canHover}> 15 + <Tooltip text={props.label}> 16 + <button 17 + type="button" 5 18 6 19 7 20 ··· 17 30 18 31 19 32 33 + export const NavBar = (props: { params: Params }) => { 34 + const [handle, setHandle] = createSignal(props.params.repo); 35 + const [repoHovered, setRepoHovered] = createSignal(false); 36 + const [hasHoveredRepo, setHasHoveredRepo] = createSignal(false); 37 + const [faviconLoaded, setFaviconLoaded] = createSignal(false); 38 + const isCustomDomain = () => handle() && !handle()!.endsWith(".bsky.social"); 20 39 40 + createEffect(() => { 41 + if (pds() !== undefined && props.params.repo) { 21 42 22 43 23 44 24 45 25 46 47 + } 48 + }); 26 49 50 + createEffect(() => { 51 + handle(); 52 + setHasHoveredRepo(false); 53 + setFaviconLoaded(false); 54 + }); 27 55 56 + const rkeyTimestamp = createMemo(() => { 57 + if (!props.params.rkey || !TID.validate(props.params.rkey)) return undefined; 58 + const timestamp = TID.parse(props.params.rkey).timestamp / 1000; 28 59 29 60 30 61 ··· 34 65 35 66 36 67 68 + <span 69 + classList={{ 70 + "iconify shrink-0 transition-colors duration-200": true, 71 + "lucide--unplug text-red-500 dark:text-red-400": 72 + pds() === "Missing PDS" && props.params.repo?.startsWith("did:"), 73 + "lucide--hard-drive text-neutral-500 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200": 74 + pds() !== "Missing PDS" || !props.params.repo?.startsWith("did:"), 37 75 38 76 39 77 ··· 64 102 65 103 66 104 67 - 68 - 69 - 70 - 71 - 72 - 73 - 74 - 75 - 76 - 77 - 78 - 79 - 80 - 81 - 82 - 83 - 84 - 85 - 86 - 87 - 88 - 89 - 90 - 91 - 92 - 93 - 94 - 95 - 96 - 97 - 98 - 99 - 100 - 101 - 102 - 103 - 104 - 105 + <div class="flex flex-col"> 106 + <Show when={props.params.repo}> 107 + {/* Repository Level */} 108 + <div 109 + class="group relative flex items-center justify-between gap-1 rounded-md border-[0.5px] border-transparent bg-transparent px-2 transition-all duration-200 hover:border-neutral-300 hover:bg-neutral-50/40 dark:hover:border-neutral-600 dark:hover:bg-neutral-800/40" 110 + onMouseEnter={() => { 111 + if (canHover) { 112 + setRepoHovered(true); 113 + setHasHoveredRepo(true); 114 + } 115 + }} 116 + onMouseLeave={() => { 117 + if (canHover) { 118 + setRepoHovered(false); 119 + } 120 + }} 121 + > 122 + <div class="flex min-w-0 basis-full items-center gap-2"> 123 + <Tooltip text="Repository"> 124 + <div class="relative flex h-5 w-4 shrink-0 items-center justify-center"> 125 + <span 126 + class="iconify lucide--book-user absolute text-neutral-500 transition-colors duration-200 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200" 127 + classList={{ 128 + hidden: 129 + (repoHovered() && isCustomDomain() && faviconLoaded()) || 130 + (repoHovered() && handle() === "jcsalterego.bsky.social"), 131 + }} 132 + ></span> 133 + <Show when={repoHovered() && handle() === "jcsalterego.bsky.social"}> 134 + <div class="flex size-4 items-center justify-center rounded-full bg-blue-500"> 135 + <span class="iconify lucide--check size-2.5 text-white"></span> 136 + </div> 137 + </Show> 138 + <Show when={hasHoveredRepo() && isCustomDomain()}> 139 + <img 140 + src={`https://${handle()}/favicon.ico`} 141 + class="size-4" 142 + classList={{ hidden: !repoHovered() || !faviconLoaded() }} 143 + onLoad={() => setFaviconLoaded(true)} 144 + onError={() => setFaviconLoaded(false)} 145 + /> 146 + </Show> 147 + </div> 148 + </Tooltip> 149 + <Show 150 + when={props.params.collection} 105 151 106 152 107 153 108 154 when={handle() !== props.params.repo} 109 155 fallback={<span class="truncate">{props.params.repo}</span>} 110 156 > 111 - <span class="shrink-0">{handle()}</span> 157 + <span class="max-w-full shrink-0 truncate">{handle()}</span> 112 158 <span class="truncate text-neutral-500 dark:text-neutral-400"> 113 159 ({props.params.repo}) 114 160 </span> ··· 125 171 when={handle() !== props.params.repo} 126 172 fallback={<span class="truncate">{props.params.repo}</span>} 127 173 > 128 - <span class="shrink-0">{handle()}</span> 174 + <span class="max-w-full shrink-0 truncate">{handle()}</span> 129 175 <span class="truncate">({props.params.repo})</span> 130 176 </Show> 131 177 </A> ··· 170 216 <span class="iconify lucide--file-json text-neutral-500 transition-colors duration-200 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200"></span> 171 217 </Tooltip> 172 218 <div class="flex min-w-0 gap-1 py-0.5 font-medium"> 173 - <span class="shrink-0">{props.params.rkey}</span> 219 + <span>{props.params.rkey}</span> 174 220 <Show when={rkeyTimestamp()}> 175 221 <span class="truncate text-neutral-500 dark:text-neutral-400"> 176 222 ({localDateFromTimestamp(rkeyTimestamp()!)})
+1
src/auth/state.ts
··· 13 13 export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>(); 14 14 export const [sessions, setSessions] = createStore<Sessions>(); 15 15 export const [openManager, setOpenManager] = createSignal(false); 16 + export const [showAddAccount, setShowAddAccount] = createSignal(false); 16 17 export const [pendingPermissionEdit, setPendingPermissionEdit] = createSignal<string | null>(null);
+28 -20
src/components/search.tsx
··· 7 7 8 8 9 9 10 + onMount, 11 + Show, 12 + } from "solid-js"; 13 + import { canHover } from "../layout"; 14 + import { resolveLexiconAuthority, resolveLexiconAuthorityDirect } from "../utils/api"; 15 + import { appHandleLink, appList, AppUrl } from "../utils/app-urls"; 16 + import { createDebouncedValue } from "../utils/hooks/debounced"; 10 17 11 18 12 19 ··· 89 96 90 97 91 98 99 + <Button onClick={() => setShowSearch(!showSearch())}> 100 + <span class="iconify lucide--search"></span> 101 + <span>Search</span> 102 + <Show when={canHover}> 103 + <kbd class="font-sans text-neutral-400 dark:text-neutral-500"> 104 + {/Mac/i.test(navigator.platform) ? "โŒ˜" : "โŒƒ"}K 105 + </kbd> 92 106 93 107 94 108 ··· 112 126 113 127 114 128 129 + window.addEventListener("paste", handlePaste); 130 + onCleanup(() => window.removeEventListener("paste", handlePaste)); 115 131 132 + const requestUrl = new URL(location.href); 133 + const requestQuery = requestUrl.searchParams.get("q"); 134 + if (requestQuery !== null) { 135 + requestUrl.searchParams.delete("q"); 136 + history.replaceState(null, "", requestUrl.toString()); 137 + processInput(requestQuery); 138 + } 139 + }); 116 140 117 - 118 - 119 - 120 - 121 - 122 - 123 - 124 - 125 - 126 - 127 - 128 - 129 - 130 - 131 - 132 - 133 - 141 + createEffect(() => { 134 142 135 143 136 144 ··· 348 356 </span> 349 357 <button 350 358 type="button" 351 - class="text-xs text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200" 359 + class="text-xs not-hover:text-neutral-500 dark:not-hover:text-neutral-400" 352 360 onClick={() => { 353 361 localStorage.removeItem(RECENT_SEARCHES_KEY); 354 362 setRecentSearches([]); ··· 390 398 </A> 391 399 <button 392 400 type="button" 393 - class="mr-1 flex items-center rounded p-1 opacity-0 group-hover:opacity-100 hover:bg-neutral-300 dark:hover:bg-neutral-600" 401 + class="flex items-center p-2.5 opacity-0 not-hover:text-neutral-500 group-hover:opacity-100 dark:not-hover:text-neutral-400" 394 402 onClick={() => { 395 403 removeRecentSearch(recent.path); 396 404 setRecentSearches(getRecentSearches()); 397 405 }} 398 406 > 399 - <span class="iconify lucide--x text-sm text-neutral-500 dark:text-neutral-400"></span> 407 + <span class="iconify lucide--x text-base"></span> 400 408 </button> 401 409 </div> 402 410 );
+13 -14
src/views/logs.tsx
··· 3 3 defs, 4 4 IndexedEntry, 5 5 IndexedEntryLog, 6 - processIndexedEntryLog, 7 6 } from "@atcute/did-plc"; 8 - import { createEffect, createResource, createSignal, For, Show } from "solid-js"; 7 + import { createEffect, createResource, createSignal, For, onCleanup, Show } from "solid-js"; 9 8 import { localDateFromTimestamp } from "../utils/date.js"; 10 9 import { createOperationHistory, DiffEntry, groupBy } from "../utils/plc-logs.js"; 10 + import PlcValidateWorker from "../workers/plc-validate.ts?worker"; 11 11 import { plcDirectory } from "./settings.jsx"; 12 12 13 13 type PlcEvent = "handle" | "rotation_key" | "service" | "verification_method"; ··· 32 32 return Array.from(groupBy(opHistory, (item) => item.orig)); 33 33 }; 34 34 35 - const validateLog = async (logs: IndexedEntryLog) => { 36 - try { 37 - await processIndexedEntryLog(props.did as any, logs); 38 - setValidLog(true); 39 - } catch (e) { 40 - console.error(e); 41 - setValidLog(false); 42 - } 43 - }; 44 - 45 35 const [plcOps] = 46 36 createResource<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>(fetchPlcLogs); 47 37 38 + let worker: Worker | undefined; 39 + onCleanup(() => worker?.terminate()); 40 + 48 41 createEffect(() => { 49 42 const logs = rawLogs(); 50 43 if (logs) { 51 44 setValidLog(undefined); 52 - // Defer validation to next tick to avoid blocking rendering 53 - setTimeout(() => validateLog(logs), 0); 45 + worker?.terminate(); 46 + worker = new PlcValidateWorker(); 47 + worker.onmessage = (e: MessageEvent<{ valid: boolean }>) => { 48 + setValidLog(e.data.valid); 49 + worker?.terminate(); 50 + worker = undefined; 51 + }; 52 + worker.postMessage({ did: props.did, logs }); 54 53 } 55 54 }); 56 55
+11
src/workers/plc-validate.ts
··· 1 + import { processIndexedEntryLog } from "@atcute/did-plc"; 2 + 3 + self.onmessage = async (e: MessageEvent<{ did: string; logs: any }>) => { 4 + const { did, logs } = e.data; 5 + try { 6 + await processIndexedEntryLog(did as any, logs); 7 + self.postMessage({ valid: true }); 8 + } catch { 9 + self.postMessage({ valid: false }); 10 + } 11 + };
public/avatar/drunk.moe.jpg

This is a binary file and will not be displayed.

public/avatar/slug.moe.jpg

This is a binary file and will not be displayed.

public/avatar/notnite.com.jpg

This is a binary file and will not be displayed.

+13 -3
src/components/tooltip.tsx
··· 1 1 import { JSX, Show } from "solid-js"; 2 - import { isTouchDevice } from "../layout"; 2 + import { canHover } from "../layout"; 3 3 4 - const Tooltip = (props: { text: string; children: JSX.Element }) => ( 4 + const Tooltip = (props: { text: string; shortcut?: string; children: JSX.Element }) => ( 5 5 <span class="group/tooltip relative inline-flex items-center"> 6 6 {props.children} 7 - <Show when={!isTouchDevice}> 7 + <Show when={canHover}> 8 8 <span 9 9 style={`transform: translate(-50%, 28px)`} 10 10 class={`dark:shadow-dark-700 dark:bg-dark-300 pointer-events-none absolute left-[50%] z-20 hidden min-w-fit rounded border-[0.5px] border-neutral-300 bg-white p-1 text-center font-sans text-xs font-normal whitespace-nowrap text-neutral-900 shadow-md select-none group-hover/tooltip:inline first-letter:capitalize dark:border-neutral-600 dark:text-neutral-200`} 11 + > 12 + {props.text} 13 + <Show when={props.shortcut}> 14 + <kbd class="ml-2 rounded border border-neutral-300 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] dark:border-neutral-600 dark:bg-neutral-700"> 15 + {props.shortcut} 16 + </kbd> 17 + </Show> 18 + </span> 19 + </Show> 20 + </span>
+39 -20
src/components/dropdown.tsx
··· 47 47 48 48 49 49 50 + icon?: string; 51 + newTab?: boolean; 52 + external?: boolean; 53 + shortcut?: string; 54 + }) => { 55 + const ctx = useContext(MenuContext); 50 56 51 57 52 58 53 - 54 - 55 - 56 - 57 - 58 - 59 - 60 - 61 - 62 - 63 - 64 - 65 - 66 - 67 - 68 - 69 - 70 - 59 + href={props.href} 60 + onClick={() => ctx?.setShowMenu(false)} 61 + class="flex items-center gap-2 rounded-md p-1.5 hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 62 + classList={{ "justify-between": props.external || !!props.shortcut }} 63 + target={props.newTab ? "_blank" : undefined} 64 + > 65 + <div class="flex items-center gap-2"> 66 + <Show when={props.icon}> 67 + <span class={"iconify shrink-0 " + props.icon}></span> 68 + </Show> 69 + <span class="whitespace-nowrap">{props.label}</span> 70 + </div> 71 + <Show when={props.shortcut}> 72 + <kbd class="rounded border border-neutral-300 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] text-neutral-500 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-400"> 73 + {props.shortcut} 74 + </kbd> 75 + </Show> 76 + <Show when={props.external}> 77 + <span class="iconify lucide--external-link"></span> 78 + </Show> 71 79 72 80 73 81 ··· 104 112 const ctx = useContext(MenuContext); 105 113 const [menu, setMenu] = createSignal<HTMLDivElement>(); 106 114 const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>(); 107 - const [buttonRect, setButtonRect] = createSignal<DOMRect>(); 115 + const [buttonRect, setButtonRect] = createSignal<{ bottom: number; right: number }>(); 108 116 109 117 const clickEvent = (event: MouseEvent) => { 110 118 const target = event.target as Node; ··· 113 121 114 122 const updatePosition = () => { 115 123 const rect = menuButton()?.getBoundingClientRect(); 116 - if (rect) setButtonRect(rect); 124 + if (rect) { 125 + const isTouchDevice = window.matchMedia("(hover: none)").matches; 126 + const vv = isTouchDevice ? window.visualViewport : null; 127 + setButtonRect({ 128 + bottom: rect.bottom + (vv?.offsetTop ?? 0), 129 + right: rect.right + (vv?.offsetLeft ?? 0), 130 + }); 131 + } 117 132 }; 118 133 119 134 onMount(() => { 120 135 window.addEventListener("click", clickEvent); 121 136 window.addEventListener("scroll", updatePosition, true); 122 137 window.addEventListener("resize", updatePosition); 138 + window.visualViewport?.addEventListener("resize", updatePosition); 139 + window.visualViewport?.addEventListener("scroll", updatePosition); 123 140 }); 124 141 125 142 onCleanup(() => { 126 143 window.removeEventListener("click", clickEvent); 127 144 window.removeEventListener("scroll", updatePosition, true); 128 145 window.removeEventListener("resize", updatePosition); 146 + window.visualViewport?.removeEventListener("resize", updatePosition); 147 + window.visualViewport?.removeEventListener("scroll", updatePosition); 129 148 }); 130 149 131 150 return (
+697 -669
pnpm-lock.yaml
··· 12 12 specifier: ^3.1.10 13 13 version: 3.1.10 14 14 '@atcute/bluesky': 15 - specifier: ^3.2.15 16 - version: 3.2.15 15 + specifier: ^3.2.17 16 + version: 3.2.17 17 17 '@atcute/car': 18 18 specifier: ^5.1.0 19 19 version: 5.1.0 ··· 39 39 specifier: ^1.2.2 40 40 version: 1.2.2(@atcute/identity@1.1.3) 41 41 '@atcute/lexicon-doc': 42 - specifier: ^2.0.6 43 - version: 2.0.6 42 + specifier: ^2.1.0 43 + version: 2.1.0 44 44 '@atcute/lexicon-resolver': 45 45 specifier: ^0.1.6 46 46 version: 0.1.6(@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3))(@atcute/identity@1.1.3) 47 47 '@atcute/lexicons': 48 - specifier: ^1.2.6 49 - version: 1.2.6 48 + specifier: ^1.2.7 49 + version: 1.2.7 50 50 '@atcute/multibase': 51 - specifier: ^1.1.6 52 - version: 1.1.6 51 + specifier: ^1.1.7 52 + version: 1.1.7 53 53 '@atcute/oauth-browser-client': 54 - specifier: ^2.0.3 55 - version: 2.0.3(@atcute/identity@1.1.3) 54 + specifier: ^3.0.0 55 + version: 3.0.0(@atcute/identity@1.1.3) 56 56 '@atcute/repo': 57 57 specifier: ^0.1.1 58 58 version: 0.1.1 59 59 '@atcute/tangled': 60 - specifier: ^1.0.14 61 - version: 1.0.14 60 + specifier: ^1.0.16 61 + version: 1.0.16 62 62 '@atcute/tid': 63 63 specifier: ^1.1.1 64 64 version: 1.1.1 ··· 69 69 specifier: ^6.0.2 70 70 version: 6.0.2 71 71 '@codemirror/lint': 72 - specifier: ^6.9.2 73 - version: 6.9.2 72 + specifier: ^6.9.3 73 + version: 6.9.3 74 74 '@codemirror/state': 75 75 specifier: ^6.5.4 76 76 version: 6.5.4 77 77 '@codemirror/view': 78 - specifier: ^6.39.11 79 - version: 6.39.11 78 + specifier: ^6.39.12 79 + version: 6.39.12 80 80 '@fsegurai/codemirror-theme-basic-dark': 81 81 specifier: ^6.2.3 82 - version: 6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)(@lezer/highlight@1.2.3) 82 + version: 6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.12)(@lezer/highlight@1.2.3) 83 83 '@fsegurai/codemirror-theme-basic-light': 84 84 specifier: ^6.2.3 85 - version: 6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)(@lezer/highlight@1.2.3) 85 + version: 6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.12)(@lezer/highlight@1.2.3) 86 86 '@mary/exif-rm': 87 87 specifier: jsr:^0.2.2 88 88 version: '@jsr/mary__exif-rm@0.2.2' ··· 109 109 version: 1.9.11 110 110 devDependencies: 111 111 '@iconify-json/lucide': 112 - specifier: ^1.2.86 113 - version: 1.2.86 112 + specifier: ^1.2.89 113 + version: 1.2.89 114 114 '@iconify/tailwind4': 115 115 specifier: ^1.2.1 116 116 version: 1.2.1(tailwindcss@4.1.18) 117 117 '@tailwindcss/vite': 118 118 specifier: ^4.1.18 119 - version: 4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 119 + version: 4.1.18(vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 120 120 prettier: 121 121 specifier: ^3.8.1 122 122 version: 3.8.1 ··· 134 134 version: 5.9.3 135 135 vite: 136 136 specifier: ^7.3.1 137 - version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 137 + version: 7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 138 138 vite-plugin-solid: 139 139 specifier: ^2.11.10 140 - version: 2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 140 + version: 2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 141 141 142 142 packages: 143 143 ··· 147 147 '@atcute/atproto@3.1.10': 148 148 resolution: {integrity: sha512-+GKZpOc0PJcdWMQEkTfg/rSNDAAHxmAUGBl60g2az15etqJn5WaUPNGFE2sB7hKpwi5Ue2h/L0OacINcE/JDDQ==} 149 149 150 - '@atcute/bluesky@3.2.15': 151 - resolution: {integrity: sha512-H4RW3WffjfdKvOZ9issEUQnuSR4KfuAwwJnYu0fclA9VDa99JTJ+pa8tTl9lFeBV9DINtWJAx7rdIbICoVCstQ==} 150 + '@atcute/bluesky@3.2.17': 151 + resolution: {integrity: sha512-Li+RsPkcRNC6AnNlqOGnlmAcjSwBdXIKFubJL1nwACDngKNXG4ooGL5cvzeekdDEfHmtFhS/tyZNaUx9QXYEUw==} 152 152 153 153 '@atcute/car@3.1.3': 154 154 resolution: {integrity: sha512-WJ13bAEt7TjDMVi09ubjLtvhdljbWInGm9Kfy7Y6NhrmiyC/aZYaA/zHX/bHI6xv1c/h3SQduWqxOr4ae49eqA==} ··· 179 179 '@atcute/identity@1.1.3': 180 180 resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} 181 181 182 - '@atcute/lexicon-doc@2.0.6': 183 - resolution: {integrity: sha512-iDYJkuom+tIw3zIvU1ggCEVFfReXKfOUtIhpY2kEg2kQeSfMB75F+8k1QOpeAQBetyWYmjsHqBuSUX9oQS6L1Q==} 182 + '@atcute/lexicon-doc@2.1.0': 183 + resolution: {integrity: sha512-I8BNFKUP2VvEv2tNzZ10b0TKtQWJHQ/CLUh29N1HRDUImah+OS7Z7wGBFleF7e1JnMzcWDhgZCvA6JaJOScBuA==} 184 184 185 185 '@atcute/lexicon-resolver@0.1.6': 186 186 resolution: {integrity: sha512-wJC/ChmpP7k+ywpOd07CMvioXjIGaFpF3bDwXLi/086LYjSWHOvtW6pyC+mqP5wLhjyH2hn4wmi77Buew1l1aw==} ··· 188 188 '@atcute/identity': ^1.1.0 189 189 '@atcute/identity-resolver': ^1.1.3 190 190 191 - '@atcute/lexicons@1.2.6': 192 - resolution: {integrity: sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA==} 191 + '@atcute/lexicons@1.2.7': 192 + resolution: {integrity: sha512-gCvkSMI1F1zx7xXa59iPiSKMH3L5Hga6iurGqQjaQbE2V/np/2QuDqQzt96TNbWfaFAXE9f9oY+0z3ljf/bweA==} 193 193 194 194 '@atcute/mst@0.1.2': 195 195 resolution: {integrity: sha512-Oz5CZTjqauEJLT9B+zkoy/mjl216DrjCxJFrguRV3N+1NkIbCfAcSRf3UDSNjfzDzBkJvC1WjA/3oQkm83duPg==} 196 196 197 - '@atcute/multibase@1.1.6': 198 - resolution: {integrity: sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==} 197 + '@atcute/multibase@1.1.7': 198 + resolution: {integrity: sha512-YmWds7U52b7Qri0xNfGeqSOvgyNfHR8Yy/NNDQx4d5TkCX2fHJIo0pXquEhCyMNAwKt53uH5yQDswy4TNP1Zhw==} 199 199 200 - '@atcute/oauth-browser-client@2.0.3': 201 - resolution: {integrity: sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ==} 200 + '@atcute/oauth-browser-client@3.0.0': 201 + resolution: {integrity: sha512-7AbKV8tTe7aRJNJV7gCcWHSVEADb2nr58O1p7dQsf73HSe9pvlBkj/Vk1yjjtH691uAVYkwhHSh0bC7D8XdwJw==} 202 + 203 + '@atcute/oauth-crypto@0.1.0': 204 + resolution: {integrity: sha512-qZYDCNLF/4B6AndYT1rsQelN8621AC5u/sL5PHvlr/qqAbmmUwCBGjEgRSyZtHE1AqD60VNiSMlOgAuEQTSl3w==} 205 + 206 + '@atcute/oauth-keyset@0.1.0': 207 + resolution: {integrity: sha512-+wqT/+I5Lg9VzKnKY3g88+N45xbq+wsdT6bHDGqCVa2u57gRvolFF4dY+weMfc/OX641BIZO6/o+zFtKBsMQnQ==} 208 + 209 + '@atcute/oauth-types@0.1.1': 210 + resolution: {integrity: sha512-u+3KMjse3Uc/9hDyilu1QVN7IpcnjVXgRzhddzBB8Uh6wePHNVBDdi9wQvFTVVA3zmxtMJVptXRyLLg6Ou9bqg==} 202 211 203 212 '@atcute/repo@0.1.1': 204 213 resolution: {integrity: sha512-P5aWjt3bvcquUkUmGPslF0naAfLGRHse5Qdz9/RJYrFuoH0iiEMyRnW6M+3ksOe20GPsMnbq71WbzzFkRFPBtg==} 205 214 206 - '@atcute/tangled@1.0.14': 207 - resolution: {integrity: sha512-vOJuFUCI/dova2OL5kPFgMzYLFyzFNzxxLCvtPvukgcr7ZIm4qLGkNHdVg3Jk8c0bknULe0pJnaCKUTWA1VPdA==} 215 + '@atcute/tangled@1.0.16': 216 + resolution: {integrity: sha512-8we9seXrD79MhvA0BSBgKOvZz/Cx6sJJ4wc4QcHZF4Ats/+4+xuNOwzuFNL+1sNib/GqXxa8dhmmpoY6T1PRZQ==} 208 217 209 218 '@atcute/tid@1.1.1': 210 219 resolution: {integrity: sha512-djJ8UGhLkTU5V51yCnBEruMg35qETjWzWy5sJG/2gEOl2Gd7rQWHSaf+yrO6vMS5EFA38U2xOWE3EDUPzvc2ZQ==} ··· 212 221 '@atcute/time-ms@1.2.0': 213 222 resolution: {integrity: sha512-dtNKebVIbr1+yu3a6vgtL4sfkNgxkL3aA+ohHsjtW83WWMjjGvX8GVTVmYCJ2dYSxIoxK0q1yWs11PmlqzmQ/A==} 214 223 215 - '@atcute/uint8array@1.0.6': 216 - resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==} 224 + '@atcute/uint8array@1.1.0': 225 + resolution: {integrity: sha512-JtHXIVW6LPU9FMWp7SgE4HbUs3uV2WdfkK/2RWdEGjr4EgMV50P3FdU6fPeGlTfDNBJVYMIsuD2wwaKRPV/Aqg==} 217 226 218 227 '@atcute/util-fetch@1.0.5': 219 228 resolution: {integrity: sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig==} 220 229 221 - '@atcute/util-text@0.0.1': 222 - resolution: {integrity: sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g==} 230 + '@atcute/util-text@1.1.0': 231 + resolution: {integrity: sha512-34G9KD5Z9f7oEdFpZOmqrMnU86p8ne6LlxJowfZzKNszRcl1GH+FtEPh3N1woelJT2SkPXMK2anwT8DESTluwA==} 223 232 224 233 '@atcute/varint@1.0.3': 225 234 resolution: {integrity: sha512-fdvMPyBB+McDT+Ai5e9RwEbwYV4yjZ60S2Dn5PTjGqUyxvoCH1z42viuheDZRUDkmfQehXJTZ5az7dSozVNtog==} 226 235 227 - '@babel/code-frame@7.28.6': 228 - resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} 236 + '@babel/code-frame@7.29.0': 237 + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} 229 238 engines: {node: '>=6.9.0'} 230 239 231 - '@babel/compat-data@7.28.6': 232 - resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} 240 + '@babel/compat-data@7.29.0': 241 + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} 233 242 engines: {node: '>=6.9.0'} 234 243 235 - '@babel/core@7.28.6': 236 - resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} 244 + '@babel/core@7.29.0': 245 + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} 237 246 engines: {node: '>=6.9.0'} 238 247 239 - '@babel/generator@7.28.6': 240 - resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} 248 + '@babel/generator@7.29.1': 249 + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} 241 250 engines: {node: '>=6.9.0'} 242 251 243 252 '@babel/helper-compilation-targets@7.28.6': ··· 282 291 resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} 283 292 engines: {node: '>=6.9.0'} 284 293 285 - '@babel/parser@7.28.6': 286 - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} 294 + '@babel/parser@7.29.0': 295 + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} 287 296 engines: {node: '>=6.0.0'} 288 297 hasBin: true 289 298 ··· 297 306 resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} 298 307 engines: {node: '>=6.9.0'} 299 308 300 - '@babel/traverse@7.28.6': 301 - resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} 309 + '@babel/traverse@7.29.0': 310 + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} 302 311 engines: {node: '>=6.9.0'} 303 312 304 - '@babel/types@7.28.6': 305 - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} 313 + '@babel/types@7.29.0': 314 + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} 306 315 engines: {node: '>=6.9.0'} 307 316 308 317 '@badrap/valita@0.4.6': ··· 321 330 '@codemirror/language@6.12.1': 322 331 resolution: {integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==} 323 332 324 - '@codemirror/lint@6.9.2': 325 - resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} 333 + '@codemirror/lint@6.9.3': 334 + resolution: {integrity: sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==} 326 335 327 336 '@codemirror/search@6.6.0': 328 337 resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} ··· 330 339 '@codemirror/state@6.5.4': 331 340 resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==} 332 341 333 - '@codemirror/view@6.39.11': 334 - resolution: {integrity: sha512-bWdeR8gWM87l4DB/kYSF9A+dVackzDb/V56Tq7QVrQ7rn86W0rgZFtlL3g3pem6AeGcb9NQNoy3ao4WpW4h5tQ==} 342 + '@codemirror/view@6.39.12': 343 + resolution: {integrity: sha512-f+/VsHVn/kOA9lltk/GFzuYwVVAKmOnNjxbrhkk3tPHntFqjWeI2TbIXx006YkBkqC10wZ4NsnWXCQiFPeAISQ==} 335 344 336 - '@cyberalien/svg-utils@1.0.11': 337 - resolution: {integrity: sha512-qEE9mnyI+avfGT3emKuRs3ucYkITeaV0Xi7VlYN41f+uGnZBecQP3jwz/AF437H9J4Q7qPClHKm4NiTYpNE6hA==} 345 + '@cyberalien/svg-utils@1.1.1': 346 + resolution: {integrity: sha512-i05Cnpzeezf3eJAXLx7aFirTYYoq5D1XUItp1XsjqkerNJh//6BG9sOYHbiO7v0KYMvJAx3kosrZaRcNlQPdsA==} 338 347 339 348 '@esbuild/aix-ppc64@0.23.1': 340 349 resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} 341 350 351 + cpu: [ppc64] 352 + os: [aix] 342 353 354 + '@esbuild/aix-ppc64@0.27.3': 355 + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} 356 + engines: {node: '>=18'} 357 + cpu: [ppc64] 358 + os: [aix] 343 359 344 360 345 361 346 362 363 + cpu: [arm64] 364 + os: [android] 347 365 366 + '@esbuild/android-arm64@0.27.3': 367 + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} 368 + engines: {node: '>=18'} 369 + cpu: [arm64] 370 + os: [android] 348 371 349 372 350 373 351 374 375 + cpu: [arm] 376 + os: [android] 352 377 378 + '@esbuild/android-arm@0.27.3': 379 + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} 380 + engines: {node: '>=18'} 381 + cpu: [arm] 382 + os: [android] 353 383 354 384 355 385 356 386 387 + cpu: [x64] 388 + os: [android] 357 389 390 + '@esbuild/android-x64@0.27.3': 391 + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} 392 + engines: {node: '>=18'} 393 + cpu: [x64] 394 + os: [android] 358 395 359 396 360 397 361 398 399 + cpu: [arm64] 400 + os: [darwin] 362 401 402 + '@esbuild/darwin-arm64@0.27.3': 403 + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} 404 + engines: {node: '>=18'} 405 + cpu: [arm64] 406 + os: [darwin] 363 407 364 408 365 409 366 410 411 + cpu: [x64] 412 + os: [darwin] 367 413 414 + '@esbuild/darwin-x64@0.27.3': 415 + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} 416 + engines: {node: '>=18'} 417 + cpu: [x64] 418 + os: [darwin] 368 419 369 420 370 421 371 422 423 + cpu: [arm64] 424 + os: [freebsd] 372 425 426 + '@esbuild/freebsd-arm64@0.27.3': 427 + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} 428 + engines: {node: '>=18'} 429 + cpu: [arm64] 430 + os: [freebsd] 373 431 374 432 375 433 376 434 435 + cpu: [x64] 436 + os: [freebsd] 377 437 438 + '@esbuild/freebsd-x64@0.27.3': 439 + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} 440 + engines: {node: '>=18'} 441 + cpu: [x64] 442 + os: [freebsd] 378 443 379 444 380 445 381 446 447 + cpu: [arm64] 448 + os: [linux] 382 449 450 + '@esbuild/linux-arm64@0.27.3': 451 + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} 452 + engines: {node: '>=18'} 453 + cpu: [arm64] 454 + os: [linux] 383 455 384 456 385 457 386 458 459 + cpu: [arm] 460 + os: [linux] 387 461 462 + '@esbuild/linux-arm@0.27.3': 463 + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} 464 + engines: {node: '>=18'} 465 + cpu: [arm] 466 + os: [linux] 388 467 389 468 390 469 391 470 471 + cpu: [ia32] 472 + os: [linux] 392 473 474 + '@esbuild/linux-ia32@0.27.3': 475 + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} 476 + engines: {node: '>=18'} 477 + cpu: [ia32] 478 + os: [linux] 393 479 394 480 395 481 396 482 483 + cpu: [loong64] 484 + os: [linux] 397 485 486 + '@esbuild/linux-loong64@0.27.3': 487 + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} 488 + engines: {node: '>=18'} 489 + cpu: [loong64] 490 + os: [linux] 398 491 399 492 400 493 401 494 495 + cpu: [mips64el] 496 + os: [linux] 402 497 498 + '@esbuild/linux-mips64el@0.27.3': 499 + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} 500 + engines: {node: '>=18'} 501 + cpu: [mips64el] 502 + os: [linux] 403 503 404 504 405 505 406 506 507 + cpu: [ppc64] 508 + os: [linux] 407 509 510 + '@esbuild/linux-ppc64@0.27.3': 511 + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} 512 + engines: {node: '>=18'} 513 + cpu: [ppc64] 514 + os: [linux] 408 515 409 516 410 517 411 518 519 + cpu: [riscv64] 520 + os: [linux] 412 521 522 + '@esbuild/linux-riscv64@0.27.3': 523 + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} 524 + engines: {node: '>=18'} 525 + cpu: [riscv64] 526 + os: [linux] 413 527 414 528 415 529 416 530 531 + cpu: [s390x] 532 + os: [linux] 417 533 534 + '@esbuild/linux-s390x@0.27.3': 535 + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} 536 + engines: {node: '>=18'} 537 + cpu: [s390x] 538 + os: [linux] 418 539 419 540 420 541 421 542 543 + cpu: [x64] 544 + os: [linux] 422 545 546 + '@esbuild/linux-x64@0.27.3': 547 + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} 548 + engines: {node: '>=18'} 549 + cpu: [x64] 550 + os: [linux] 423 551 552 + '@esbuild/netbsd-arm64@0.27.3': 553 + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} 554 + engines: {node: '>=18'} 555 + cpu: [arm64] 556 + os: [netbsd] 424 557 425 558 426 559 427 560 561 + cpu: [x64] 562 + os: [netbsd] 428 563 564 + '@esbuild/netbsd-x64@0.27.3': 565 + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} 566 + engines: {node: '>=18'} 567 + cpu: [x64] 568 + os: [netbsd] 429 569 430 570 431 571 432 572 573 + cpu: [arm64] 574 + os: [openbsd] 433 575 576 + '@esbuild/openbsd-arm64@0.27.3': 577 + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} 578 + engines: {node: '>=18'} 579 + cpu: [arm64] 580 + os: [openbsd] 434 581 435 582 436 583 437 584 585 + cpu: [x64] 586 + os: [openbsd] 438 587 588 + '@esbuild/openbsd-x64@0.27.3': 589 + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} 590 + engines: {node: '>=18'} 591 + cpu: [x64] 592 + os: [openbsd] 439 593 594 + '@esbuild/openharmony-arm64@0.27.3': 595 + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} 596 + engines: {node: '>=18'} 597 + cpu: [arm64] 598 + os: [openharmony] 440 599 441 600 442 601 443 602 603 + cpu: [x64] 604 + os: [sunos] 444 605 606 + '@esbuild/sunos-x64@0.27.3': 607 + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} 608 + engines: {node: '>=18'} 609 + cpu: [x64] 610 + os: [sunos] 445 611 446 612 447 613 448 614 615 + cpu: [arm64] 616 + os: [win32] 449 617 618 + '@esbuild/win32-arm64@0.27.3': 619 + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} 620 + engines: {node: '>=18'} 621 + cpu: [arm64] 622 + os: [win32] 450 623 451 624 452 625 453 626 627 + cpu: [ia32] 628 + os: [win32] 454 629 630 + '@esbuild/win32-ia32@0.27.3': 631 + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} 632 + engines: {node: '>=18'} 633 + cpu: [ia32] 634 + os: [win32] 455 635 456 636 457 637 458 638 639 + cpu: [x64] 640 + os: [win32] 459 641 460 - 461 - 462 - 463 - 464 - 465 - 466 - 467 - 468 - 469 - 470 - 471 - 472 - 473 - 474 - 475 - 476 - 477 - 478 - 479 - 480 - 481 - 482 - 483 - 484 - 485 - 486 - 487 - 488 - 489 - 490 - 491 - 492 - 493 - 494 - 495 - 496 - 497 - 498 - 499 - 500 - 501 - 502 - 503 - 504 - 505 - 506 - 507 - 508 - 509 - 510 - 511 - 512 - 513 - 514 - 515 - 516 - 517 - 518 - 519 - 520 - 521 - 522 - 523 - 524 - 525 - 526 - 527 - 528 - 529 - 530 - 531 - 532 - 533 - 534 - 535 - 536 - 537 - 538 - 539 - 540 - 541 - 542 - 543 - 544 - 545 - 546 - 547 - 548 - 549 - 550 - 551 - 552 - 553 - 554 - 555 - 556 - 557 - 558 - 559 - 560 - 561 - 562 - 563 - 564 - 565 - 566 - 567 - 568 - 569 - 570 - 571 - 572 - 573 - 574 - 575 - 576 - 577 - 578 - 579 - 580 - 581 - 582 - 583 - 584 - 585 - 586 - 587 - 588 - 589 - 590 - 591 - 592 - 593 - 594 - 595 - 596 - 597 - 598 - 599 - 600 - 601 - 602 - 603 - 604 - 605 - 606 - 607 - 608 - 609 - 610 - 611 - 612 - 613 - 614 - 615 - 616 - 617 - 618 - 619 - 620 - 621 - 622 - 623 - 624 - 625 - 626 - 627 - 628 - 629 - 630 - 631 - 632 - 633 - 634 - 635 - 636 - 637 - 642 + '@esbuild/win32-x64@0.27.3': 643 + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} 644 + engines: {node: '>=18'} 645 + cpu: [x64] 646 + os: [win32] 638 647 639 648 640 649 ··· 652 661 '@codemirror/view': ^6.0.0 653 662 '@lezer/highlight': ^1.0.0 654 663 655 - '@iconify-json/lucide@1.2.86': 656 - resolution: {integrity: sha512-W/Jz7/gGOkI9u43r+UHmQtZtcyw2YLvMwiHa01WV6V4DYltrPNXiD+bCa+djV8LZB1uwF8CiympOMIbgiQ74nA==} 664 + '@iconify-json/lucide@1.2.89': 665 + resolution: {integrity: sha512-9rZaJZn8VBls1KZnGaFTnqqZrUkd++XB3vy9WYIMgmHHgLxQMEZXg3V+oJSEeit0kCNr/OfDBmrDwuGl/LZulA==} 657 666 658 667 '@iconify/tailwind4@1.2.1': 659 668 resolution: {integrity: sha512-Hd7k8y7uzT3hk8ltw0jGku0r0wA8sc3d2iMvVTYv/9tMxBb+frZtWZGD9hDMU3EYuE+lMn58wi2lS8R2ZbwFcQ==} 660 669 peerDependencies: 661 670 tailwindcss: '>= 4.0.0' 662 671 663 - '@iconify/tools@5.0.2': 664 - resolution: {integrity: sha512-esoFiH0LYpiqqVAO+RTenh6qqGKf0V8T0T6IG7dFLCw26cjcYGG34UMHjkbuq+MMl23U39FtkzhWZsCDDtOhew==} 672 + '@iconify/tools@5.0.3': 673 + resolution: {integrity: sha512-W5nbH5fNv20TvU49Al19Foos/ViAnmppbCNV9ieGl6/dRMDRzxeFol6peXX/NAgaOytQwZZxTTJRq/Kxd4eWsA==} 665 674 666 675 '@iconify/types@2.0.0': 667 676 resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} ··· 700 709 701 710 702 711 712 + '@jsr/mary__zip@0.1.1': 713 + resolution: {integrity: sha512-RIt6xSshG6x1X4qUVGXukgS8ysot+wLQc/WAkNogbiDwm3tzFyDxZdEs8TVDQyT1L7lApPPgbEgdcex7NcfGrw==, tarball: https://npm.jsr.io/~/11/@jsr/mary__zip/0.1.1.tgz} 703 714 715 + '@lezer/common@1.5.1': 716 + resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==} 704 717 705 - 706 - 707 - 708 - 709 - 710 - 718 + '@lezer/highlight@1.2.3': 719 + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} 711 720 712 721 '@lezer/json@1.0.3': 713 722 resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} 714 723 715 - '@lezer/lr@1.4.7': 716 - resolution: {integrity: sha512-wNIFWdSUfX9Jc6ePMzxSPVgTVB4EOfDIwLQLWASyiUdHKaMsiilj9bYiGkGQCKVodd0x6bgQCV207PILGFCF9Q==} 724 + '@lezer/lr@1.4.8': 725 + resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==} 717 726 718 727 '@marijn/find-cluster-break@1.0.2': 719 728 resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} ··· 721 730 '@noble/secp256k1@3.0.0': 722 731 resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==} 723 732 724 - '@rollup/rollup-android-arm-eabi@4.56.0': 725 - resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==} 733 + '@rollup/rollup-android-arm-eabi@4.57.1': 734 + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} 726 735 cpu: [arm] 727 736 os: [android] 728 737 729 - '@rollup/rollup-android-arm64@4.56.0': 730 - resolution: {integrity: sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==} 738 + '@rollup/rollup-android-arm64@4.57.1': 739 + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} 731 740 cpu: [arm64] 732 741 os: [android] 733 742 734 - '@rollup/rollup-darwin-arm64@4.56.0': 735 - resolution: {integrity: sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==} 743 + '@rollup/rollup-darwin-arm64@4.57.1': 744 + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} 736 745 cpu: [arm64] 737 746 os: [darwin] 738 747 739 - '@rollup/rollup-darwin-x64@4.56.0': 740 - resolution: {integrity: sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==} 748 + '@rollup/rollup-darwin-x64@4.57.1': 749 + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} 741 750 cpu: [x64] 742 751 os: [darwin] 743 752 744 - '@rollup/rollup-freebsd-arm64@4.56.0': 745 - resolution: {integrity: sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==} 753 + '@rollup/rollup-freebsd-arm64@4.57.1': 754 + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} 746 755 cpu: [arm64] 747 756 os: [freebsd] 748 757 749 - '@rollup/rollup-freebsd-x64@4.56.0': 750 - resolution: {integrity: sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==} 758 + '@rollup/rollup-freebsd-x64@4.57.1': 759 + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} 751 760 cpu: [x64] 752 761 os: [freebsd] 753 762 754 - '@rollup/rollup-linux-arm-gnueabihf@4.56.0': 755 - resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==} 763 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 764 + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} 756 765 cpu: [arm] 757 766 os: [linux] 758 767 759 - '@rollup/rollup-linux-arm-musleabihf@4.56.0': 760 - resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==} 768 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 769 + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} 761 770 cpu: [arm] 762 771 os: [linux] 763 772 764 - '@rollup/rollup-linux-arm64-gnu@4.56.0': 765 - resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==} 773 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 774 + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} 766 775 cpu: [arm64] 767 776 os: [linux] 768 777 769 - '@rollup/rollup-linux-arm64-musl@4.56.0': 770 - resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==} 778 + '@rollup/rollup-linux-arm64-musl@4.57.1': 779 + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} 771 780 cpu: [arm64] 772 781 os: [linux] 773 782 774 - '@rollup/rollup-linux-loong64-gnu@4.56.0': 775 - resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==} 783 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 784 + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} 776 785 cpu: [loong64] 777 786 os: [linux] 778 787 779 - '@rollup/rollup-linux-loong64-musl@4.56.0': 780 - resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==} 788 + '@rollup/rollup-linux-loong64-musl@4.57.1': 789 + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} 781 790 cpu: [loong64] 782 791 os: [linux] 783 792 784 - '@rollup/rollup-linux-ppc64-gnu@4.56.0': 785 - resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==} 793 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 794 + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} 786 795 cpu: [ppc64] 787 796 os: [linux] 788 797 789 - '@rollup/rollup-linux-ppc64-musl@4.56.0': 790 - resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==} 798 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 799 + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} 791 800 cpu: [ppc64] 792 801 os: [linux] 793 802 794 - '@rollup/rollup-linux-riscv64-gnu@4.56.0': 795 - resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==} 803 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 804 + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} 796 805 cpu: [riscv64] 797 806 os: [linux] 798 807 799 - '@rollup/rollup-linux-riscv64-musl@4.56.0': 800 - resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==} 808 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 809 + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} 801 810 cpu: [riscv64] 802 811 os: [linux] 803 812 804 - '@rollup/rollup-linux-s390x-gnu@4.56.0': 805 - resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==} 813 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 814 + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} 806 815 cpu: [s390x] 807 816 os: [linux] 808 817 809 - '@rollup/rollup-linux-x64-gnu@4.56.0': 810 - resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==} 818 + '@rollup/rollup-linux-x64-gnu@4.57.1': 819 + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} 811 820 cpu: [x64] 812 821 os: [linux] 813 822 814 - '@rollup/rollup-linux-x64-musl@4.56.0': 815 - resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==} 823 + '@rollup/rollup-linux-x64-musl@4.57.1': 824 + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} 816 825 cpu: [x64] 817 826 os: [linux] 818 827 819 - '@rollup/rollup-openbsd-x64@4.56.0': 820 - resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==} 828 + '@rollup/rollup-openbsd-x64@4.57.1': 829 + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} 821 830 cpu: [x64] 822 831 os: [openbsd] 823 832 824 - '@rollup/rollup-openharmony-arm64@4.56.0': 825 - resolution: {integrity: sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==} 833 + '@rollup/rollup-openharmony-arm64@4.57.1': 834 + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} 826 835 cpu: [arm64] 827 836 os: [openharmony] 828 837 829 - '@rollup/rollup-win32-arm64-msvc@4.56.0': 830 - resolution: {integrity: sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==} 838 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 839 + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} 831 840 cpu: [arm64] 832 841 os: [win32] 833 842 834 - '@rollup/rollup-win32-ia32-msvc@4.56.0': 835 - resolution: {integrity: sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==} 843 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 844 + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} 836 845 cpu: [ia32] 837 846 os: [win32] 838 847 839 - '@rollup/rollup-win32-x64-gnu@4.56.0': 840 - resolution: {integrity: sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==} 848 + '@rollup/rollup-win32-x64-gnu@4.57.1': 849 + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} 841 850 cpu: [x64] 842 851 os: [win32] 843 852 844 - '@rollup/rollup-win32-x64-msvc@4.56.0': 845 - resolution: {integrity: sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==} 853 + '@rollup/rollup-win32-x64-msvc@4.57.1': 854 + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} 846 855 cpu: [x64] 847 856 os: [win32] 848 857 ··· 964 973 '@types/babel__traverse@7.28.0': 965 974 resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} 966 975 967 - '@types/bun@1.3.6': 968 - resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==} 976 + '@types/bun@1.3.8': 977 + resolution: {integrity: sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA==} 969 978 970 979 '@types/estree@1.0.8': 971 980 resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 972 981 973 - '@types/node@25.0.10': 974 - resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==} 982 + '@types/node@25.2.1': 983 + resolution: {integrity: sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==} 975 984 976 985 acorn@8.15.0: 977 986 resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} ··· 992 1001 solid-js: 993 1002 optional: true 994 1003 995 - baseline-browser-mapping@2.9.17: 996 - resolution: {integrity: sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==} 1004 + baseline-browser-mapping@2.9.19: 1005 + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} 997 1006 hasBin: true 998 1007 999 1008 boolbase@1.0.0: ··· 1004 1013 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 1005 1014 hasBin: true 1006 1015 1007 - bun-types@1.3.6: 1008 - resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} 1009 - 1010 - caniuse-lite@1.0.30001766: 1011 - resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} 1012 - 1016 + bun-types@1.3.8: 1017 + resolution: {integrity: sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q==} 1013 1018 1019 + caniuse-lite@1.0.30001769: 1020 + resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} 1014 1021 1022 + codemirror@6.0.2: 1023 + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} 1015 1024 1016 1025 1017 1026 ··· 1074 1083 domutils@3.2.2: 1075 1084 resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} 1076 1085 1077 - electron-to-chromium@1.5.278: 1078 - resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==} 1079 - 1080 - enhanced-resolve@5.18.4: 1081 - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} 1082 - 1083 - 1084 - 1085 - 1086 - 1087 - 1088 - 1086 + electron-to-chromium@1.5.286: 1087 + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} 1089 1088 1089 + enhanced-resolve@5.19.0: 1090 + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} 1091 + engines: {node: '>=10.13.0'} 1090 1092 1093 + entities@4.5.0: 1091 1094 1092 1095 1093 1096 ··· 1097 1100 1098 1101 1099 1102 1103 + engines: {node: '>=18'} 1104 + hasBin: true 1100 1105 1106 + esbuild@0.27.3: 1107 + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} 1108 + engines: {node: '>=18'} 1109 + hasBin: true 1101 1110 1102 1111 1103 1112 ··· 1131 1140 resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1132 1141 engines: {node: '>=6.9.0'} 1133 1142 1134 - get-tsconfig@4.13.0: 1135 - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} 1143 + get-tsconfig@4.13.5: 1144 + resolution: {integrity: sha512-v4/4xAEpBRp6SvCkWhnGCaLkJf9IwWzrsygJPxD/+p2/xPE3C5m2fA9FD0Ry9tG+Rqqq3gBzHSl6y1/T9V/tMQ==} 1136 1145 1137 1146 graceful-fs@4.2.11: 1138 1147 resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} ··· 1386 1395 resolve-pkg-maps@1.0.0: 1387 1396 resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 1388 1397 1389 - rollup@4.56.0: 1390 - resolution: {integrity: sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==} 1398 + rollup@4.57.1: 1399 + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} 1391 1400 engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1392 1401 hasBin: true 1393 1402 ··· 1550 1559 1551 1560 '@atcute/atproto@3.1.10': 1552 1561 dependencies: 1553 - '@atcute/lexicons': 1.2.6 1562 + '@atcute/lexicons': 1.2.7 1554 1563 1555 - '@atcute/bluesky@3.2.15': 1564 + '@atcute/bluesky@3.2.17': 1556 1565 dependencies: 1557 1566 '@atcute/atproto': 3.1.10 1558 - '@atcute/lexicons': 1.2.6 1567 + '@atcute/lexicons': 1.2.7 1559 1568 1560 1569 '@atcute/car@3.1.3': 1561 1570 dependencies: 1562 1571 '@atcute/cbor': 2.3.0 1563 1572 '@atcute/cid': 2.4.0 1564 - '@atcute/uint8array': 1.0.6 1573 + '@atcute/uint8array': 1.1.0 1565 1574 '@atcute/varint': 1.0.3 1566 1575 yocto-queue: 1.2.2 1567 1576 ··· 1569 1578 dependencies: 1570 1579 '@atcute/cbor': 2.3.0 1571 1580 '@atcute/cid': 2.4.0 1572 - '@atcute/uint8array': 1.0.6 1581 + '@atcute/uint8array': 1.1.0 1573 1582 '@atcute/varint': 1.0.3 1574 1583 1575 1584 '@atcute/cbor@2.3.0': 1576 1585 dependencies: 1577 1586 '@atcute/cid': 2.4.0 1578 - '@atcute/multibase': 1.1.6 1579 - '@atcute/uint8array': 1.0.6 1587 + '@atcute/multibase': 1.1.7 1588 + '@atcute/uint8array': 1.1.0 1580 1589 1581 1590 '@atcute/cid@2.4.0': 1582 1591 dependencies: 1583 - '@atcute/multibase': 1.1.6 1584 - '@atcute/uint8array': 1.0.6 1592 + '@atcute/multibase': 1.1.7 1593 + '@atcute/uint8array': 1.1.0 1585 1594 1586 1595 '@atcute/client@4.2.1': 1587 1596 dependencies: 1588 1597 '@atcute/identity': 1.1.3 1589 - '@atcute/lexicons': 1.2.6 1598 + '@atcute/lexicons': 1.2.7 1590 1599 1591 1600 '@atcute/crypto@2.3.0': 1592 1601 dependencies: 1593 - '@atcute/multibase': 1.1.6 1594 - '@atcute/uint8array': 1.0.6 1602 + '@atcute/multibase': 1.1.7 1603 + '@atcute/uint8array': 1.1.0 1595 1604 '@noble/secp256k1': 3.0.0 1596 1605 1597 1606 '@atcute/did-plc@0.3.1': ··· 1600 1609 '@atcute/cid': 2.4.0 1601 1610 '@atcute/crypto': 2.3.0 1602 1611 '@atcute/identity': 1.1.3 1603 - '@atcute/lexicons': 1.2.6 1604 - '@atcute/multibase': 1.1.6 1605 - '@atcute/uint8array': 1.0.6 1612 + '@atcute/lexicons': 1.2.7 1613 + '@atcute/multibase': 1.1.7 1614 + '@atcute/uint8array': 1.1.0 1606 1615 '@atcute/util-fetch': 1.0.5 1607 1616 '@badrap/valita': 0.4.6 1608 1617 1609 1618 '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3)': 1610 1619 dependencies: 1611 1620 '@atcute/identity': 1.1.3 1612 - '@atcute/lexicons': 1.2.6 1621 + '@atcute/lexicons': 1.2.7 1613 1622 '@atcute/util-fetch': 1.0.5 1614 1623 '@badrap/valita': 0.4.6 1615 1624 1616 1625 '@atcute/identity@1.1.3': 1617 1626 dependencies: 1618 - '@atcute/lexicons': 1.2.6 1627 + '@atcute/lexicons': 1.2.7 1619 1628 '@badrap/valita': 0.4.6 1620 1629 1621 - '@atcute/lexicon-doc@2.0.6': 1630 + '@atcute/lexicon-doc@2.1.0': 1622 1631 dependencies: 1623 1632 '@atcute/identity': 1.1.3 1624 - '@atcute/lexicons': 1.2.6 1625 - '@atcute/uint8array': 1.0.6 1626 - '@atcute/util-text': 0.0.1 1633 + '@atcute/lexicons': 1.2.7 1634 + '@atcute/uint8array': 1.1.0 1635 + '@atcute/util-text': 1.1.0 1627 1636 '@badrap/valita': 0.4.6 1628 1637 1629 1638 '@atcute/lexicon-resolver@0.1.6(@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)': ··· 1631 1640 '@atcute/crypto': 2.3.0 1632 1641 '@atcute/identity': 1.1.3 1633 1642 '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.3) 1634 - '@atcute/lexicon-doc': 2.0.6 1635 - '@atcute/lexicons': 1.2.6 1643 + '@atcute/lexicon-doc': 2.1.0 1644 + '@atcute/lexicons': 1.2.7 1636 1645 '@atcute/repo': 0.1.1 1637 1646 '@atcute/util-fetch': 1.0.5 1638 1647 '@badrap/valita': 0.4.6 1639 1648 1640 - '@atcute/lexicons@1.2.6': 1649 + '@atcute/lexicons@1.2.7': 1641 1650 dependencies: 1642 - '@atcute/uint8array': 1.0.6 1643 - '@atcute/util-text': 0.0.1 1651 + '@atcute/uint8array': 1.1.0 1652 + '@atcute/util-text': 1.1.0 1644 1653 '@standard-schema/spec': 1.1.0 1645 1654 esm-env: 1.2.2 1646 1655 ··· 1648 1657 dependencies: 1649 1658 '@atcute/cbor': 2.3.0 1650 1659 '@atcute/cid': 2.4.0 1651 - '@atcute/uint8array': 1.0.6 1660 + '@atcute/uint8array': 1.1.0 1652 1661 1653 - '@atcute/multibase@1.1.6': 1662 + '@atcute/multibase@1.1.7': 1654 1663 dependencies: 1655 - '@atcute/uint8array': 1.0.6 1664 + '@atcute/uint8array': 1.1.0 1656 1665 1657 - '@atcute/oauth-browser-client@2.0.3(@atcute/identity@1.1.3)': 1666 + '@atcute/oauth-browser-client@3.0.0(@atcute/identity@1.1.3)': 1658 1667 dependencies: 1659 1668 '@atcute/client': 4.2.1 1660 1669 '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.3) 1661 - '@atcute/lexicons': 1.2.6 1662 - '@atcute/multibase': 1.1.6 1663 - '@atcute/uint8array': 1.0.6 1670 + '@atcute/lexicons': 1.2.7 1671 + '@atcute/multibase': 1.1.7 1672 + '@atcute/oauth-crypto': 0.1.0 1673 + '@atcute/oauth-types': 0.1.1 1664 1674 nanoid: 5.1.6 1665 1675 transitivePeerDependencies: 1666 1676 - '@atcute/identity' 1667 1677 1678 + '@atcute/oauth-crypto@0.1.0': 1679 + dependencies: 1680 + '@atcute/multibase': 1.1.7 1681 + '@atcute/uint8array': 1.1.0 1682 + '@badrap/valita': 0.4.6 1683 + nanoid: 5.1.6 1684 + 1685 + '@atcute/oauth-keyset@0.1.0': 1686 + dependencies: 1687 + '@atcute/oauth-crypto': 0.1.0 1688 + 1689 + '@atcute/oauth-types@0.1.1': 1690 + dependencies: 1691 + '@atcute/identity': 1.1.3 1692 + '@atcute/lexicons': 1.2.7 1693 + '@atcute/oauth-keyset': 0.1.0 1694 + '@badrap/valita': 0.4.6 1695 + 1668 1696 '@atcute/repo@0.1.1': 1669 1697 dependencies: 1670 1698 '@atcute/car': 5.1.0 1671 1699 '@atcute/cbor': 2.3.0 1672 1700 '@atcute/cid': 2.4.0 1673 1701 '@atcute/crypto': 2.3.0 1674 - '@atcute/lexicons': 1.2.6 1702 + '@atcute/lexicons': 1.2.7 1675 1703 '@atcute/mst': 0.1.2 1676 - '@atcute/uint8array': 1.0.6 1704 + '@atcute/uint8array': 1.1.0 1677 1705 1678 - '@atcute/tangled@1.0.14': 1706 + '@atcute/tangled@1.0.16': 1679 1707 dependencies: 1680 1708 '@atcute/atproto': 3.1.10 1681 - '@atcute/lexicons': 1.2.6 1709 + '@atcute/lexicons': 1.2.7 1682 1710 1683 1711 '@atcute/tid@1.1.1': 1684 1712 dependencies: ··· 1686 1714 1687 1715 '@atcute/time-ms@1.2.0': 1688 1716 dependencies: 1689 - '@types/bun': 1.3.6 1717 + '@types/bun': 1.3.8 1690 1718 node-gyp-build: 4.8.4 1691 1719 1692 - '@atcute/uint8array@1.0.6': {} 1720 + '@atcute/uint8array@1.1.0': {} 1693 1721 1694 1722 '@atcute/util-fetch@1.0.5': 1695 1723 dependencies: 1696 1724 '@badrap/valita': 0.4.6 1697 1725 1698 - '@atcute/util-text@0.0.1': 1726 + '@atcute/util-text@1.1.0': 1699 1727 dependencies: 1700 1728 unicode-segmenter: 0.14.5 1701 1729 1702 1730 '@atcute/varint@1.0.3': {} 1703 1731 1704 - '@babel/code-frame@7.28.6': 1732 + '@babel/code-frame@7.29.0': 1705 1733 dependencies: 1706 1734 '@babel/helper-validator-identifier': 7.28.5 1707 1735 js-tokens: 4.0.0 1708 1736 picocolors: 1.1.1 1709 1737 1710 - '@babel/compat-data@7.28.6': {} 1738 + '@babel/compat-data@7.29.0': {} 1711 1739 1712 - '@babel/core@7.28.6': 1740 + '@babel/core@7.29.0': 1713 1741 dependencies: 1714 - '@babel/code-frame': 7.28.6 1715 - '@babel/generator': 7.28.6 1742 + '@babel/code-frame': 7.29.0 1743 + '@babel/generator': 7.29.1 1716 1744 '@babel/helper-compilation-targets': 7.28.6 1717 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) 1745 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) 1718 1746 '@babel/helpers': 7.28.6 1719 - '@babel/parser': 7.28.6 1747 + '@babel/parser': 7.29.0 1720 1748 '@babel/template': 7.28.6 1721 - '@babel/traverse': 7.28.6 1722 - '@babel/types': 7.28.6 1749 + '@babel/traverse': 7.29.0 1750 + '@babel/types': 7.29.0 1723 1751 '@jridgewell/remapping': 2.3.5 1724 1752 convert-source-map: 2.0.0 1725 1753 debug: 4.4.3 ··· 1729 1757 transitivePeerDependencies: 1730 1758 - supports-color 1731 1759 1732 - '@babel/generator@7.28.6': 1760 + '@babel/generator@7.29.1': 1733 1761 dependencies: 1734 - '@babel/parser': 7.28.6 1735 - '@babel/types': 7.28.6 1762 + '@babel/parser': 7.29.0 1763 + '@babel/types': 7.29.0 1736 1764 '@jridgewell/gen-mapping': 0.3.13 1737 1765 '@jridgewell/trace-mapping': 0.3.31 1738 1766 jsesc: 3.1.0 1739 1767 1740 1768 '@babel/helper-compilation-targets@7.28.6': 1741 1769 dependencies: 1742 - '@babel/compat-data': 7.28.6 1770 + '@babel/compat-data': 7.29.0 1743 1771 '@babel/helper-validator-option': 7.27.1 1744 1772 browserslist: 4.28.1 1745 1773 lru-cache: 5.1.1 ··· 1749 1777 1750 1778 '@babel/helper-module-imports@7.18.6': 1751 1779 dependencies: 1752 - '@babel/types': 7.28.6 1780 + '@babel/types': 7.29.0 1753 1781 1754 1782 '@babel/helper-module-imports@7.28.6': 1755 1783 dependencies: 1756 - '@babel/traverse': 7.28.6 1757 - '@babel/types': 7.28.6 1784 + '@babel/traverse': 7.29.0 1785 + '@babel/types': 7.29.0 1758 1786 transitivePeerDependencies: 1759 1787 - supports-color 1760 1788 1761 - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': 1789 + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': 1762 1790 dependencies: 1763 - '@babel/core': 7.28.6 1791 + '@babel/core': 7.29.0 1764 1792 '@babel/helper-module-imports': 7.28.6 1765 1793 '@babel/helper-validator-identifier': 7.28.5 1766 - '@babel/traverse': 7.28.6 1794 + '@babel/traverse': 7.29.0 1767 1795 transitivePeerDependencies: 1768 1796 - supports-color 1769 1797 ··· 1778 1806 '@babel/helpers@7.28.6': 1779 1807 dependencies: 1780 1808 '@babel/template': 7.28.6 1781 - '@babel/types': 7.28.6 1809 + '@babel/types': 7.29.0 1782 1810 1783 - '@babel/parser@7.28.6': 1811 + '@babel/parser@7.29.0': 1784 1812 dependencies: 1785 - '@babel/types': 7.28.6 1813 + '@babel/types': 7.29.0 1786 1814 1787 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': 1815 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': 1788 1816 dependencies: 1789 - '@babel/core': 7.28.6 1817 + '@babel/core': 7.29.0 1790 1818 '@babel/helper-plugin-utils': 7.28.6 1791 1819 1792 1820 '@babel/template@7.28.6': 1793 1821 dependencies: 1794 - '@babel/code-frame': 7.28.6 1795 - '@babel/parser': 7.28.6 1796 - '@babel/types': 7.28.6 1822 + '@babel/code-frame': 7.29.0 1823 + '@babel/parser': 7.29.0 1824 + '@babel/types': 7.29.0 1797 1825 1798 - '@babel/traverse@7.28.6': 1826 + '@babel/traverse@7.29.0': 1799 1827 dependencies: 1800 - '@babel/code-frame': 7.28.6 1801 - '@babel/generator': 7.28.6 1828 + '@babel/code-frame': 7.29.0 1829 + '@babel/generator': 7.29.1 1802 1830 '@babel/helper-globals': 7.28.0 1803 - '@babel/parser': 7.28.6 1831 + '@babel/parser': 7.29.0 1804 1832 '@babel/template': 7.28.6 1805 - '@babel/types': 7.28.6 1833 + '@babel/types': 7.29.0 1806 1834 debug: 4.4.3 1807 1835 transitivePeerDependencies: 1808 1836 - supports-color 1809 1837 1810 - '@babel/types@7.28.6': 1838 + '@babel/types@7.29.0': 1811 1839 dependencies: 1812 1840 '@babel/helper-string-parser': 7.27.1 1813 1841 '@babel/helper-validator-identifier': 7.28.5 ··· 1818 1846 dependencies: 1819 1847 '@codemirror/language': 6.12.1 1820 1848 '@codemirror/state': 6.5.4 1821 - '@codemirror/view': 6.39.11 1822 - '@lezer/common': 1.5.0 1849 + '@codemirror/view': 6.39.12 1850 + '@lezer/common': 1.5.1 1823 1851 1824 1852 '@codemirror/commands@6.10.1': 1825 1853 dependencies: 1826 1854 '@codemirror/language': 6.12.1 1827 1855 '@codemirror/state': 6.5.4 1828 - '@codemirror/view': 6.39.11 1829 - '@lezer/common': 1.5.0 1856 + '@codemirror/view': 6.39.12 1857 + '@lezer/common': 1.5.1 1830 1858 1831 1859 '@codemirror/lang-json@6.0.2': 1832 - 1860 + dependencies: 1833 1861 1834 1862 1835 1863 1836 1864 '@codemirror/language@6.12.1': 1837 1865 dependencies: 1838 1866 '@codemirror/state': 6.5.4 1839 - '@codemirror/view': 6.39.11 1840 - '@lezer/common': 1.5.0 1867 + '@codemirror/view': 6.39.12 1868 + '@lezer/common': 1.5.1 1841 1869 '@lezer/highlight': 1.2.3 1842 - '@lezer/lr': 1.4.7 1870 + '@lezer/lr': 1.4.8 1843 1871 style-mod: 4.1.3 1844 1872 1845 - '@codemirror/lint@6.9.2': 1873 + '@codemirror/lint@6.9.3': 1846 1874 dependencies: 1847 1875 '@codemirror/state': 6.5.4 1848 - '@codemirror/view': 6.39.11 1876 + '@codemirror/view': 6.39.12 1849 1877 crelt: 1.0.6 1850 1878 1851 1879 '@codemirror/search@6.6.0': 1852 1880 dependencies: 1853 1881 '@codemirror/state': 6.5.4 1854 - '@codemirror/view': 6.39.11 1882 + '@codemirror/view': 6.39.12 1855 1883 crelt: 1.0.6 1856 1884 1857 1885 '@codemirror/state@6.5.4': 1858 1886 dependencies: 1859 1887 '@marijn/find-cluster-break': 1.0.2 1860 1888 1861 - '@codemirror/view@6.39.11': 1889 + '@codemirror/view@6.39.12': 1862 1890 dependencies: 1863 1891 '@codemirror/state': 6.5.4 1864 1892 crelt: 1.0.6 1865 1893 style-mod: 4.1.3 1866 1894 w3c-keyname: 2.2.8 1867 1895 1868 - '@cyberalien/svg-utils@1.0.11': 1896 + '@cyberalien/svg-utils@1.1.1': 1869 1897 dependencies: 1870 1898 '@iconify/types': 2.0.0 1871 1899 1900 + '@esbuild/aix-ppc64@0.23.1': 1901 + optional: true 1872 1902 1903 + '@esbuild/aix-ppc64@0.27.3': 1904 + optional: true 1873 1905 1906 + '@esbuild/android-arm64@0.23.1': 1907 + optional: true 1874 1908 1909 + '@esbuild/android-arm64@0.27.3': 1910 + optional: true 1875 1911 1912 + '@esbuild/android-arm@0.23.1': 1913 + optional: true 1876 1914 1915 + '@esbuild/android-arm@0.27.3': 1916 + optional: true 1877 1917 1918 + '@esbuild/android-x64@0.23.1': 1919 + optional: true 1878 1920 1921 + '@esbuild/android-x64@0.27.3': 1922 + optional: true 1879 1923 1924 + '@esbuild/darwin-arm64@0.23.1': 1925 + optional: true 1880 1926 1927 + '@esbuild/darwin-arm64@0.27.3': 1928 + optional: true 1881 1929 1930 + '@esbuild/darwin-x64@0.23.1': 1931 + optional: true 1882 1932 1933 + '@esbuild/darwin-x64@0.27.3': 1934 + optional: true 1883 1935 1936 + '@esbuild/freebsd-arm64@0.23.1': 1937 + optional: true 1884 1938 1939 + '@esbuild/freebsd-arm64@0.27.3': 1940 + optional: true 1885 1941 1942 + '@esbuild/freebsd-x64@0.23.1': 1943 + optional: true 1886 1944 1945 + '@esbuild/freebsd-x64@0.27.3': 1946 + optional: true 1887 1947 1948 + '@esbuild/linux-arm64@0.23.1': 1949 + optional: true 1888 1950 1951 + '@esbuild/linux-arm64@0.27.3': 1952 + optional: true 1889 1953 1954 + '@esbuild/linux-arm@0.23.1': 1955 + optional: true 1890 1956 1957 + '@esbuild/linux-arm@0.27.3': 1958 + optional: true 1891 1959 1960 + '@esbuild/linux-ia32@0.23.1': 1961 + optional: true 1892 1962 1963 + '@esbuild/linux-ia32@0.27.3': 1964 + optional: true 1893 1965 1966 + '@esbuild/linux-loong64@0.23.1': 1967 + optional: true 1894 1968 1969 + '@esbuild/linux-loong64@0.27.3': 1970 + optional: true 1895 1971 1972 + '@esbuild/linux-mips64el@0.23.1': 1973 + optional: true 1896 1974 1975 + '@esbuild/linux-mips64el@0.27.3': 1976 + optional: true 1897 1977 1978 + '@esbuild/linux-ppc64@0.23.1': 1979 + optional: true 1898 1980 1981 + '@esbuild/linux-ppc64@0.27.3': 1982 + optional: true 1899 1983 1984 + '@esbuild/linux-riscv64@0.23.1': 1985 + optional: true 1900 1986 1987 + '@esbuild/linux-riscv64@0.27.3': 1988 + optional: true 1901 1989 1990 + '@esbuild/linux-s390x@0.23.1': 1991 + optional: true 1902 1992 1993 + '@esbuild/linux-s390x@0.27.3': 1994 + optional: true 1903 1995 1996 + '@esbuild/linux-x64@0.23.1': 1997 + optional: true 1904 1998 1999 + '@esbuild/linux-x64@0.27.3': 2000 + optional: true 1905 2001 2002 + '@esbuild/netbsd-arm64@0.27.3': 2003 + optional: true 1906 2004 2005 + '@esbuild/netbsd-x64@0.23.1': 2006 + optional: true 1907 2007 2008 + '@esbuild/netbsd-x64@0.27.3': 2009 + optional: true 1908 2010 2011 + '@esbuild/openbsd-arm64@0.23.1': 2012 + optional: true 1909 2013 2014 + '@esbuild/openbsd-arm64@0.27.3': 2015 + optional: true 1910 2016 2017 + '@esbuild/openbsd-x64@0.23.1': 2018 + optional: true 1911 2019 2020 + '@esbuild/openbsd-x64@0.27.3': 2021 + optional: true 1912 2022 2023 + '@esbuild/openharmony-arm64@0.27.3': 2024 + optional: true 1913 2025 2026 + '@esbuild/sunos-x64@0.23.1': 2027 + optional: true 1914 2028 2029 + '@esbuild/sunos-x64@0.27.3': 2030 + optional: true 1915 2031 2032 + '@esbuild/win32-arm64@0.23.1': 2033 + optional: true 1916 2034 2035 + '@esbuild/win32-arm64@0.27.3': 2036 + optional: true 1917 2037 2038 + '@esbuild/win32-ia32@0.23.1': 2039 + optional: true 1918 2040 2041 + '@esbuild/win32-ia32@0.27.3': 2042 + optional: true 1919 2043 2044 + '@esbuild/win32-x64@0.23.1': 2045 + optional: true 1920 2046 1921 - 1922 - 1923 - 1924 - 1925 - 1926 - 1927 - 1928 - 1929 - 1930 - 1931 - 1932 - 1933 - 1934 - 1935 - 1936 - 1937 - 1938 - 1939 - 1940 - 1941 - 1942 - 1943 - 1944 - 1945 - 1946 - 1947 - 1948 - 1949 - 1950 - 1951 - 1952 - 1953 - 1954 - 1955 - 1956 - 1957 - 1958 - 1959 - 1960 - 1961 - 1962 - 1963 - 1964 - 1965 - 1966 - 1967 - 1968 - 1969 - 1970 - 1971 - 1972 - 1973 - 1974 - 1975 - 1976 - 1977 - 1978 - 1979 - 1980 - 1981 - 1982 - 1983 - 1984 - 1985 - 1986 - 1987 - 1988 - 1989 - 1990 - 1991 - 1992 - 1993 - 1994 - 1995 - 1996 - 1997 - 1998 - 1999 - 2000 - 2001 - 2002 - 2003 - 2004 - 2005 - 2006 - 2007 - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - '@esbuild/win32-x64@0.27.2': 2047 + '@esbuild/win32-x64@0.27.3': 2020 2048 optional: true 2021 2049 2022 - '@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)(@lezer/highlight@1.2.3)': 2050 + '@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.12)(@lezer/highlight@1.2.3)': 2023 2051 dependencies: 2024 2052 '@codemirror/language': 6.12.1 2025 2053 '@codemirror/state': 6.5.4 2026 - '@codemirror/view': 6.39.11 2054 + '@codemirror/view': 6.39.12 2027 2055 '@lezer/highlight': 1.2.3 2028 2056 2029 - '@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)(@lezer/highlight@1.2.3)': 2057 + '@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.12)(@lezer/highlight@1.2.3)': 2030 2058 dependencies: 2031 2059 '@codemirror/language': 6.12.1 2032 2060 '@codemirror/state': 6.5.4 2033 - '@codemirror/view': 6.39.11 2061 + '@codemirror/view': 6.39.12 2034 2062 '@lezer/highlight': 1.2.3 2035 2063 2036 - '@iconify-json/lucide@1.2.86': 2064 + '@iconify-json/lucide@1.2.89': 2037 2065 dependencies: 2038 2066 '@iconify/types': 2.0.0 2039 2067 2040 2068 '@iconify/tailwind4@1.2.1(tailwindcss@4.1.18)': 2041 2069 dependencies: 2042 - '@iconify/tools': 5.0.2 2070 + '@iconify/tools': 5.0.3 2043 2071 '@iconify/types': 2.0.0 2044 2072 '@iconify/utils': 3.1.0 2045 2073 tailwindcss: 4.1.18 2046 2074 2047 - '@iconify/tools@5.0.2': 2075 + '@iconify/tools@5.0.3': 2048 2076 dependencies: 2049 - '@cyberalien/svg-utils': 1.0.11 2077 + '@cyberalien/svg-utils': 1.1.1 2050 2078 '@iconify/types': 2.0.0 2051 2079 '@iconify/utils': 3.1.0 2052 2080 fflate: 0.8.2 ··· 2095 2123 2096 2124 2097 2125 2126 + '@jsr/mary__ds-queue': 0.1.3 2127 + '@jsr/mary__mutex': 0.1.0 2098 2128 2129 + '@lezer/common@1.5.1': {} 2099 2130 2131 + '@lezer/highlight@1.2.3': 2132 + dependencies: 2133 + '@lezer/common': 1.5.1 2100 2134 2101 - 2102 - 2103 - 2104 - 2105 - 2106 - 2107 - 2135 + '@lezer/json@1.0.3': 2108 2136 dependencies: 2109 - '@lezer/common': 1.5.0 2137 + '@lezer/common': 1.5.1 2110 2138 '@lezer/highlight': 1.2.3 2111 - '@lezer/lr': 1.4.7 2139 + '@lezer/lr': 1.4.8 2112 2140 2113 - '@lezer/lr@1.4.7': 2141 + '@lezer/lr@1.4.8': 2114 2142 dependencies: 2115 - '@lezer/common': 1.5.0 2116 - 2143 + '@lezer/common': 1.5.1 2117 2144 2145 + '@marijn/find-cluster-break@1.0.2': {} 2118 2146 2119 2147 '@noble/secp256k1@3.0.0': {} 2120 2148 2121 - '@rollup/rollup-android-arm-eabi@4.56.0': 2149 + '@rollup/rollup-android-arm-eabi@4.57.1': 2122 2150 optional: true 2123 2151 2124 - '@rollup/rollup-android-arm64@4.56.0': 2152 + '@rollup/rollup-android-arm64@4.57.1': 2125 2153 optional: true 2126 2154 2127 - '@rollup/rollup-darwin-arm64@4.56.0': 2155 + '@rollup/rollup-darwin-arm64@4.57.1': 2128 2156 optional: true 2129 2157 2130 - '@rollup/rollup-darwin-x64@4.56.0': 2158 + '@rollup/rollup-darwin-x64@4.57.1': 2131 2159 optional: true 2132 2160 2133 - '@rollup/rollup-freebsd-arm64@4.56.0': 2161 + '@rollup/rollup-freebsd-arm64@4.57.1': 2134 2162 optional: true 2135 2163 2136 - '@rollup/rollup-freebsd-x64@4.56.0': 2164 + '@rollup/rollup-freebsd-x64@4.57.1': 2137 2165 optional: true 2138 2166 2139 - '@rollup/rollup-linux-arm-gnueabihf@4.56.0': 2167 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 2140 2168 optional: true 2141 2169 2142 - '@rollup/rollup-linux-arm-musleabihf@4.56.0': 2170 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 2143 2171 optional: true 2144 2172 2145 - '@rollup/rollup-linux-arm64-gnu@4.56.0': 2173 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 2146 2174 optional: true 2147 2175 2148 - '@rollup/rollup-linux-arm64-musl@4.56.0': 2176 + '@rollup/rollup-linux-arm64-musl@4.57.1': 2149 2177 optional: true 2150 2178 2151 - '@rollup/rollup-linux-loong64-gnu@4.56.0': 2179 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 2152 2180 optional: true 2153 2181 2154 - '@rollup/rollup-linux-loong64-musl@4.56.0': 2182 + '@rollup/rollup-linux-loong64-musl@4.57.1': 2155 2183 optional: true 2156 2184 2157 - '@rollup/rollup-linux-ppc64-gnu@4.56.0': 2185 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 2158 2186 optional: true 2159 2187 2160 - '@rollup/rollup-linux-ppc64-musl@4.56.0': 2188 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 2161 2189 optional: true 2162 2190 2163 - '@rollup/rollup-linux-riscv64-gnu@4.56.0': 2191 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 2164 2192 optional: true 2165 2193 2166 - '@rollup/rollup-linux-riscv64-musl@4.56.0': 2194 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 2167 2195 optional: true 2168 2196 2169 - '@rollup/rollup-linux-s390x-gnu@4.56.0': 2197 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 2170 2198 optional: true 2171 2199 2172 - '@rollup/rollup-linux-x64-gnu@4.56.0': 2200 + '@rollup/rollup-linux-x64-gnu@4.57.1': 2173 2201 optional: true 2174 2202 2175 - '@rollup/rollup-linux-x64-musl@4.56.0': 2203 + '@rollup/rollup-linux-x64-musl@4.57.1': 2176 2204 optional: true 2177 2205 2178 - '@rollup/rollup-openbsd-x64@4.56.0': 2206 + '@rollup/rollup-openbsd-x64@4.57.1': 2179 2207 optional: true 2180 2208 2181 - '@rollup/rollup-openharmony-arm64@4.56.0': 2209 + '@rollup/rollup-openharmony-arm64@4.57.1': 2182 2210 optional: true 2183 2211 2184 - '@rollup/rollup-win32-arm64-msvc@4.56.0': 2212 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 2185 2213 optional: true 2186 2214 2187 - '@rollup/rollup-win32-ia32-msvc@4.56.0': 2215 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 2188 2216 optional: true 2189 2217 2190 - '@rollup/rollup-win32-x64-gnu@4.56.0': 2218 + '@rollup/rollup-win32-x64-gnu@4.57.1': 2191 2219 optional: true 2192 2220 2193 - '@rollup/rollup-win32-x64-msvc@4.56.0': 2221 + '@rollup/rollup-win32-x64-msvc@4.57.1': 2194 2222 optional: true 2195 2223 2196 2224 '@skyware/firehose@0.5.2': ··· 2209 2237 2210 2238 2211 2239 2212 - 2213 - 2214 - 2215 - 2216 - 2217 - 2218 - 2240 + '@tailwindcss/node@4.1.18': 2241 + dependencies: 2242 + '@jridgewell/remapping': 2.3.5 2243 + enhanced-resolve: 5.19.0 2244 + jiti: 2.6.1 2245 + lightningcss: 1.30.2 2246 + magic-string: 0.30.21 2219 2247 2220 2248 2221 2249 ··· 2270 2298 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 2271 2299 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 2272 2300 2273 - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))': 2301 + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))': 2274 2302 dependencies: 2275 2303 '@tailwindcss/node': 4.1.18 2276 2304 '@tailwindcss/oxide': 4.1.18 2277 2305 tailwindcss: 4.1.18 2278 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2306 + vite: 7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2279 2307 2280 2308 '@types/babel__core@7.20.5': 2281 2309 dependencies: 2282 - '@babel/parser': 7.28.6 2283 - '@babel/types': 7.28.6 2310 + '@babel/parser': 7.29.0 2311 + '@babel/types': 7.29.0 2284 2312 '@types/babel__generator': 7.27.0 2285 2313 '@types/babel__template': 7.4.4 2286 2314 '@types/babel__traverse': 7.28.0 2287 2315 2288 2316 '@types/babel__generator@7.27.0': 2289 2317 dependencies: 2290 - '@babel/types': 7.28.6 2318 + '@babel/types': 7.29.0 2291 2319 2292 2320 '@types/babel__template@7.4.4': 2293 2321 dependencies: 2294 - '@babel/parser': 7.28.6 2295 - '@babel/types': 7.28.6 2322 + '@babel/parser': 7.29.0 2323 + '@babel/types': 7.29.0 2296 2324 2297 2325 '@types/babel__traverse@7.28.0': 2298 2326 dependencies: 2299 - '@babel/types': 7.28.6 2327 + '@babel/types': 7.29.0 2300 2328 2301 - '@types/bun@1.3.6': 2329 + '@types/bun@1.3.8': 2302 2330 dependencies: 2303 - bun-types: 1.3.6 2331 + bun-types: 1.3.8 2304 2332 2305 2333 '@types/estree@1.0.8': {} 2306 2334 2307 - '@types/node@25.0.10': 2335 + '@types/node@25.2.1': 2308 2336 dependencies: 2309 2337 undici-types: 7.16.0 2310 2338 2311 2339 acorn@8.15.0: {} 2312 2340 2313 - babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.6): 2341 + babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.29.0): 2314 2342 dependencies: 2315 - '@babel/core': 7.28.6 2343 + '@babel/core': 7.29.0 2316 2344 '@babel/helper-module-imports': 7.18.6 2317 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) 2318 - '@babel/types': 7.28.6 2345 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) 2346 + '@babel/types': 7.29.0 2319 2347 html-entities: 2.3.3 2320 2348 parse5: 7.3.0 2321 2349 2322 - babel-preset-solid@1.9.10(@babel/core@7.28.6)(solid-js@1.9.11): 2350 + babel-preset-solid@1.9.10(@babel/core@7.29.0)(solid-js@1.9.11): 2323 2351 dependencies: 2324 - '@babel/core': 7.28.6 2325 - babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.28.6) 2352 + '@babel/core': 7.29.0 2353 + babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.29.0) 2326 2354 optionalDependencies: 2327 2355 solid-js: 1.9.11 2328 2356 2329 - baseline-browser-mapping@2.9.17: {} 2357 + baseline-browser-mapping@2.9.19: {} 2330 2358 2331 2359 boolbase@1.0.0: {} 2332 2360 2333 2361 browserslist@4.28.1: 2334 2362 dependencies: 2335 - baseline-browser-mapping: 2.9.17 2336 - caniuse-lite: 1.0.30001766 2337 - electron-to-chromium: 1.5.278 2363 + baseline-browser-mapping: 2.9.19 2364 + caniuse-lite: 1.0.30001769 2365 + electron-to-chromium: 1.5.286 2338 2366 node-releases: 2.0.27 2339 2367 update-browserslist-db: 1.2.3(browserslist@4.28.1) 2340 2368 2341 - bun-types@1.3.6: 2369 + bun-types@1.3.8: 2342 2370 dependencies: 2343 - '@types/node': 25.0.10 2344 - 2345 - caniuse-lite@1.0.30001766: {} 2346 - 2371 + '@types/node': 25.2.1 2347 2372 2373 + caniuse-lite@1.0.30001769: {} 2348 2374 2375 + codemirror@6.0.2: 2376 + dependencies: 2349 2377 '@codemirror/autocomplete': 6.20.0 2350 2378 '@codemirror/commands': 6.10.1 2351 2379 '@codemirror/language': 6.12.1 2352 - '@codemirror/lint': 6.9.2 2380 + '@codemirror/lint': 6.9.3 2353 2381 '@codemirror/search': 6.6.0 2354 2382 '@codemirror/state': 6.5.4 2355 - '@codemirror/view': 6.39.11 2383 + '@codemirror/view': 6.39.12 2356 2384 2357 2385 commander@11.1.0: {} 2358 2386 ··· 2412 2440 domelementtype: 2.3.0 2413 2441 domhandler: 5.0.3 2414 2442 2415 - electron-to-chromium@1.5.278: {} 2443 + electron-to-chromium@1.5.286: {} 2416 2444 2417 - enhanced-resolve@5.18.4: 2445 + enhanced-resolve@5.19.0: 2418 2446 dependencies: 2447 + graceful-fs: 4.2.11 2448 + tapable: 2.3.0 2419 2449 2420 2450 2421 2451 ··· 2446 2476 2447 2477 2448 2478 2479 + '@esbuild/win32-x64': 0.23.1 2480 + optional: true 2449 2481 2482 + esbuild@0.27.3: 2483 + optionalDependencies: 2484 + '@esbuild/aix-ppc64': 0.27.3 2485 + '@esbuild/android-arm': 0.27.3 2486 + '@esbuild/android-arm64': 0.27.3 2487 + '@esbuild/android-x64': 0.27.3 2488 + '@esbuild/darwin-arm64': 0.27.3 2489 + '@esbuild/darwin-x64': 0.27.3 2490 + '@esbuild/freebsd-arm64': 0.27.3 2491 + '@esbuild/freebsd-x64': 0.27.3 2492 + '@esbuild/linux-arm': 0.27.3 2493 + '@esbuild/linux-arm64': 0.27.3 2494 + '@esbuild/linux-ia32': 0.27.3 2495 + '@esbuild/linux-loong64': 0.27.3 2496 + '@esbuild/linux-mips64el': 0.27.3 2497 + '@esbuild/linux-ppc64': 0.27.3 2498 + '@esbuild/linux-riscv64': 0.27.3 2499 + '@esbuild/linux-s390x': 0.27.3 2500 + '@esbuild/linux-x64': 0.27.3 2501 + '@esbuild/netbsd-arm64': 0.27.3 2502 + '@esbuild/netbsd-x64': 0.27.3 2503 + '@esbuild/openbsd-arm64': 0.27.3 2504 + '@esbuild/openbsd-x64': 0.27.3 2505 + '@esbuild/openharmony-arm64': 0.27.3 2506 + '@esbuild/sunos-x64': 0.27.3 2507 + '@esbuild/win32-arm64': 0.27.3 2508 + '@esbuild/win32-ia32': 0.27.3 2509 + '@esbuild/win32-x64': 0.27.3 2450 2510 2451 - 2452 - 2453 - 2454 - 2455 - 2456 - 2457 - 2458 - 2459 - 2460 - 2461 - 2462 - 2463 - 2464 - 2465 - 2466 - 2467 - 2468 - 2469 - 2470 - 2471 - 2472 - 2473 - 2474 - 2475 - 2476 - 2477 - 2478 - 2479 - 2480 - 2481 - 2482 - 2483 - 2511 + escalade@3.2.0: {} 2484 2512 2485 2513 2486 2514 ··· 2501 2529 2502 2530 gensync@1.0.0-beta.2: {} 2503 2531 2504 - get-tsconfig@4.13.0: 2532 + get-tsconfig@4.13.5: 2505 2533 dependencies: 2506 2534 resolve-pkg-maps: 1.0.0 2507 2535 optional: true ··· 2657 2685 resolve-pkg-maps@1.0.0: 2658 2686 optional: true 2659 2687 2660 - rollup@4.56.0: 2688 + rollup@4.57.1: 2661 2689 dependencies: 2662 2690 '@types/estree': 1.0.8 2663 2691 optionalDependencies: 2664 - '@rollup/rollup-android-arm-eabi': 4.56.0 2665 - '@rollup/rollup-android-arm64': 4.56.0 2666 - '@rollup/rollup-darwin-arm64': 4.56.0 2667 - '@rollup/rollup-darwin-x64': 4.56.0 2668 - '@rollup/rollup-freebsd-arm64': 4.56.0 2669 - '@rollup/rollup-freebsd-x64': 4.56.0 2670 - '@rollup/rollup-linux-arm-gnueabihf': 4.56.0 2671 - '@rollup/rollup-linux-arm-musleabihf': 4.56.0 2672 - '@rollup/rollup-linux-arm64-gnu': 4.56.0 2673 - '@rollup/rollup-linux-arm64-musl': 4.56.0 2674 - '@rollup/rollup-linux-loong64-gnu': 4.56.0 2675 - '@rollup/rollup-linux-loong64-musl': 4.56.0 2676 - '@rollup/rollup-linux-ppc64-gnu': 4.56.0 2677 - '@rollup/rollup-linux-ppc64-musl': 4.56.0 2678 - '@rollup/rollup-linux-riscv64-gnu': 4.56.0 2679 - '@rollup/rollup-linux-riscv64-musl': 4.56.0 2680 - '@rollup/rollup-linux-s390x-gnu': 4.56.0 2681 - '@rollup/rollup-linux-x64-gnu': 4.56.0 2682 - '@rollup/rollup-linux-x64-musl': 4.56.0 2683 - '@rollup/rollup-openbsd-x64': 4.56.0 2684 - '@rollup/rollup-openharmony-arm64': 4.56.0 2685 - '@rollup/rollup-win32-arm64-msvc': 4.56.0 2686 - '@rollup/rollup-win32-ia32-msvc': 4.56.0 2687 - '@rollup/rollup-win32-x64-gnu': 4.56.0 2688 - '@rollup/rollup-win32-x64-msvc': 4.56.0 2692 + '@rollup/rollup-android-arm-eabi': 4.57.1 2693 + '@rollup/rollup-android-arm64': 4.57.1 2694 + '@rollup/rollup-darwin-arm64': 4.57.1 2695 + '@rollup/rollup-darwin-x64': 4.57.1 2696 + '@rollup/rollup-freebsd-arm64': 4.57.1 2697 + '@rollup/rollup-freebsd-x64': 4.57.1 2698 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 2699 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 2700 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 2701 + '@rollup/rollup-linux-arm64-musl': 4.57.1 2702 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 2703 + '@rollup/rollup-linux-loong64-musl': 4.57.1 2704 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 2705 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 2706 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 2707 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 2708 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 2709 + '@rollup/rollup-linux-x64-gnu': 4.57.1 2710 + '@rollup/rollup-linux-x64-musl': 4.57.1 2711 + '@rollup/rollup-openbsd-x64': 4.57.1 2712 + '@rollup/rollup-openharmony-arm64': 4.57.1 2713 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 2714 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 2715 + '@rollup/rollup-win32-x64-gnu': 4.57.1 2716 + '@rollup/rollup-win32-x64-msvc': 4.57.1 2689 2717 fsevents: 2.3.3 2690 2718 2691 2719 sax@1.4.4: {} ··· 2706 2734 2707 2735 solid-refresh@0.6.3(solid-js@1.9.11): 2708 2736 dependencies: 2709 - '@babel/generator': 7.28.6 2737 + '@babel/generator': 7.29.1 2710 2738 '@babel/helper-module-imports': 7.28.6 2711 - '@babel/types': 7.28.6 2739 + '@babel/types': 7.29.0 2712 2740 solid-js: 1.9.11 2713 2741 transitivePeerDependencies: 2714 2742 - supports-color ··· 2741 2769 tsx@4.19.2: 2742 2770 dependencies: 2743 2771 esbuild: 0.23.1 2744 - get-tsconfig: 4.13.0 2772 + get-tsconfig: 4.13.5 2745 2773 optionalDependencies: 2746 2774 fsevents: 2.3.3 2747 2775 optional: true ··· 2760 2788 escalade: 3.2.0 2761 2789 picocolors: 1.1.1 2762 2790 2763 - vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2791 + vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2764 2792 dependencies: 2765 - '@babel/core': 7.28.6 2793 + '@babel/core': 7.29.0 2766 2794 '@types/babel__core': 7.20.5 2767 - babel-preset-solid: 1.9.10(@babel/core@7.28.6)(solid-js@1.9.11) 2795 + babel-preset-solid: 1.9.10(@babel/core@7.29.0)(solid-js@1.9.11) 2768 2796 merge-anything: 5.1.7 2769 2797 solid-js: 1.9.11 2770 2798 solid-refresh: 0.6.3(solid-js@1.9.11) 2771 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2772 - vitefu: 1.1.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 2799 + vite: 7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2800 + vitefu: 1.1.1(vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 2773 2801 transitivePeerDependencies: 2774 2802 - supports-color 2775 2803 2776 - vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2): 2804 + vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2): 2777 2805 dependencies: 2778 - esbuild: 0.27.2 2806 + esbuild: 0.27.3 2779 2807 fdir: 6.5.0(picomatch@4.0.3) 2780 2808 picomatch: 4.0.3 2781 2809 postcss: 8.5.6 2782 - rollup: 4.56.0 2810 + rollup: 4.57.1 2783 2811 tinyglobby: 0.2.15 2784 2812 optionalDependencies: 2785 - '@types/node': 25.0.10 2813 + '@types/node': 25.2.1 2786 2814 fsevents: 2.3.3 2787 2815 jiti: 2.6.1 2788 2816 lightningcss: 1.30.2 2789 2817 tsx: 4.19.2 2790 2818 2791 - vitefu@1.1.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2819 + vitefu@1.1.1(vite@7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2792 2820 optionalDependencies: 2793 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2821 + vite: 7.3.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2794 2822 2795 2823 w3c-keyname@2.2.8: {} 2796 2824
public/avatar/neko.moe.observer.jpg

This is a binary file and will not be displayed.

public/avatar/nel.pet.jpg

This is a binary file and will not be displayed.

public/avatar/shi.gg.jpg

This is a binary file and will not be displayed.

+59
scripts/generate-metadata.js
··· 1 + import { mkdirSync, writeFileSync } from "fs"; 2 + import { dirname } from "path"; 3 + import { fileURLToPath } from "url"; 4 + 5 + const __filename = fileURLToPath(import.meta.url); 6 + const __dirname = dirname(__filename); 7 + 8 + const domain = process.env.APP_DOMAIN || "pdsls.dev"; 9 + const protocol = process.env.APP_PROTOCOL || "https"; 10 + const baseUrl = `${protocol}://${domain}`; 11 + 12 + const configs = { 13 + oauth: { 14 + name: "OAuth metadata", 15 + path: `${__dirname}/../public/oauth-client-metadata.json`, 16 + content: 17 + JSON.stringify( 18 + { 19 + client_id: `${baseUrl}/oauth-client-metadata.json`, 20 + client_name: "PDSls", 21 + client_uri: baseUrl, 22 + logo_uri: `${baseUrl}/favicon.ico`, 23 + redirect_uris: [`${baseUrl}/`], 24 + scope: "atproto repo:*?action=create repo:*?action=update repo:*?action=delete blob:*/*", 25 + grant_types: ["authorization_code", "refresh_token"], 26 + response_types: ["code"], 27 + token_endpoint_auth_method: "none", 28 + application_type: "web", 29 + dpop_bound_access_tokens: true, 30 + }, 31 + null, 32 + 2, 33 + ) + "\n", 34 + }, 35 + opensearch: { 36 + name: "OpenSearch XML", 37 + path: `${__dirname}/../public/opensearch.xml`, 38 + content: `<?xml version="1.0" encoding="UTF-8"?> 39 + <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/"> 40 + <ShortName>PDSls</ShortName> 41 + <Description>Search the Atmosphere</Description> 42 + <InputEncoding>UTF-8</InputEncoding> 43 + <Image width="16" height="16" type="image/x-icon">${baseUrl}/favicon.ico</Image> 44 + <Url type="text/html" method="get" template="${baseUrl}/?q={searchTerms}"/> 45 + <moz:SearchForm>${baseUrl}</moz:SearchForm> 46 + </OpenSearchDescription>`, 47 + }, 48 + }; 49 + 50 + try { 51 + Object.values(configs).forEach((config) => { 52 + mkdirSync(dirname(config.path), { recursive: true }); 53 + writeFileSync(config.path, config.content); 54 + console.log(`Generated ${config.name} for ${baseUrl}`); 55 + }); 56 + } catch (error) { 57 + console.error("Failed to generate files:", error); 58 + process.exit(1); 59 + }
public/headers/montreal.webp

This is a binary file and will not be displayed.

+8 -2
src/views/repo.tsx
··· 325 325 </div> 326 326 <div class="flex gap-1"> 327 327 <Show when={error() && error() !== "Missing PDS"}> 328 - <div class="flex items-center gap-1 font-medium text-red-500 dark:text-red-400"> 329 - <span class="iconify lucide--alert-triangle"></span> 328 + <div class="flex items-center gap-1 rounded-md border border-red-500 px-1.5 py-0.5 text-xs font-medium text-red-500 sm:text-sm dark:border-red-400 dark:text-red-400"> 329 + <span 330 + class={`iconify ${ 331 + error() === "Deactivated" ? "lucide--user-round-x" 332 + : error() === "Takendown" ? "lucide--shield-ban" 333 + : "lucide--unplug" 334 + }`} 335 + ></span> 330 336 <span>{error()}</span> 331 337 </div> 332 338 </Show>
+8 -3
src/components/video-player.tsx
··· 1 - import { onMount } from "solid-js"; 1 + import { onCleanup, onMount } from "solid-js"; 2 2 import { pds } from "./navbar"; 3 3 4 4 export interface VideoPlayerProps { ··· 9 9 10 10 const VideoPlayer = (props: VideoPlayerProps) => { 11 11 let video!: HTMLVideoElement; 12 + let objectUrl: string | undefined; 12 13 13 14 onMount(async () => { 14 15 // thanks bf <3 ··· 17 18 ); 18 19 if (!res.ok) throw new Error(res.statusText); 19 20 const blob = await res.blob(); 20 - const url = URL.createObjectURL(blob); 21 - if (video) video.src = url; 21 + objectUrl = URL.createObjectURL(blob); 22 + if (video) video.src = objectUrl; 23 + }); 24 + 25 + onCleanup(() => { 26 + if (objectUrl) URL.revokeObjectURL(objectUrl); 22 27 }); 23 28 24 29 return (