Compare changes

Choose any two refs to compare.

+62 -10
atproto-notifications/package-lock.json
··· 8 8 "name": "atproto-notifications", 9 9 "version": "0.0.0", 10 10 "dependencies": { 11 + "@atcute/client": "^4.0.3", 11 12 "@atcute/identity-resolver": "^1.1.3", 12 13 "@uidotdev/usehooks": "^2.4.1", 13 14 "lexicons": "file:../lexicons", 14 15 "psl": "^1.15.0", 15 16 "react": "^19.1.0", 16 17 "react-dom": "^19.1.0", 18 + "react-error-boundary": "^6.0.0", 17 19 "react-router": "^7.6.3", 18 - "react-time-ago": "^7.3.3" 20 + "react-time-ago": "^7.3.3", 21 + "reactjs-popup": "^2.0.6" 19 22 }, 20 23 "devDependencies": { 21 24 "@eslint/js": "^9.29.0", ··· 32 35 } 33 36 }, 34 37 "../lexicons": { 35 - "version": "0.0.1" 38 + "version": "0.0.1", 39 + "dependencies": { 40 + "@atcute/client": "^4.0.3", 41 + "@atcute/identity-resolver": "^1.1.3", 42 + "jsonpath-plus": "^10.3.0", 43 + "psl": "^1.15.0" 44 + } 36 45 }, 37 46 "node_modules/@ampproject/remapping": { 38 47 "version": "2.3.0", ··· 48 57 "node": ">=6.0.0" 49 58 } 50 59 }, 60 + "node_modules/@atcute/client": { 61 + "version": "4.0.3", 62 + "resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.3.tgz", 63 + "integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==", 64 + "license": "MIT", 65 + "dependencies": { 66 + "@atcute/identity": "^1.0.2", 67 + "@atcute/lexicons": "^1.0.3" 68 + } 69 + }, 51 70 "node_modules/@atcute/identity": { 52 71 "version": "1.0.3", 53 72 "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.0.3.tgz", 54 73 "integrity": "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==", 55 74 "license": "0BSD", 56 - "peer": true, 57 75 "dependencies": { 58 76 "@atcute/lexicons": "^1.0.4", 59 77 "@badrap/valita": "^0.4.5" ··· 313 331 }, 314 332 "peerDependencies": { 315 333 "@babel/core": "^7.0.0-0" 334 + } 335 + }, 336 + "node_modules/@babel/runtime": { 337 + "version": "7.27.6", 338 + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", 339 + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", 340 + "license": "MIT", 341 + "engines": { 342 + "node": ">=6.9.0" 316 343 } 317 344 }, 318 345 "node_modules/@babel/template": { ··· 948 975 } 949 976 }, 950 977 "node_modules/@eslint/plugin-kit": { 951 - "version": "0.3.2", 952 - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", 953 - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", 978 + "version": "0.3.4", 979 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", 980 + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", 954 981 "dev": true, 955 982 "license": "Apache-2.0", 956 983 "dependencies": { 957 - "@eslint/core": "^0.15.0", 984 + "@eslint/core": "^0.15.1", 958 985 "levn": "^0.4.1" 959 986 }, 960 987 "engines": { ··· 962 989 } 963 990 }, 964 991 "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { 965 - "version": "0.15.0", 966 - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", 967 - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", 992 + "version": "0.15.1", 993 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", 994 + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", 968 995 "dev": true, 969 996 "license": "Apache-2.0", 970 997 "dependencies": { ··· 3063 3090 "react": "^19.1.0" 3064 3091 } 3065 3092 }, 3093 + "node_modules/react-error-boundary": { 3094 + "version": "6.0.0", 3095 + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz", 3096 + "integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==", 3097 + "license": "MIT", 3098 + "dependencies": { 3099 + "@babel/runtime": "^7.12.5" 3100 + }, 3101 + "peerDependencies": { 3102 + "react": ">=16.13.1" 3103 + } 3104 + }, 3066 3105 "node_modules/react-is": { 3067 3106 "version": "16.13.1", 3068 3107 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", ··· 3115 3154 "javascript-time-ago": "^2.3.7", 3116 3155 "react": ">=0.16.8", 3117 3156 "react-dom": ">=0.16.8" 3157 + } 3158 + }, 3159 + "node_modules/reactjs-popup": { 3160 + "version": "2.0.6", 3161 + "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.6.tgz", 3162 + "integrity": "sha512-A+tt+x9wdgZiZjv0e2WzYLD3IfFwJALaRaqwrCSXGjo0iQdsry/EtBEbQXRSmQs7cHmOi5eytCiSlOm8k4C+dg==", 3163 + "license": "MIT", 3164 + "engines": { 3165 + "node": ">=10" 3166 + }, 3167 + "peerDependencies": { 3168 + "react": ">=16", 3169 + "react-dom": ">=16" 3118 3170 } 3119 3171 }, 3120 3172 "node_modules/relative-time-format": {
+4 -1
atproto-notifications/package.json
··· 11 11 "preview": "vite preview" 12 12 }, 13 13 "dependencies": { 14 + "@atcute/client": "^4.0.3", 14 15 "@atcute/identity-resolver": "^1.1.3", 15 16 "@uidotdev/usehooks": "^2.4.1", 16 17 "lexicons": "file:../lexicons", 17 18 "psl": "^1.15.0", 18 19 "react": "^19.1.0", 19 20 "react-dom": "^19.1.0", 21 + "react-error-boundary": "^6.0.0", 20 22 "react-router": "^7.6.3", 21 - "react-time-ago": "^7.3.3" 23 + "react-time-ago": "^7.3.3", 24 + "reactjs-popup": "^2.0.6" 22 25 }, 23 26 "devDependencies": { 24 27 "@eslint/js": "^9.29.0",
atproto-notifications/public/icons/microcosm.png

This is a binary file and will not be displayed.

+21
atproto-notifications/src/components/Notification.css
··· 7 7 box-sizing: border-box; 8 8 display: flex; 9 9 justify-content: space-between; 10 + gap: 0.5rem; 10 11 } 11 12 a.notification { 12 13 font: inherit; ··· 22 23 border-bottom-color: hsla(0, 0%, 0%, 0.3); 23 24 } 24 25 26 + .notification.error { 27 + background: hsla(347, 72%, 20%, 0.333); 28 + align-items: center; 29 + } 30 + .notification.error p { 31 + margin: 0; 32 + } 33 + 34 + .notification-info { 35 + display: flex; 36 + align-items: baseline; 37 + } 38 + 25 39 .handle { 26 40 color: skyblue; 41 + } 42 + 43 + .notification-context { 44 + font-size: 0.8rem; 45 + opacity: 0.667; 46 + margin: 0.25rem 0 0; 47 + max-width: 48em; 27 48 } 28 49 29 50 .notification-when {
+49 -15
atproto-notifications/src/components/Notification.tsx
··· 1 + import { useState, useEffect } from 'react'; 1 2 import ReactTimeAgo from 'react-time-ago'; 2 3 import psl from 'psl'; 3 - import lexicons from 'lexicons'; 4 + import { default as lexicons, getLink, getContext } from 'lexicons'; 4 5 import { resolveDid } from '../atproto/resolve'; 5 6 import { Fetch } from './Fetch'; 6 7 7 8 import './Notification.css'; 8 9 10 + export function fallbackRender({ error, resetErrorBoundary }) { 11 + console.error('rendering fallback for error', error); 12 + return ( 13 + <div className="notification error"> 14 + <p>sorry, something went wrong trying to show this notification</p> 15 + <p><button onClick={resetErrorBoundary}>retry</button></p> 16 + </div> 17 + ); 18 + } 19 + 9 20 export function Notification({ app, group, source, source_record, source_did, subject, timestamp }) { 21 + const [resolvedLink, setResolvedLink] = useState(null); 22 + const [resolvedContext, setResolvedContext] = useState([]); 23 + 24 + useEffect(() => { 25 + (async () => { 26 + const link = await getLink(source, source_record, subject); 27 + if (link) setResolvedLink(link); 28 + })(); 29 + (async() => { 30 + const context = await getContext(source, source_record, subject); 31 + setResolvedContext(context); 32 + })(); 33 + }, [source, source_record, subject]); 10 34 11 35 // TODO: clean up / move this to lexicons package? 12 36 let title = source; ··· 23 47 let link = lex?.clients[0]?.notifications; 24 48 appName = lex?.name; 25 49 const sourceRemainder = source.slice(app.length + 1); 26 - title = lex?.known_sources[sourceRemainder] ?? source; 50 + title = lex?.known_sources[sourceRemainder]?.name ?? source; 27 51 28 52 let directLink; 29 53 if (subject.startsWith('did:')) { ··· 48 72 49 73 directLink = lex 50 74 ?.clients[0] 51 - ?.direct_links[`at_uri:${sourceRemainder}`] 75 + ?.direct_links?.[`at_uri:${sourceRemainder}`] 52 76 ?.replace('{subject.did}', did) 53 77 ?.replace('{subject.collection}', collection) 54 78 ?.replace('{subject.rkey}', rest.join('/') || null) ··· 56 80 ?.replace('{source_record.collection}', sCollection) 57 81 ?.replace('{source_record.rkey}', sRest.join('/') || null); 58 82 } 59 - link = directLink ?? link; 83 + link = resolvedLink ?? directLink ?? link; 84 + 85 + let contextClipped = resolvedContext.join(' '); 86 + if (contextClipped.length > 240) { 87 + contextClipped = contextClipped.slice(0, 239) + 'โ€ฆ'; 88 + } 60 89 61 90 const contents = ( 62 91 <> ··· 64 93 {icon && ( 65 94 <img className="app-icon" src={icon} title={appName ?? app} alt="" /> 66 95 )} 67 - {title} from 68 - {' '} 69 - {source_did ? ( 70 - <Fetch 71 - using={resolveDid} 72 - args={[source_did]} 73 - ok={handle => <span className="handle">@{handle}</span>} 74 - /> 75 - ) : ( 76 - source_record 77 - )} 96 + <div> 97 + {title} from 98 + {' '} 99 + {source_did ? ( 100 + <Fetch 101 + using={resolveDid} 102 + args={[source_did]} 103 + ok={handle => <span className="handle">@{handle}</span>} 104 + /> 105 + ) : ( 106 + source_record 107 + )} 108 + {contextClipped.length > 0 && ( 109 + <p className="notification-context">{contextClipped}</p> 110 + )} 111 + </div> 78 112 </div> 79 113 {timestamp && ( 80 114 <div className="notification-when">
+39 -7
atproto-notifications/src/components/NotificationSettings.tsx
··· 1 + import { useState, useCallback } from 'react'; 2 + import { Link } from 'react-router'; 3 + import { GetJson, postJson } from './Fetch'; 4 + import { ButtonGroup } from './Buttons'; 5 + 1 6 export function NotificationSettings({ secondary, secondaryFilter }) { 2 - if (secondary === 'all') { 3 - return <p>Notifications default: [todo: toggle mute], unknown sources: [toggle mute]</p>; 4 - } 5 - if (secondaryFilter === null) { 6 - return null; 7 - } 7 + const [notifyToggleCounter, setNotifyToggleCounter] = useState(0); 8 + 9 + // TODO move up (to chrome?) so it syncs 10 + const setGlobalNotifications = useCallback(async enabled => { 11 + const host = import.meta.env.VITE_NOTIFICATIONS_HOST; 12 + const url = new URL('/global-notify', host); 13 + try { 14 + await postJson(url, JSON.stringify({ notify_enabled: enabled }), true) 15 + } catch (err) { 16 + console.error('failed to set self-notify setting', err); 17 + } 18 + setNotifyToggleCounter(n => n + 1); 19 + }); 20 + 21 + if (secondary !== 'all') return; 22 + 8 23 return ( 9 - <p>{secondaryFilter} [todo: toggle mute]</p> 24 + <div className="feed-filter-type"> 25 + <h4>All notifications:</h4> 26 + <GetJson 27 + key={notifyToggleCounter} 28 + endpoint="/global-notify" 29 + credentials 30 + ok={({ notify_enabled }) => ( 31 + <ButtonGroup 32 + options={[ 33 + {val: 'paused', label: <>โธ&nbsp;&nbsp;pause{!notify_enabled && 'd'}</>}, 34 + {val: 'active', label: <>โ–ถ&nbsp;&nbsp;{notify_enabled ? 'notifications active' : 'enable notifications'}</>}, 35 + ]} 36 + current={notify_enabled ? 'active' : 'paused'} 37 + onChange={val => setGlobalNotifications(val === 'active')} 38 + /> 39 + )} 40 + /> 41 + </div> 10 42 ); 11 43 }
+5 -22
atproto-notifications/src/components/SecretPassword.jsx
··· 2 2 import { PostJson } from './Fetch'; 3 3 4 4 export function SecretPassword({ did, role }) { 5 - const [begun, setBegun] = useState(false); 6 - const [pw, setPw] = useState(''); 7 5 const [submission, setSubmission] = useState(0); 8 6 const [submitting, setSubmitting] = useState(false); 9 7 ··· 15 13 16 14 return ( 17 15 <form method="post" onSubmit={handleSubmit}> 18 - <h2>Secret password required</h2> 19 - <p>This demo is not ready for public yet, but you can get early access as a <a href="https://github.com/sponsors/uniphil/" target="_blank">github sponsor</a> or <a href="https://ko-fi.com/bad_example" target="_blank">ko-fi supporter</a>.</p> 16 + <h2>Secret early access</h2> 17 + <p>This demo is still in development! Your support helps keep it going: <a href="https://github.com/sponsors/uniphil/" target="_blank">github sponsors</a>, <a href="https://ko-fi.com/bad_example" target="_blank">ko-fi</a>.</p> 20 18 21 19 {submitting ? ( 22 20 <PostJson 23 21 key={submission} 24 22 endpoint="/super-top-secret-access" 25 - data={{ secret_password: pw }} 23 + data={{ secret_password: "letmein" }} 26 24 credentials 27 25 loading={() => (<>Checking&hellip;</>)} 28 26 error={e => { ··· 41 39 <p style={{ color: "#9f0" }}>Secret password accepted.</p> 42 40 <p> 43 41 {/* an <a> tag, not a <Link>, on purpose so we relaod for our role */} 44 - <a className="button" href="/early"> 42 + <a className="button" href="/early?hello"> 45 43 Continue 46 44 </a> 47 45 </p> ··· 50 48 /> 51 49 ) : ( 52 50 <p> 53 - <label> 54 - Password: 55 - {' '} 56 - <input 57 - type="text" 58 - value={pw} 59 - onFocus={() => setBegun(true)} 60 - onChange={e => setPw(e.target.value)} 61 - /> 62 - </label> 63 - {' '} 64 - {begun && ( 65 - <button type="submit" className="subtle"> 66 - unlock 67 - </button> 68 - )} 51 + <button type="submit">Let me in</button> 69 52 </p> 70 53 )} 71 54 </form>
+1 -1
atproto-notifications/src/components/WhoAmI.tsx
··· 18 18 return ( 19 19 <iframe 20 20 src={`${origin}/prompt?app=notifications.microcosm.blue`} 21 - referrerpolicy="strict-origin" 21 + referrerPolicy="strict-origin" 22 22 ref={frameRef} 23 23 height="180" 24 24 width="360"
+72 -39
atproto-notifications/src/components/setup/Chrome.tsx
··· 1 + import { useCallback, useEffect, useState } from 'react'; 1 2 import { Link } from 'react-router'; 2 3 import { Handle } from '../User'; 4 + import { GetJson, postJson } from '../Fetch'; 3 5 import './Chrome.css'; 4 6 7 + function Header({ user, onLogout }) { 8 + return ( 9 + <header id="app-header"> 10 + <h1> 11 + <Link to="/" className="inherit-font"> 12 + spacedust notifications&nbsp;<span className="demo">demo!</span> 13 + </Link> 14 + </h1> 15 + {user && ( 16 + <div className="current-user"> 17 + <p> 18 + <span className="handle"> 19 + <Handle did={user.did} /> 20 + {user.role !== 'public' && ( 21 + <span className="chrome-role-tag"> 22 + {user.role === 'admin' ? ( 23 + <Link to="/admin" className="inherit-font">{user.role}</Link> 24 + ) : user.role === 'early' ? ( 25 + <Link to="/early" className="inherit-font">{user.role}</Link> 26 + ) : ( 27 + <>{user.role}</> 28 + )} 29 + </span> 30 + )} 31 + </span> 32 + <button className="subtle bad" onClick={onLogout}>&times;</button> 33 + </p> 34 + </div> 35 + )} 36 + </header> 37 + ); 38 + } 39 + 5 40 export function Chrome({ user, onLogout, children }) { 6 - const content = children; 7 - const logout = () => null; 41 + const [secretDevCounter, setSecretDevCounter] = useState(0); 42 + const [secretDevStatus, setSecretDevStatus] = useState(null); 43 + 44 + // ~~is this the best way~~ does it work? yeh 45 + const setSelfNotify = useCallback(async enabled => { 46 + setSecretDevStatus('pending'); 47 + const host = import.meta.env.VITE_NOTIFICATIONS_HOST; 48 + const url = new URL('/global-notify', host); 49 + try { 50 + await postJson(url, JSON.stringify({ notify_self: enabled }), true) 51 + setSecretDevStatus(null); 52 + } catch (err) { 53 + console.error('failed to set self-notify setting', err); 54 + setSecretDevStatus('failed'); 55 + } 56 + setSecretDevCounter(n => n + 1); 57 + }); 58 + 8 59 return ( 9 60 <> 10 - <header id="app-header"> 11 - <h1> 12 - <Link to="/" className="inherit-font"> 13 - spacedust notifications&nbsp;<span className="demo">demo!</span> 14 - </Link> 15 - </h1> 16 - {user && ( 17 - <div className="current-user"> 18 - <p> 19 - <span className="handle"> 20 - <Handle did={user.did} /> 21 - {user.role !== 'public' && ( 22 - <span className="chrome-role-tag"> 23 - {user.role === 'admin' ? ( 24 - <Link to="/admin" className="inherit-font">{user.role}</Link> 25 - ) : user.role === 'early' ? ( 26 - <Link to="/early" className="inherit-font">{user.role}</Link> 27 - ) : ( 28 - <>{user.role}</> 29 - )} 30 - </span> 31 - )} 32 - </span> 33 - <button className="subtle bad" onClick={onLogout}>&times;</button> 34 - </p> 35 - </div> 36 - )} 37 - </header> 61 + <Header user={user} onLogout={onLogout} /> 38 62 39 63 <div id="app-content"> 40 - {content} 64 + {children} 41 65 </div> 42 66 43 67 <div className="footer"> ··· 72 96 <p className="secret-dev"> 73 97 secret dev setting: 74 98 {' '} 75 - <label> 76 - <input 77 - type="checkbox" 78 - onChange={e => setDev(e.target.checked)} 79 - checked={true /*isDev(ufosHost)*/} 80 - /> 81 - localhost 82 - </label> 99 + <GetJson 100 + key={secretDevCounter} 101 + endpoint="/global-notify" 102 + credentials 103 + loading={() => <>&hellip;</>} 104 + ok={({ notify_self }) => ( 105 + <label> 106 + <input 107 + type="checkbox" 108 + onChange={e => setSelfNotify(e.target.checked)} 109 + checked={notify_self ^ (secretDevStatus === 'pending')} 110 + disabled={secretDevStatus === 'pending'} 111 + /> 112 + self-notify 113 + </label> 114 + )} 115 + /> 83 116 </p> 84 117 </div> 85 118 </>
+2 -2
atproto-notifications/src/index.css
··· 39 39 } 40 40 41 41 @media (prefers-color-scheme: light) { 42 - :root { 42 + /* :root { 43 43 color: #213547; 44 44 background-color: #ffffff; 45 45 } ··· 48 48 } 49 49 button { 50 50 background-color: #f9f9f9; 51 - } 51 + }*/ 52 52 }
+39 -5
atproto-notifications/src/pages/Early.tsx
··· 1 1 import { useCallback, useState } from 'react'; 2 - import { Link } from 'react-router'; 3 - import { postJson } from '../components/Fetch'; 2 + import { Link, useSearchParams } from 'react-router'; 3 + import { GetJson, postJson } from '../components/Fetch'; 4 + import { ButtonGroup } from '../components/Buttons'; 4 5 import './Early.css'; 5 6 6 7 export function Early({ }) { 8 + const [searchParams, _] = useSearchParams(); 7 9 const [notified, setNotified] = useState(false); 8 10 const [pushStatus, setPushStatus] = useState(null); 9 11 const [pushed, setPushed] = useState(false); 12 + const [notifyToggleCounter, setNotifyToggleCounter] = useState(0); 13 + 14 + const returning = !searchParams.has('hello'); 10 15 11 16 const localTest = useCallback(() => { 12 17 try { ··· 32 37 setPushed(true); 33 38 }); 34 39 40 + // TODO move up (to chrome?) so it syncs 41 + const setGlobalNotifications = useCallback(async enabled => { 42 + const host = import.meta.env.VITE_NOTIFICATIONS_HOST; 43 + const url = new URL('/global-notify', host); 44 + try { 45 + await postJson(url, JSON.stringify({ notify_enabled: enabled }), true) 46 + } catch (err) { 47 + console.error('failed to set self-notify setting', err); 48 + } 49 + setNotifyToggleCounter(n => n + 1); 50 + }); 51 + 35 52 return ( 36 53 <div className="early"> 37 54 <h2>Hello!</h2> ··· 39 56 <p>A few things to keep in mind:</p> 40 57 <ol> 41 58 <li>This is a <a href="https://spacedust.microcosm.blue" target="_blank">spacedust</a> demo, not a polished product</li> 42 - <li>It's not a long-term committed part of microcosm <em>(yet)</em></li> 43 59 <li>Mobile browsers are unreliable at delivering Web Push notifications</li> 44 60 <li>Many features are easy to add! Some are surprisingly hard! Make a request and let's see :)</li> 45 61 </ol> ··· 49 65 <p> 50 66 To see a test notification, <button onClick={localTest}>click on this</button>. This is a local-only test. 51 67 </p> 52 - {notified && ( 68 + {(returning || notified) && ( 53 69 <> 54 70 <p> 55 71 Then ··· 66 82 {pushStatus === 'failed' && <p>uh oh, something went wrong requesting a web push</p>} 67 83 </> 68 84 )} 69 - {(pushed && pushStatus !== 'failed') && ( 85 + {(returning || (pushed && pushStatus !== 'failed')) && ( 70 86 <> 71 87 <h3>Great!</h3> 88 + <p>You're all set up to enable notifications:</p> 89 + 90 + <GetJson 91 + key={notifyToggleCounter} 92 + endpoint="/global-notify" 93 + credentials 94 + ok={({ notify_enabled }) => ( 95 + <ButtonGroup 96 + options={[ 97 + {val: 'paused', label: <>โธ&nbsp;&nbsp;pause{!notify_enabled && 'd'}</>}, 98 + {val: 'active', label: <>โ–ถ&nbsp;&nbsp;{notify_enabled ? 'notifications active' : 'enable notifications'}</>}, 99 + ]} 100 + current={notify_enabled ? 'active' : 'paused'} 101 + onChange={val => setGlobalNotifications(val === 'active')} 102 + /> 103 + )} 104 + /> 105 + 72 106 <p> 73 107 You can get back to this page by clicking the early 74 108 <span className="chrome-role-tag">early</span>
+44
atproto-notifications/src/pages/Feed.css
··· 26 26 text-align: left; 27 27 margin: 2rem auto; 28 28 } 29 + 30 + .filter-pref-wrapper { 31 + display: inline-block; 32 + } 33 + 34 + .filter-pref-trigger { 35 + display: inline-block; 36 + padding: 0 0.25rem; 37 + } 38 + .filter-pref-trigger:hover { 39 + background: hsla(0, 0%, 50%, 0.333); 40 + border-radius: 0.3333rem; 41 + } 42 + 43 + .popup-arrow { 44 + color: #2c343c; 45 + stroke-width: 1.5px; 46 + stroke: hsla(0, 0%, 50%, 0.333); 47 + stroke-dasharray: 30px; 48 + stroke-dashoffset: -54px; 49 + } 50 + 51 + .popup-overlay { 52 + background: hsla(0, 0%, 0%, 0.1); 53 + } 54 + 55 + .popup-content { 56 + background: #2c343c; 57 + padding: 0.25rem 0.333rem; 58 + font-size: 0.8rem; 59 + border: 0.5px solid hsla(0, 0%, 50%, 0.333); 60 + border-radius: 0.25rem; 61 + } 62 + .filter-pref-popup { 63 + text-align: center; 64 + } 65 + .filter-pref-popup h4 { 66 + margin: 0 0 0.25rem; 67 + font-size: 0.8rem; 68 + color: #bbb; 69 + } 70 + .filter-pref.option { 71 + display: block; 72 + }
+93 -5
atproto-notifications/src/pages/Feed.tsx
··· 1 - import { useEffect, useState } from 'react'; 1 + import { useCallback, useEffect, useState } from 'react'; 2 + import { ErrorBoundary } from 'react-error-boundary'; 3 + import Popup from 'reactjs-popup'; 2 4 import { getNotifications, getSecondary } from '../db'; 3 5 import { ButtonGroup } from '../components/Buttons'; 4 6 import { NotificationSettings } from '../components/NotificationSettings'; 5 - import { Notification } from '../components/Notification'; 7 + import { Notification, fallbackRender } from '../components/Notification'; 8 + import { GetJson, PostJson } from '../components/Fetch'; 6 9 import psl from 'psl'; 7 10 import lexicons from 'lexicons'; 8 11 9 12 import './feed.css'; 10 13 14 + function FilterPref({ secondary, value }) { 15 + const [wanted, setWanted] = useState(null); 16 + const [updateCount, setUpdateCount] = useState(0); 17 + const v = `${updateCount}:${wanted}`; 18 + 19 + const setFilterBool = useCallback(val => { 20 + setUpdateCount(n => n + 1); 21 + setWanted(val === 'notify'); 22 + }); 23 + const resetFilter = useCallback(() => { 24 + setUpdateCount(n => n + 1); 25 + setWanted(null); 26 + }); 27 + 28 + const trigger = useCallback(notify => { 29 + let icon = 'โš™', title = 'Default (inherit)'; 30 + if (notify === true) { 31 + icon = '๐Ÿ”Š'; 32 + title = 'Always notify'; 33 + } else if (notify === false) { 34 + icon = '๐Ÿšซ'; 35 + title = 'Notifications muted'; 36 + } 37 + return ( 38 + <div className="filter-pref-trigger" title={title}> 39 + {icon} 40 + </div> 41 + ); 42 + }); 43 + 44 + const renderFilter = useCallback(({ notify }) => ( 45 + <Popup 46 + key="x" 47 + trigger={trigger(notify)} 48 + position={['bottom center']} 49 + closeOnDocumentClick 50 + > 51 + <div className="filter-pref-popup"> 52 + <h4>filter notifications</h4> 53 + <ButtonGroup 54 + options={[ 55 + { val: 'notify', label: 'notify' }, 56 + { val: 'mute' }, 57 + ]} 58 + current={notify === null ? null : notify ? 'notify' : 'mute'} 59 + onChange={setFilterBool} 60 + /> 61 + {notify !== null && ( 62 + <button className="subtle" onClick={resetFilter}>reset</button> 63 + )} 64 + </div> 65 + </Popup> 66 + )); 67 + 68 + const common = { 69 + endpoint: '/notification-filter', 70 + credentials: true, 71 + ok: renderFilter, 72 + loading: () => <>&hellip;</>, 73 + }; 74 + 75 + return updateCount === 0 76 + ? <GetJson key={v} 77 + params={{ selector: secondary, selection: value }} 78 + {...common} 79 + /> 80 + : <PostJson key={v} 81 + data={{ selector: secondary, selection: value, notify: wanted }} 82 + {...common} 83 + />; 84 + } 85 + 11 86 function SecondaryFilter({ inc, secondary, current, onUpdate }) { 12 87 const [secondaries, setSecondaries] = useState([]); 13 88 ··· 48 123 const lex = lexicons[appPrefix]; 49 124 icon = lex?.clients[0]?.icon; 50 125 appName = lex?.name; 51 - title = lex?.known_sources[k.slice(app.length + 1)] ?? k; 126 + title = lex?.known_sources[k.slice(app.length + 1)]?.name ?? k; 52 127 53 128 } else if (secondary === 'group') { 54 129 ··· 80 155 {icon && ( 81 156 <img className="app-icon" src={icon} title={appName ?? app} alt="" /> 82 157 )} 83 - {title} ({total}) 158 + {title} 159 + <small style={{ 160 + display: 'inline-block', 161 + fontSize: '0.6rem', 162 + padding: '0 0.2rem', 163 + color: '#f90', 164 + fontFamily: 'monospace', 165 + verticalAlign: 'top', 166 + }}> 167 + {total >= 30 ? '30+' : total} 168 + </small> 169 + <FilterPref secondary={secondary} value={k} /> 84 170 </> 85 171 ), 86 172 }; ··· 147 233 148 234 <div className="feed-notifications"> 149 235 {feed.map(([k, n]) => ( 150 - <Notification key={k} {...n} /> 236 + <ErrorBoundary key={k} fallbackRender={fallbackRender}> 237 + <Notification {...n} /> 238 + </ErrorBoundary> 151 239 ))} 152 240 </div> 153 241 </div>
+8 -4
atproto-notifications/src/service-worker.ts
··· 37 37 // TODO: user pref for alt client -> prefer that client's icon 38 38 const lex = lexicons[appPrefix]; 39 39 const icon = lex?.clients[0]?.icon; 40 - const title = lex?.known_sources[source.slice(app.length + 1)] ?? source; 40 + const title = lex?.known_sources[source.slice(app.length + 1)]?.name ?? source; 41 41 const body = `from @${handle} on ${lex?.name ?? app}`; 42 42 43 43 // const tag = 'simple-push-demo-notification-tag'; ··· 72 72 includeUncontrolled: true, 73 73 }); 74 74 75 - // focus the first available existing window/tab 76 - for (const client of clientList) 77 - return await client.focus(); 75 + // focus the first available existing window/tab open at / 76 + for (const client of clientList) { 77 + const pathname = new URL(client.url).pathname; 78 + if (pathname === '/') { 79 + return await client.focus(); 80 + } 81 + } 78 82 79 83 // otherwise open a new tab 80 84 await clients.openWindow('/');
+67
lexicons/atproto.js
··· 1 + import { Client, CredentialManager, ok, simpleFetchHandler } from '@atcute/client'; 2 + import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'; 3 + 4 + // cleanup needed 5 + 6 + const docResolver = new CompositeDidDocumentResolver({ 7 + methods: { 8 + plc: new PlcDidDocumentResolver(), 9 + web: new WebDidDocumentResolver(), 10 + }, 11 + }); 12 + 13 + async function resolve_did(did) { 14 + return await docResolver.resolve(did); 15 + } 16 + 17 + function pds({ service }) { 18 + if (!service) { 19 + throw new Error('missing service from identity doc'); 20 + } 21 + const { serviceEndpoint } = service[0]; 22 + if (!serviceEndpoint) { 23 + throw new Error('missing serviceEndpoint from identity service array'); 24 + } 25 + return serviceEndpoint; 26 + } 27 + 28 + 29 + async function get_pds_record(endpoint, did, collection, rkey) { 30 + const handler = simpleFetchHandler({ service: endpoint }); 31 + const rpc = new Client({ handler }); 32 + const { ok, data } = await rpc.get('com.atproto.repo.getRecord', { 33 + params: { repo: did, collection, rkey }, 34 + }); 35 + if (!ok) throw new Error('fetching pds record failed'); 36 + return data; 37 + } 38 + 39 + function parse_at_uri(uri) { 40 + let collection, rkey; 41 + if (!uri.startsWith('at://')) { 42 + throw new Error('invalid at-uri: did not start with "at://"'); 43 + } 44 + let remaining = uri.slice('at://'.length); // remove the at:// prefix 45 + remaining = remaining.split('#')[0]; // hash is valid in at-uri but we don't handle them 46 + remaining = remaining.split('?')[0]; // query is valid in at-uri but we don't handle it 47 + const segments = remaining.split('/'); 48 + if (segments.length === 0) { 49 + throw new Error('invalid at-uri: could not find did after "at://"'); 50 + } 51 + const did = segments[0]; 52 + if (segments.length > 1) { 53 + collection = segments[1]; 54 + } 55 + if (segments.length > 2) { 56 + rkey = segments.slice(2).join('/'); // hmm are slashes actually valid in rkey? 57 + } 58 + return { did, collection, rkey }; 59 + } 60 + 61 + export async function getAtUri(atUri) { 62 + const { did, collection, rkey } = parse_at_uri(atUri); 63 + const doc = await resolve_did(did); 64 + const endpoint = pds(doc); 65 + const { value } = await get_pds_record(endpoint, did, collection, rkey); 66 + return value; 67 + }
+106
lexicons/bits.js
··· 1 + import psl from 'psl'; 2 + import { JSONPath } from 'jsonpath-plus'; 3 + import defs from './defs.js'; 4 + import { getAtUri } from './atproto.js'; 5 + 6 + export function getBits(source) { 7 + const [nsid, ...rp] = source.split(':'); 8 + const segments = nsid.split('.'); 9 + const group = segments.slice(0, segments.length - 1).join('.') ?? null; 10 + segments.reverse(); 11 + const app = psl.parse(segments.join('.'))?.domain ?? null; 12 + return { app, group }; 13 + } 14 + 15 + function getAppDefs(source) { 16 + const { app } = getBits(source); 17 + const appPrefix = source.slice(0, app.length); 18 + const appSource = source.slice(app.length + 1); 19 + return [appSource, defs[appPrefix]]; 20 + } 21 + 22 + const uriBits = async uri => { 23 + const bits = uri.slice('at://'.length).split('/'); 24 + // TODO: identifier might be a handle 25 + // TODO: rest might contain stuff after the rkey 26 + const [did, nsid, rkey] = [bits[0], bits[1], bits.slice(2)]; 27 + return [did, nsid, rkey.join('/') || null]; 28 + }; 29 + 30 + export async function getLink(source, source_record, subject) { 31 + // TODO: pass in preferred client 32 + const [appSource, appDefs] = getAppDefs(source); 33 + const appLinks = appDefs?.clients?.[0]?.direct_links; 34 + const linkType = subject.startsWith('did:') ? 'did' : 'at_uri'; 35 + const linkTemplate = appLinks?.[`${linkType}:${appSource}`]; 36 + if (!linkTemplate) return null; 37 + 38 + let link = linkTemplate; 39 + 40 + // 1. sync subs 41 + const [sourceDid, sourceNsid, sourceRkey] = await uriBits(source_record); 42 + link = link 43 + .replaceAll('{source_record.did}', sourceDid) 44 + .replaceAll('{source_record.collection}', sourceNsid) 45 + .replaceAll('{source_record.rkey}', sourceRkey); 46 + if (linkTemplate === 'did') { 47 + link = link.replaceAll('{subject.did}', subject); 48 + } else { 49 + const [subjectDid, subjectNsid, subjectRkey] = await uriBits(subject); 50 + link = link 51 + .replaceAll('{subject.did}', subjectDid) 52 + .replaceAll('{subject.collection}', subjectNsid) 53 + .replaceAll('{subject.rkey}', subjectRkey); 54 + } 55 + 56 + // 2. async lookups 57 + 58 + // do we need to fetch anything from the link subject record? 59 + if (linkType === 'at_uri') { 60 + const subjectMatches = [...link.matchAll(/(\{@subject:(?<path>[^\}]+)\})/g)]; 61 + if (subjectMatches.length > 0) { 62 + const subjectRecord = await getAtUri(subject); 63 + 64 + // do the actual replacements 65 + for (const match of subjectMatches) { 66 + // TODO: JSONPath won't actually cut it once we get $type in 67 + const sub = JSONPath({ 68 + path: `$.${match.groups.path}`, 69 + json: subjectRecord, 70 + })[0]; // TODO: array result? 71 + 72 + link = link.replaceAll(match[0], sub); 73 + } 74 + } 75 + } 76 + 77 + // 2.b TODO: source record lookups if needed 78 + return link; 79 + } 80 + 81 + export async function getContext(source, source_record, subject) { 82 + const [appSource, appDefs] = getAppDefs(source); 83 + const contexts = appDefs?.known_sources?.[appSource]?.context ?? []; 84 + const linkType = subject.startsWith('did:') ? 'did' : 'at_uri'; 85 + 86 + let loaded = []; 87 + for (const ctx of contexts) { 88 + const [o, ...pathstuff] = ctx.split(':'); 89 + if (o !== '@subject') { 90 + throw new Error('only @subject is implemented for context loading so far'); 91 + } 92 + if (linkType !== 'at_uri') { 93 + throw new Error('only at_uris can be used for @subject loading so far'); 94 + } 95 + const path = pathstuff.join(':'); 96 + const subjectRecord = await getAtUri(subject); 97 + // using json path is temporary -- need recordpath convention defined 98 + const found = JSONPath({ 99 + path, 100 + json: subjectRecord, 101 + }); 102 + loaded = loaded.concat(found); // TODO: think about array handling 103 + } 104 + 105 + return loaded; 106 + }
+271
lexicons/defs.js
··· 1 + export default { 2 + 'blue.microcosm': { 3 + name: 'microcosm', 4 + clients: [ 5 + { 6 + app_name: 'Spacedust notifications demo', 7 + canonical: true, 8 + main: 'https://notifications.microcosm.blue', 9 + icon: '/icons/microcosm.png', 10 + }, 11 + ], 12 + known_sources: { 13 + 'test.notification:hello': 'Hello spacedust!', 14 + }, 15 + }, 16 + 'app.bsky': { 17 + name: 'Bluesky', 18 + profile: { 19 + display_name: 'app.bsky.actor.profile:displayName', 20 + avatar: 'app.bsky.actor.profile:avatar', 21 + }, 22 + clients: [ 23 + { 24 + app_name: 'Bluesky Social', 25 + canonical: true, 26 + main: 'https://bsky.app', 27 + icon: '/icons/app.bsky.png', 28 + notifications: 'https://bsky.app/notifications', 29 + direct_links: { 30 + 'at_uri:feed.like:subject.uri': 'https://bsky.app/profile/{subject.did}/post/{subject.rkey}', 31 + 'at_uri:feed.post:reply.parent.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 32 + 'at_uri:feed.post:reply.root.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 33 + 'at_uri:feed.post:embed.record.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 34 + 'at_uri:feed.post:embed.record.record.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 35 + 'did:graph.follow:subject': 'https://bsky.app/profile/{source_record.did}', 36 + 'did:feed.post:facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 37 + }, 38 + }, 39 + { 40 + app_name: 'Deer Social', 41 + main: 'https://deer.social', 42 + notifications: 'https://deer.social/notifications', 43 + direct_links: { 44 + 'at_uri:feed.post': 'https://deer.social/profile/{did}/post/{rkey}', 45 + 'did': 'https://deer.social/profile/{did}', 46 + }, 47 + }, 48 + ], 49 + known_sources: { 50 + 'graph.follow:subject': { 51 + name: 'Follow', 52 + }, 53 + 'graph.verification:subject': { 54 + name: 'Verification', 55 + }, 56 + 'feed.like:subject.uri': { 57 + name: 'Like', 58 + context: ['@subject:text'], 59 + }, 60 + 'feed.like:via.uri': { 61 + name: 'Repost like', 62 + }, 63 + 'feed.post:reply.parent.uri': { 64 + name: 'Reply', 65 + }, 66 + 'feed.post:reply.root.uri': { 67 + name: 'Reply in thread', 68 + }, 69 + 'feed.post:embed.record.uri': { 70 + name: 'Quote', 71 + }, 72 + 'feed.post:embed.record.record.uri': { 73 + name: 'Quote', // with media 74 + }, 75 + 'feed.post:facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did': { 76 + name: 'Mention', 77 + }, 78 + 'feed.repost:subject.uri': { 79 + name: 'Repost', 80 + }, 81 + 'feed.repost:via.uri': { 82 + name: 'Repost repost', 83 + }, 84 + }, 85 + torment_sources: { 86 + 'graph.block:subject': null, 87 + 'graph.listitem:subject': null, // we are never ever building listifications 88 + 'graph.listblock:subject': null, // "subscribed to your blocklist?" idk 89 + 'feed.threadgate:hiddenReplies[]': null, 90 + 'feed.postgate:detachedEmbeddingUris[]': null, 91 + }, 92 + }, 93 + 'pub.leaflet': { 94 + name: 'Leaflet', 95 + clients: [ 96 + { 97 + app_name: 'leaflet.pub', 98 + canonical: true, 99 + icon: '/icons/pub.leaflet.jpg', 100 + main: 'https://leaflet.pub/home', 101 + direct_links: { 102 + 'at_uri:graph.subscription:publication': 'https://leaflet.pub/lish/{did}/{rkey}/dashboard', 103 + }, 104 + } 105 + ], 106 + known_sources: { 107 + 'graph.subscription:publication': { 108 + name: 'Subscription', 109 + }, 110 + }, 111 + }, 112 + 'sh.tangled': { 113 + name: 'Tangled', 114 + clients: [ 115 + { 116 + app_name: 'Tangled', 117 + canonical: true, 118 + icon: '/icons/sh.tangled.jpg', 119 + main: 'https://tangled.sh', 120 + direct_links: { 121 + 'at_uri:feed.star:subject': 'https://tangled.sh/{subject.did}/{@subject:name}', 122 + 'did:graph.follow:subject': 'https://tangled.sh/{source_record.did}', 123 + }, 124 + } 125 + ], 126 + known_sources: { 127 + 'feed.star:subject': { 128 + name: 'Star', 129 + context: ['@subject:name'], 130 + }, 131 + 'feed.reaction:subject': { 132 + name: 'Reaction', 133 + }, 134 + 'graph.follow:subject': { 135 + name: 'Follow', 136 + }, 137 + 'actor.profile:pinnedRepositories[]': { 138 + name: 'Pinned repo', 139 + }, 140 + 'repo.issue.comment:issue': { 141 + name: 'Issue comment', 142 + }, 143 + 'repo.issue.comment:owner': { 144 + name: 'Issue comment', 145 + }, 146 + 'repo.issue.comment:repo': { 147 + name: 'Issue comment', 148 + }, 149 + 'repo.pull:targetRepo': { 150 + name: 'Pull', 151 + }, 152 + 'repo.pull.comment:owner': { 153 + name: 'Pull comment', 154 + }, 155 + 'repo.pull.comment:pull': { 156 + name: 'Pull comment', 157 + }, 158 + 'repo.pull.comment:repo': { 159 + name: 'Pull comment', 160 + }, 161 + 'knot.member:subject': { 162 + name: 'Knot member', 163 + }, 164 + 'spindle.member:subject': { 165 + name: 'Spindle member', 166 + }, 167 + }, 168 + }, 169 + 'com.shinolabs': { // TODO: this app isn't exactly tld+1 170 + name: 'Pinksea', 171 + clients: [ 172 + { 173 + app_name: 'Pinksea', 174 + canonical: true, 175 + icon: '/icons/com.shinolabs.jpg', 176 + main: 'https://pinksea.art', 177 + }, 178 + ], 179 + known_sources: { 180 + 'pinksea.oekaki:inResponseTo.uri': { 181 + name: 'Response', 182 + }, 183 + }, 184 + }, 185 + 'place.stream': { 186 + name: 'Streamplace', 187 + clients: [ 188 + { 189 + app_name: 'Streamplace', 190 + canonical: true, 191 + icon: '/icons/place.stream.png', 192 + main: 'https://stream.place', 193 + }, 194 + ], 195 + known_sources: { 196 + 'chat.message:streamer': { 197 + name: 'Message', 198 + }, 199 + 'key:signingKey': { 200 + name: 'Signing key', 201 + }, 202 + }, 203 + }, 204 + 'so.sprk': { 205 + name: 'Spark', 206 + clients: [ 207 + { 208 + app_name: 'Spark', 209 + canonical: true, 210 + icon: '/icons/so.sprk.png', 211 + main: 'https://spark.so', 212 + }, 213 + ], 214 + known_sources: { 215 + 'feed.like:subject.uri': { 216 + name: 'Like', 217 + }, 218 + // it's not actually clear to me if *all* the bsky sources were copied for sprk posts or not 219 + 'feed.post:reply.parent.uri': { 220 + name: 'Reply', 221 + }, 222 + 'feed.post:reply.root.uri': { 223 + name: 'Reply in thread', 224 + }, 225 + 'feed.post:embed.record.uri': { 226 + name: 'Quote', 227 + }, 228 + 'feed.post:embed.record.record.uri': { 229 + name: 'Quote', // with media 230 + }, 231 + 'feed.post:facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did': { 232 + name: 'Mention', 233 + }, 234 + }, 235 + }, 236 + 'events.smokesignal': { 237 + name: 'Smoke Signal', 238 + clients: [ 239 + { 240 + app_name: 'Smoke Signal', 241 + canonical: true, 242 + icon: '/icons/events.smokesignal.png', 243 + main: 'https://smokesignal.events', 244 + }, 245 + ], 246 + known_sources: { 247 + 'calendar.rsvp:subject.uri': { 248 + name: 'RSVP', 249 + }, 250 + }, 251 + }, 252 + 'app.popsky': { 253 + name: 'Popsky', 254 + clients: [ 255 + { 256 + app_name: 'Popsky', 257 + canonical: true, 258 + icon: '/icons/app.popsky.png', 259 + main: 'https://popsky.social', 260 + }, 261 + ], 262 + known_sources: { 263 + 'like:subjectUri': { 264 + name: 'Like', 265 + }, 266 + 'comment:subjectUri': { 267 + name: 'Comment', 268 + }, 269 + }, 270 + }, 271 + };
+2 -186
lexicons/index.js
··· 1 - export default { 2 - 'blue.microcosm': { 3 - name: 'microcosm', 4 - clients: [ 5 - {}, 6 - ], 7 - known_sources: { 8 - 'test.notification:hello': 'Hello spacedust!', 9 - }, 10 - }, 11 - 'app.bsky': { 12 - name: 'Bluesky', 13 - profile: { 14 - display_name: 'app.bsky.actor.profile:displayName', 15 - avatar: 'app.bsky.actor.profile:avatar', 16 - }, 17 - clients: [ 18 - { 19 - app_name: 'Bluesky Social', 20 - canonical: true, 21 - main: 'https://bsky.app', 22 - icon: '/icons/app.bsky.png', 23 - notifications: 'https://bsky.app/notifications', 24 - direct_links: { 25 - 'at_uri:feed.like:subject.uri': 'https://bsky.app/profile/{subject.did}/post/{subject.rkey}', 26 - 'at_uri:feed.post:reply.parent.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 27 - 'at_uri:feed.post:reply.root.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 28 - 'at_uri:feed.post:embed.record.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 29 - 'at_uri:feed.post:embed.record.record.uri': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 30 - 'did:graph.follow:subject': 'https://bsky.app/profile/{source_record.did}', 31 - 'did:feed.post:facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did': 'https://bsky.app/profile/{source_record.did}/post/{source_record.rkey}', 32 - }, 33 - }, 34 - { 35 - app_name: 'Deer Social', 36 - main: 'https://deer.social', 37 - notifications: 'https://deer.social/notifications', 38 - direct_links: { 39 - 'at_uri:feed.post': 'https://deer.social/profile/{did}/post/{rkey}', 40 - 'did': 'https://deer.social/profile/{did}', 41 - }, 42 - }, 43 - ], 44 - known_sources: { 45 - 'graph.follow:subject': 'Follow', 46 - 'graph.verification:subject': 'Verification', 47 - 'feed.like:subject.uri': 'Like', 48 - 'feed.like:via.uri': 'Repost like', 49 - 'feed.post:reply.parent.uri': 'Reply', 50 - 'feed.post:reply.root.uri': 'Reply in thread', 51 - 'feed.post:embed.record.uri': 'Quote', 52 - 'feed.post:embed.record.record.uri': 'Quote', // with media 53 - 'feed.post:facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did': 'Mention', 54 - 'feed.repost:subject.uri': 'Repost', 55 - 'feed.repost:via.uri': 'Repost repost', 56 - }, 57 - torment_sources: { 58 - 'graph.block:subject': null, 59 - 'graph.listitem:subject': null, // we are never ever building listifications 60 - 'graph.listblock:subject': null, // "subscribed to your blocklist?" idk 61 - 'feed.threadgate:hiddenReplies[]': null, 62 - 'feed.postgate:detachedEmbeddingUris[]': null, 63 - }, 64 - }, 65 - 'pub.leaflet': { 66 - name: 'Leaflet', 67 - clients: [ 68 - { 69 - app_name: 'leaflet.pub', 70 - canonical: true, 71 - icon: '/icons/pub.leaflet.jpg', 72 - main: 'https://leaflet.pub/home', 73 - direct_links: { 74 - 'at_uri:graph.subscription:publication': 'https://leaflet.pub/lish/{did}/{rkey}/dashboard', 75 - }, 76 - } 77 - ], 78 - known_sources: { 79 - 'graph.subscription:publication': 'Subscription', 80 - }, 81 - }, 82 - 'sh.tangled': { 83 - name: 'Tangled', 84 - clients: [ 85 - { 86 - app_name: 'Tangled', 87 - canonical: true, 88 - icon: '/icons/sh.tangled.jpg', 89 - main: 'https://tangled.sh', 90 - } 91 - ], 92 - known_sources: { 93 - 'feed.star:subject': 'Star', 94 - 'feed.reaction:subject': 'Reaction', 95 - 'graph.follow:subject': 'Follow', 96 - 'actor.profile:pinnedRepositories[]': 'Pinned repo', 97 - 'repo.issue.comment:issue': 'Issue comment', 98 - 'repo.issue.comment:owner': 'Issue comment', 99 - 'repo.issue.comment:repo': 'Issue comment', 100 - 'repo.pull:targetRepo': 'Pull', 101 - 'repo.pull.comment:owner': 'Pull comment', 102 - 'repo.pull.comment:pull': 'Pull comment', 103 - 'repo.pull.comment:repo': 'Pull comment', 104 - 'knot.member:subject': 'Knot member', 105 - 'spindle.member:subject': 'Spindle member', 106 - }, 107 - }, 108 - 'com.shinolabs': { // TODO: this app isn't exactly tld+1 109 - name: 'Pinksea', 110 - clients: [ 111 - { 112 - app_name: 'Pinksea', 113 - canonical: true, 114 - icon: '/icons/com.shinolabs.jpg', 115 - main: 'https://pinksea.art', 116 - }, 117 - ], 118 - known_sources: { 119 - 'pinksea.oekaki:inResponseTo.uri': 'Response', 120 - }, 121 - }, 122 - 'place.stream': { 123 - name: 'Streamplace', 124 - clients: [ 125 - { 126 - app_name: 'Streamplace', 127 - canonical: true, 128 - icon: '/icons/place.stream.png', 129 - main: 'https://stream.place', 130 - }, 131 - ], 132 - known_sources: { 133 - 'chat.message:streamer': 'Message', 134 - 'key:signingKey': 'Signing key', 135 - }, 136 - }, 137 - 'so.sprk': { 138 - name: 'Spark', 139 - clients: [ 140 - { 141 - app_name: 'Spark', 142 - canonical: true, 143 - icon: '/icons/so.sprk.png', 144 - main: 'https://spark.so', 145 - }, 146 - ], 147 - known_sources: { 148 - 'feed.like:subject.uri': 'Like', 149 - // it's not actually clear to me if *all* the bsky sources were copied for sprk posts or not 150 - 'feed.post:reply.parent.uri': 'Reply', 151 - 'feed.post:reply.root.uri': 'Reply in thread', 152 - 'feed.post:embed.record.uri': 'Quote', 153 - 'feed.post:embed.record.record.uri': 'Quote', // with media 154 - 'feed.post:facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did': 'Mention', 155 - }, 156 - }, 157 - 'events.smokesignal': { 158 - name: 'Smoke Signal', 159 - clients: [ 160 - { 161 - app_name: 'Smoke Signal', 162 - canonical: true, 163 - icon: '/icons/events.smokesignal.png', 164 - main: 'https://smokesignal.events', 165 - }, 166 - ], 167 - known_sources: { 168 - 'calendar.rsvp:subject.uri': 'RSVP', 169 - }, 170 - }, 171 - 'app.popsky': { 172 - name: 'Popsky', 173 - clients: [ 174 - { 175 - app_name: 'Popsky', 176 - canonical: true, 177 - icon: '/icons/app.popsky.png', 178 - main: 'https://popsky.social', 179 - }, 180 - ], 181 - known_sources: { 182 - 'like:subjectUri': 'Like', 183 - 'comment:subjectUri': 'Comment', 184 - }, 185 - }, 186 - }; 1 + export { getBits, getLink, getContext } from './bits.js'; 2 + export { default } from './defs.js';
+157
lexicons/package-lock.json
··· 1 + { 2 + "name": "lexicons", 3 + "version": "0.0.1", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "lexicons", 9 + "version": "0.0.1", 10 + "dependencies": { 11 + "@atcute/client": "^4.0.3", 12 + "@atcute/identity-resolver": "^1.1.3", 13 + "jsonpath-plus": "^10.3.0", 14 + "psl": "^1.15.0" 15 + } 16 + }, 17 + "node_modules/@atcute/client": { 18 + "version": "4.0.3", 19 + "resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.3.tgz", 20 + "integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==", 21 + "license": "MIT", 22 + "dependencies": { 23 + "@atcute/identity": "^1.0.2", 24 + "@atcute/lexicons": "^1.0.3" 25 + } 26 + }, 27 + "node_modules/@atcute/identity": { 28 + "version": "1.0.3", 29 + "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.0.3.tgz", 30 + "integrity": "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==", 31 + "license": "0BSD", 32 + "dependencies": { 33 + "@atcute/lexicons": "^1.0.4", 34 + "@badrap/valita": "^0.4.5" 35 + } 36 + }, 37 + "node_modules/@atcute/identity-resolver": { 38 + "version": "1.1.3", 39 + "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.3.tgz", 40 + "integrity": "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==", 41 + "license": "MIT", 42 + "dependencies": { 43 + "@atcute/lexicons": "^1.0.4", 44 + "@atcute/util-fetch": "^1.0.1", 45 + "@badrap/valita": "^0.4.4" 46 + }, 47 + "peerDependencies": { 48 + "@atcute/identity": "^1.0.0" 49 + } 50 + }, 51 + "node_modules/@atcute/lexicons": { 52 + "version": "1.1.0", 53 + "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.1.0.tgz", 54 + "integrity": "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==", 55 + "license": "0BSD", 56 + "dependencies": { 57 + "esm-env": "^1.2.2" 58 + } 59 + }, 60 + "node_modules/@atcute/util-fetch": { 61 + "version": "1.0.1", 62 + "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.1.tgz", 63 + "integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==", 64 + "license": "MIT", 65 + "dependencies": { 66 + "@badrap/valita": "^0.4.2" 67 + } 68 + }, 69 + "node_modules/@badrap/valita": { 70 + "version": "0.4.5", 71 + "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.5.tgz", 72 + "integrity": "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ==", 73 + "license": "MIT", 74 + "engines": { 75 + "node": ">= 18" 76 + } 77 + }, 78 + "node_modules/@jsep-plugin/assignment": { 79 + "version": "1.3.0", 80 + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", 81 + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", 82 + "license": "MIT", 83 + "engines": { 84 + "node": ">= 10.16.0" 85 + }, 86 + "peerDependencies": { 87 + "jsep": "^0.4.0||^1.0.0" 88 + } 89 + }, 90 + "node_modules/@jsep-plugin/regex": { 91 + "version": "1.0.4", 92 + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", 93 + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", 94 + "license": "MIT", 95 + "engines": { 96 + "node": ">= 10.16.0" 97 + }, 98 + "peerDependencies": { 99 + "jsep": "^0.4.0||^1.0.0" 100 + } 101 + }, 102 + "node_modules/esm-env": { 103 + "version": "1.2.2", 104 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 105 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 106 + "license": "MIT" 107 + }, 108 + "node_modules/jsep": { 109 + "version": "1.4.0", 110 + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", 111 + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", 112 + "license": "MIT", 113 + "engines": { 114 + "node": ">= 10.16.0" 115 + } 116 + }, 117 + "node_modules/jsonpath-plus": { 118 + "version": "10.3.0", 119 + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", 120 + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", 121 + "license": "MIT", 122 + "dependencies": { 123 + "@jsep-plugin/assignment": "^1.3.0", 124 + "@jsep-plugin/regex": "^1.0.4", 125 + "jsep": "^1.4.0" 126 + }, 127 + "bin": { 128 + "jsonpath": "bin/jsonpath-cli.js", 129 + "jsonpath-plus": "bin/jsonpath-cli.js" 130 + }, 131 + "engines": { 132 + "node": ">=18.0.0" 133 + } 134 + }, 135 + "node_modules/psl": { 136 + "version": "1.15.0", 137 + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", 138 + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", 139 + "license": "MIT", 140 + "dependencies": { 141 + "punycode": "^2.3.1" 142 + }, 143 + "funding": { 144 + "url": "https://github.com/sponsors/lupomontero" 145 + } 146 + }, 147 + "node_modules/punycode": { 148 + "version": "2.3.1", 149 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 150 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 151 + "license": "MIT", 152 + "engines": { 153 + "node": ">=6" 154 + } 155 + } 156 + } 157 + }
+7 -1
lexicons/package.json
··· 5 5 "main": "index.js", 6 6 "scripts": {}, 7 7 "author": "", 8 - "type": "module" 8 + "type": "module", 9 + "dependencies": { 10 + "@atcute/client": "^4.0.3", 11 + "@atcute/identity-resolver": "^1.1.3", 12 + "jsonpath-plus": "^10.3.0", 13 + "psl": "^1.15.0" 14 + } 9 15 }
+24
live-embed/.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + *.local 14 + 15 + # Editor directories and files 16 + .vscode/* 17 + !.vscode/extensions.json 18 + .idea 19 + .DS_Store 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw?
+69
live-embed/README.md
··· 1 + # React + TypeScript + Vite 2 + 3 + This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 + 5 + Currently, two official plugins are available: 6 + 7 + - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh 8 + - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 + 10 + ## Expanding the ESLint configuration 11 + 12 + If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 13 + 14 + ```js 15 + export default tseslint.config([ 16 + globalIgnores(['dist']), 17 + { 18 + files: ['**/*.{ts,tsx}'], 19 + extends: [ 20 + // Other configs... 21 + 22 + // Remove tseslint.configs.recommended and replace with this 23 + ...tseslint.configs.recommendedTypeChecked, 24 + // Alternatively, use this for stricter rules 25 + ...tseslint.configs.strictTypeChecked, 26 + // Optionally, add this for stylistic rules 27 + ...tseslint.configs.stylisticTypeChecked, 28 + 29 + // Other configs... 30 + ], 31 + languageOptions: { 32 + parserOptions: { 33 + project: ['./tsconfig.node.json', './tsconfig.app.json'], 34 + tsconfigRootDir: import.meta.dirname, 35 + }, 36 + // other options... 37 + }, 38 + }, 39 + ]) 40 + ``` 41 + 42 + You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 43 + 44 + ```js 45 + // eslint.config.js 46 + import reactX from 'eslint-plugin-react-x' 47 + import reactDom from 'eslint-plugin-react-dom' 48 + 49 + export default tseslint.config([ 50 + globalIgnores(['dist']), 51 + { 52 + files: ['**/*.{ts,tsx}'], 53 + extends: [ 54 + // Other configs... 55 + // Enable lint rules for React 56 + reactX.configs['recommended-typescript'], 57 + // Enable lint rules for React DOM 58 + reactDom.configs.recommended, 59 + ], 60 + languageOptions: { 61 + parserOptions: { 62 + project: ['./tsconfig.node.json', './tsconfig.app.json'], 63 + tsconfigRootDir: import.meta.dirname, 64 + }, 65 + // other options... 66 + }, 67 + }, 68 + ]) 69 + ```
+23
live-embed/eslint.config.js
··· 1 + import js from '@eslint/js' 2 + import globals from 'globals' 3 + import reactHooks from 'eslint-plugin-react-hooks' 4 + import reactRefresh from 'eslint-plugin-react-refresh' 5 + import tseslint from 'typescript-eslint' 6 + import { globalIgnores } from 'eslint/config' 7 + 8 + export default tseslint.config([ 9 + globalIgnores(['dist']), 10 + { 11 + files: ['**/*.{ts,tsx}'], 12 + extends: [ 13 + js.configs.recommended, 14 + tseslint.configs.recommended, 15 + reactHooks.configs['recommended-latest'], 16 + reactRefresh.configs.vite, 17 + ], 18 + languageOptions: { 19 + ecmaVersion: 2020, 20 + globals: globals.browser, 21 + }, 22 + }, 23 + ])
+12
live-embed/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>zero bluesky live-updating post rendering</title> 7 + </head> 8 + <body> 9 + <div id="root"></div> 10 + <script type="module" src="/src/main.tsx"></script> 11 + </body> 12 + </html>
+3444
live-embed/package-lock.json
··· 1 + { 2 + "name": "live-embed", 3 + "version": "0.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "live-embed", 9 + "version": "0.0.0", 10 + "dependencies": { 11 + "@atcute/client": "^4.0.3", 12 + "@atcute/identity-resolver": "^1.1.3", 13 + "react": "^19.1.0", 14 + "react-dom": "^19.1.0" 15 + }, 16 + "devDependencies": { 17 + "@eslint/js": "^9.30.1", 18 + "@types/react": "^19.1.8", 19 + "@types/react-dom": "^19.1.6", 20 + "@vitejs/plugin-react": "^4.6.0", 21 + "eslint": "^9.30.1", 22 + "eslint-plugin-react-hooks": "^5.2.0", 23 + "eslint-plugin-react-refresh": "^0.4.20", 24 + "globals": "^16.3.0", 25 + "typescript": "~5.8.3", 26 + "typescript-eslint": "^8.35.1", 27 + "vite": "^7.0.4" 28 + } 29 + }, 30 + "node_modules/@ampproject/remapping": { 31 + "version": "2.3.0", 32 + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 33 + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 34 + "dev": true, 35 + "license": "Apache-2.0", 36 + "dependencies": { 37 + "@jridgewell/gen-mapping": "^0.3.5", 38 + "@jridgewell/trace-mapping": "^0.3.24" 39 + }, 40 + "engines": { 41 + "node": ">=6.0.0" 42 + } 43 + }, 44 + "node_modules/@atcute/client": { 45 + "version": "4.0.3", 46 + "resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.3.tgz", 47 + "integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==", 48 + "license": "MIT", 49 + "dependencies": { 50 + "@atcute/identity": "^1.0.2", 51 + "@atcute/lexicons": "^1.0.3" 52 + } 53 + }, 54 + "node_modules/@atcute/identity": { 55 + "version": "1.0.3", 56 + "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.0.3.tgz", 57 + "integrity": "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==", 58 + "license": "0BSD", 59 + "dependencies": { 60 + "@atcute/lexicons": "^1.0.4", 61 + "@badrap/valita": "^0.4.5" 62 + } 63 + }, 64 + "node_modules/@atcute/identity-resolver": { 65 + "version": "1.1.3", 66 + "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.3.tgz", 67 + "integrity": "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==", 68 + "license": "MIT", 69 + "dependencies": { 70 + "@atcute/lexicons": "^1.0.4", 71 + "@atcute/util-fetch": "^1.0.1", 72 + "@badrap/valita": "^0.4.4" 73 + }, 74 + "peerDependencies": { 75 + "@atcute/identity": "^1.0.0" 76 + } 77 + }, 78 + "node_modules/@atcute/lexicons": { 79 + "version": "1.1.0", 80 + "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.1.0.tgz", 81 + "integrity": "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==", 82 + "license": "0BSD", 83 + "dependencies": { 84 + "esm-env": "^1.2.2" 85 + } 86 + }, 87 + "node_modules/@atcute/util-fetch": { 88 + "version": "1.0.1", 89 + "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.1.tgz", 90 + "integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==", 91 + "license": "MIT", 92 + "dependencies": { 93 + "@badrap/valita": "^0.4.2" 94 + } 95 + }, 96 + "node_modules/@babel/code-frame": { 97 + "version": "7.27.1", 98 + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 99 + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 100 + "dev": true, 101 + "license": "MIT", 102 + "dependencies": { 103 + "@babel/helper-validator-identifier": "^7.27.1", 104 + "js-tokens": "^4.0.0", 105 + "picocolors": "^1.1.1" 106 + }, 107 + "engines": { 108 + "node": ">=6.9.0" 109 + } 110 + }, 111 + "node_modules/@babel/compat-data": { 112 + "version": "7.28.0", 113 + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", 114 + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", 115 + "dev": true, 116 + "license": "MIT", 117 + "engines": { 118 + "node": ">=6.9.0" 119 + } 120 + }, 121 + "node_modules/@babel/core": { 122 + "version": "7.28.0", 123 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", 124 + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", 125 + "dev": true, 126 + "license": "MIT", 127 + "dependencies": { 128 + "@ampproject/remapping": "^2.2.0", 129 + "@babel/code-frame": "^7.27.1", 130 + "@babel/generator": "^7.28.0", 131 + "@babel/helper-compilation-targets": "^7.27.2", 132 + "@babel/helper-module-transforms": "^7.27.3", 133 + "@babel/helpers": "^7.27.6", 134 + "@babel/parser": "^7.28.0", 135 + "@babel/template": "^7.27.2", 136 + "@babel/traverse": "^7.28.0", 137 + "@babel/types": "^7.28.0", 138 + "convert-source-map": "^2.0.0", 139 + "debug": "^4.1.0", 140 + "gensync": "^1.0.0-beta.2", 141 + "json5": "^2.2.3", 142 + "semver": "^6.3.1" 143 + }, 144 + "engines": { 145 + "node": ">=6.9.0" 146 + }, 147 + "funding": { 148 + "type": "opencollective", 149 + "url": "https://opencollective.com/babel" 150 + } 151 + }, 152 + "node_modules/@babel/generator": { 153 + "version": "7.28.0", 154 + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", 155 + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", 156 + "dev": true, 157 + "license": "MIT", 158 + "dependencies": { 159 + "@babel/parser": "^7.28.0", 160 + "@babel/types": "^7.28.0", 161 + "@jridgewell/gen-mapping": "^0.3.12", 162 + "@jridgewell/trace-mapping": "^0.3.28", 163 + "jsesc": "^3.0.2" 164 + }, 165 + "engines": { 166 + "node": ">=6.9.0" 167 + } 168 + }, 169 + "node_modules/@babel/helper-compilation-targets": { 170 + "version": "7.27.2", 171 + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", 172 + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", 173 + "dev": true, 174 + "license": "MIT", 175 + "dependencies": { 176 + "@babel/compat-data": "^7.27.2", 177 + "@babel/helper-validator-option": "^7.27.1", 178 + "browserslist": "^4.24.0", 179 + "lru-cache": "^5.1.1", 180 + "semver": "^6.3.1" 181 + }, 182 + "engines": { 183 + "node": ">=6.9.0" 184 + } 185 + }, 186 + "node_modules/@babel/helper-globals": { 187 + "version": "7.28.0", 188 + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", 189 + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", 190 + "dev": true, 191 + "license": "MIT", 192 + "engines": { 193 + "node": ">=6.9.0" 194 + } 195 + }, 196 + "node_modules/@babel/helper-module-imports": { 197 + "version": "7.27.1", 198 + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", 199 + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", 200 + "dev": true, 201 + "license": "MIT", 202 + "dependencies": { 203 + "@babel/traverse": "^7.27.1", 204 + "@babel/types": "^7.27.1" 205 + }, 206 + "engines": { 207 + "node": ">=6.9.0" 208 + } 209 + }, 210 + "node_modules/@babel/helper-module-transforms": { 211 + "version": "7.27.3", 212 + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", 213 + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", 214 + "dev": true, 215 + "license": "MIT", 216 + "dependencies": { 217 + "@babel/helper-module-imports": "^7.27.1", 218 + "@babel/helper-validator-identifier": "^7.27.1", 219 + "@babel/traverse": "^7.27.3" 220 + }, 221 + "engines": { 222 + "node": ">=6.9.0" 223 + }, 224 + "peerDependencies": { 225 + "@babel/core": "^7.0.0" 226 + } 227 + }, 228 + "node_modules/@babel/helper-plugin-utils": { 229 + "version": "7.27.1", 230 + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", 231 + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", 232 + "dev": true, 233 + "license": "MIT", 234 + "engines": { 235 + "node": ">=6.9.0" 236 + } 237 + }, 238 + "node_modules/@babel/helper-string-parser": { 239 + "version": "7.27.1", 240 + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 241 + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 242 + "dev": true, 243 + "license": "MIT", 244 + "engines": { 245 + "node": ">=6.9.0" 246 + } 247 + }, 248 + "node_modules/@babel/helper-validator-identifier": { 249 + "version": "7.27.1", 250 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 251 + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 252 + "dev": true, 253 + "license": "MIT", 254 + "engines": { 255 + "node": ">=6.9.0" 256 + } 257 + }, 258 + "node_modules/@babel/helper-validator-option": { 259 + "version": "7.27.1", 260 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 261 + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 262 + "dev": true, 263 + "license": "MIT", 264 + "engines": { 265 + "node": ">=6.9.0" 266 + } 267 + }, 268 + "node_modules/@babel/helpers": { 269 + "version": "7.28.2", 270 + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", 271 + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", 272 + "dev": true, 273 + "license": "MIT", 274 + "dependencies": { 275 + "@babel/template": "^7.27.2", 276 + "@babel/types": "^7.28.2" 277 + }, 278 + "engines": { 279 + "node": ">=6.9.0" 280 + } 281 + }, 282 + "node_modules/@babel/parser": { 283 + "version": "7.28.0", 284 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", 285 + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", 286 + "dev": true, 287 + "license": "MIT", 288 + "dependencies": { 289 + "@babel/types": "^7.28.0" 290 + }, 291 + "bin": { 292 + "parser": "bin/babel-parser.js" 293 + }, 294 + "engines": { 295 + "node": ">=6.0.0" 296 + } 297 + }, 298 + "node_modules/@babel/plugin-transform-react-jsx-self": { 299 + "version": "7.27.1", 300 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", 301 + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", 302 + "dev": true, 303 + "license": "MIT", 304 + "dependencies": { 305 + "@babel/helper-plugin-utils": "^7.27.1" 306 + }, 307 + "engines": { 308 + "node": ">=6.9.0" 309 + }, 310 + "peerDependencies": { 311 + "@babel/core": "^7.0.0-0" 312 + } 313 + }, 314 + "node_modules/@babel/plugin-transform-react-jsx-source": { 315 + "version": "7.27.1", 316 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", 317 + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", 318 + "dev": true, 319 + "license": "MIT", 320 + "dependencies": { 321 + "@babel/helper-plugin-utils": "^7.27.1" 322 + }, 323 + "engines": { 324 + "node": ">=6.9.0" 325 + }, 326 + "peerDependencies": { 327 + "@babel/core": "^7.0.0-0" 328 + } 329 + }, 330 + "node_modules/@babel/template": { 331 + "version": "7.27.2", 332 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 333 + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 334 + "dev": true, 335 + "license": "MIT", 336 + "dependencies": { 337 + "@babel/code-frame": "^7.27.1", 338 + "@babel/parser": "^7.27.2", 339 + "@babel/types": "^7.27.1" 340 + }, 341 + "engines": { 342 + "node": ">=6.9.0" 343 + } 344 + }, 345 + "node_modules/@babel/traverse": { 346 + "version": "7.28.0", 347 + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", 348 + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", 349 + "dev": true, 350 + "license": "MIT", 351 + "dependencies": { 352 + "@babel/code-frame": "^7.27.1", 353 + "@babel/generator": "^7.28.0", 354 + "@babel/helper-globals": "^7.28.0", 355 + "@babel/parser": "^7.28.0", 356 + "@babel/template": "^7.27.2", 357 + "@babel/types": "^7.28.0", 358 + "debug": "^4.3.1" 359 + }, 360 + "engines": { 361 + "node": ">=6.9.0" 362 + } 363 + }, 364 + "node_modules/@babel/types": { 365 + "version": "7.28.2", 366 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", 367 + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", 368 + "dev": true, 369 + "license": "MIT", 370 + "dependencies": { 371 + "@babel/helper-string-parser": "^7.27.1", 372 + "@babel/helper-validator-identifier": "^7.27.1" 373 + }, 374 + "engines": { 375 + "node": ">=6.9.0" 376 + } 377 + }, 378 + "node_modules/@badrap/valita": { 379 + "version": "0.4.5", 380 + "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.5.tgz", 381 + "integrity": "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ==", 382 + "license": "MIT", 383 + "engines": { 384 + "node": ">= 18" 385 + } 386 + }, 387 + "node_modules/@esbuild/aix-ppc64": { 388 + "version": "0.25.8", 389 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", 390 + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", 391 + "cpu": [ 392 + "ppc64" 393 + ], 394 + "dev": true, 395 + "license": "MIT", 396 + "optional": true, 397 + "os": [ 398 + "aix" 399 + ], 400 + "engines": { 401 + "node": ">=18" 402 + } 403 + }, 404 + "node_modules/@esbuild/android-arm": { 405 + "version": "0.25.8", 406 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", 407 + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", 408 + "cpu": [ 409 + "arm" 410 + ], 411 + "dev": true, 412 + "license": "MIT", 413 + "optional": true, 414 + "os": [ 415 + "android" 416 + ], 417 + "engines": { 418 + "node": ">=18" 419 + } 420 + }, 421 + "node_modules/@esbuild/android-arm64": { 422 + "version": "0.25.8", 423 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", 424 + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", 425 + "cpu": [ 426 + "arm64" 427 + ], 428 + "dev": true, 429 + "license": "MIT", 430 + "optional": true, 431 + "os": [ 432 + "android" 433 + ], 434 + "engines": { 435 + "node": ">=18" 436 + } 437 + }, 438 + "node_modules/@esbuild/android-x64": { 439 + "version": "0.25.8", 440 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", 441 + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", 442 + "cpu": [ 443 + "x64" 444 + ], 445 + "dev": true, 446 + "license": "MIT", 447 + "optional": true, 448 + "os": [ 449 + "android" 450 + ], 451 + "engines": { 452 + "node": ">=18" 453 + } 454 + }, 455 + "node_modules/@esbuild/darwin-arm64": { 456 + "version": "0.25.8", 457 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", 458 + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", 459 + "cpu": [ 460 + "arm64" 461 + ], 462 + "dev": true, 463 + "license": "MIT", 464 + "optional": true, 465 + "os": [ 466 + "darwin" 467 + ], 468 + "engines": { 469 + "node": ">=18" 470 + } 471 + }, 472 + "node_modules/@esbuild/darwin-x64": { 473 + "version": "0.25.8", 474 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", 475 + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", 476 + "cpu": [ 477 + "x64" 478 + ], 479 + "dev": true, 480 + "license": "MIT", 481 + "optional": true, 482 + "os": [ 483 + "darwin" 484 + ], 485 + "engines": { 486 + "node": ">=18" 487 + } 488 + }, 489 + "node_modules/@esbuild/freebsd-arm64": { 490 + "version": "0.25.8", 491 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", 492 + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", 493 + "cpu": [ 494 + "arm64" 495 + ], 496 + "dev": true, 497 + "license": "MIT", 498 + "optional": true, 499 + "os": [ 500 + "freebsd" 501 + ], 502 + "engines": { 503 + "node": ">=18" 504 + } 505 + }, 506 + "node_modules/@esbuild/freebsd-x64": { 507 + "version": "0.25.8", 508 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", 509 + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", 510 + "cpu": [ 511 + "x64" 512 + ], 513 + "dev": true, 514 + "license": "MIT", 515 + "optional": true, 516 + "os": [ 517 + "freebsd" 518 + ], 519 + "engines": { 520 + "node": ">=18" 521 + } 522 + }, 523 + "node_modules/@esbuild/linux-arm": { 524 + "version": "0.25.8", 525 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", 526 + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", 527 + "cpu": [ 528 + "arm" 529 + ], 530 + "dev": true, 531 + "license": "MIT", 532 + "optional": true, 533 + "os": [ 534 + "linux" 535 + ], 536 + "engines": { 537 + "node": ">=18" 538 + } 539 + }, 540 + "node_modules/@esbuild/linux-arm64": { 541 + "version": "0.25.8", 542 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", 543 + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", 544 + "cpu": [ 545 + "arm64" 546 + ], 547 + "dev": true, 548 + "license": "MIT", 549 + "optional": true, 550 + "os": [ 551 + "linux" 552 + ], 553 + "engines": { 554 + "node": ">=18" 555 + } 556 + }, 557 + "node_modules/@esbuild/linux-ia32": { 558 + "version": "0.25.8", 559 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", 560 + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", 561 + "cpu": [ 562 + "ia32" 563 + ], 564 + "dev": true, 565 + "license": "MIT", 566 + "optional": true, 567 + "os": [ 568 + "linux" 569 + ], 570 + "engines": { 571 + "node": ">=18" 572 + } 573 + }, 574 + "node_modules/@esbuild/linux-loong64": { 575 + "version": "0.25.8", 576 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", 577 + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", 578 + "cpu": [ 579 + "loong64" 580 + ], 581 + "dev": true, 582 + "license": "MIT", 583 + "optional": true, 584 + "os": [ 585 + "linux" 586 + ], 587 + "engines": { 588 + "node": ">=18" 589 + } 590 + }, 591 + "node_modules/@esbuild/linux-mips64el": { 592 + "version": "0.25.8", 593 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", 594 + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", 595 + "cpu": [ 596 + "mips64el" 597 + ], 598 + "dev": true, 599 + "license": "MIT", 600 + "optional": true, 601 + "os": [ 602 + "linux" 603 + ], 604 + "engines": { 605 + "node": ">=18" 606 + } 607 + }, 608 + "node_modules/@esbuild/linux-ppc64": { 609 + "version": "0.25.8", 610 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", 611 + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", 612 + "cpu": [ 613 + "ppc64" 614 + ], 615 + "dev": true, 616 + "license": "MIT", 617 + "optional": true, 618 + "os": [ 619 + "linux" 620 + ], 621 + "engines": { 622 + "node": ">=18" 623 + } 624 + }, 625 + "node_modules/@esbuild/linux-riscv64": { 626 + "version": "0.25.8", 627 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", 628 + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", 629 + "cpu": [ 630 + "riscv64" 631 + ], 632 + "dev": true, 633 + "license": "MIT", 634 + "optional": true, 635 + "os": [ 636 + "linux" 637 + ], 638 + "engines": { 639 + "node": ">=18" 640 + } 641 + }, 642 + "node_modules/@esbuild/linux-s390x": { 643 + "version": "0.25.8", 644 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", 645 + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", 646 + "cpu": [ 647 + "s390x" 648 + ], 649 + "dev": true, 650 + "license": "MIT", 651 + "optional": true, 652 + "os": [ 653 + "linux" 654 + ], 655 + "engines": { 656 + "node": ">=18" 657 + } 658 + }, 659 + "node_modules/@esbuild/linux-x64": { 660 + "version": "0.25.8", 661 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", 662 + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", 663 + "cpu": [ 664 + "x64" 665 + ], 666 + "dev": true, 667 + "license": "MIT", 668 + "optional": true, 669 + "os": [ 670 + "linux" 671 + ], 672 + "engines": { 673 + "node": ">=18" 674 + } 675 + }, 676 + "node_modules/@esbuild/netbsd-arm64": { 677 + "version": "0.25.8", 678 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", 679 + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", 680 + "cpu": [ 681 + "arm64" 682 + ], 683 + "dev": true, 684 + "license": "MIT", 685 + "optional": true, 686 + "os": [ 687 + "netbsd" 688 + ], 689 + "engines": { 690 + "node": ">=18" 691 + } 692 + }, 693 + "node_modules/@esbuild/netbsd-x64": { 694 + "version": "0.25.8", 695 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", 696 + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", 697 + "cpu": [ 698 + "x64" 699 + ], 700 + "dev": true, 701 + "license": "MIT", 702 + "optional": true, 703 + "os": [ 704 + "netbsd" 705 + ], 706 + "engines": { 707 + "node": ">=18" 708 + } 709 + }, 710 + "node_modules/@esbuild/openbsd-arm64": { 711 + "version": "0.25.8", 712 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", 713 + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", 714 + "cpu": [ 715 + "arm64" 716 + ], 717 + "dev": true, 718 + "license": "MIT", 719 + "optional": true, 720 + "os": [ 721 + "openbsd" 722 + ], 723 + "engines": { 724 + "node": ">=18" 725 + } 726 + }, 727 + "node_modules/@esbuild/openbsd-x64": { 728 + "version": "0.25.8", 729 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", 730 + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", 731 + "cpu": [ 732 + "x64" 733 + ], 734 + "dev": true, 735 + "license": "MIT", 736 + "optional": true, 737 + "os": [ 738 + "openbsd" 739 + ], 740 + "engines": { 741 + "node": ">=18" 742 + } 743 + }, 744 + "node_modules/@esbuild/openharmony-arm64": { 745 + "version": "0.25.8", 746 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", 747 + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", 748 + "cpu": [ 749 + "arm64" 750 + ], 751 + "dev": true, 752 + "license": "MIT", 753 + "optional": true, 754 + "os": [ 755 + "openharmony" 756 + ], 757 + "engines": { 758 + "node": ">=18" 759 + } 760 + }, 761 + "node_modules/@esbuild/sunos-x64": { 762 + "version": "0.25.8", 763 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", 764 + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", 765 + "cpu": [ 766 + "x64" 767 + ], 768 + "dev": true, 769 + "license": "MIT", 770 + "optional": true, 771 + "os": [ 772 + "sunos" 773 + ], 774 + "engines": { 775 + "node": ">=18" 776 + } 777 + }, 778 + "node_modules/@esbuild/win32-arm64": { 779 + "version": "0.25.8", 780 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", 781 + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", 782 + "cpu": [ 783 + "arm64" 784 + ], 785 + "dev": true, 786 + "license": "MIT", 787 + "optional": true, 788 + "os": [ 789 + "win32" 790 + ], 791 + "engines": { 792 + "node": ">=18" 793 + } 794 + }, 795 + "node_modules/@esbuild/win32-ia32": { 796 + "version": "0.25.8", 797 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", 798 + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", 799 + "cpu": [ 800 + "ia32" 801 + ], 802 + "dev": true, 803 + "license": "MIT", 804 + "optional": true, 805 + "os": [ 806 + "win32" 807 + ], 808 + "engines": { 809 + "node": ">=18" 810 + } 811 + }, 812 + "node_modules/@esbuild/win32-x64": { 813 + "version": "0.25.8", 814 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", 815 + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", 816 + "cpu": [ 817 + "x64" 818 + ], 819 + "dev": true, 820 + "license": "MIT", 821 + "optional": true, 822 + "os": [ 823 + "win32" 824 + ], 825 + "engines": { 826 + "node": ">=18" 827 + } 828 + }, 829 + "node_modules/@eslint-community/eslint-utils": { 830 + "version": "4.7.0", 831 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", 832 + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", 833 + "dev": true, 834 + "license": "MIT", 835 + "dependencies": { 836 + "eslint-visitor-keys": "^3.4.3" 837 + }, 838 + "engines": { 839 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 840 + }, 841 + "funding": { 842 + "url": "https://opencollective.com/eslint" 843 + }, 844 + "peerDependencies": { 845 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 846 + } 847 + }, 848 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 849 + "version": "3.4.3", 850 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 851 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 852 + "dev": true, 853 + "license": "Apache-2.0", 854 + "engines": { 855 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 856 + }, 857 + "funding": { 858 + "url": "https://opencollective.com/eslint" 859 + } 860 + }, 861 + "node_modules/@eslint-community/regexpp": { 862 + "version": "4.12.1", 863 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 864 + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 865 + "dev": true, 866 + "license": "MIT", 867 + "engines": { 868 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 869 + } 870 + }, 871 + "node_modules/@eslint/config-array": { 872 + "version": "0.21.0", 873 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", 874 + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", 875 + "dev": true, 876 + "license": "Apache-2.0", 877 + "dependencies": { 878 + "@eslint/object-schema": "^2.1.6", 879 + "debug": "^4.3.1", 880 + "minimatch": "^3.1.2" 881 + }, 882 + "engines": { 883 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 884 + } 885 + }, 886 + "node_modules/@eslint/config-helpers": { 887 + "version": "0.3.0", 888 + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", 889 + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", 890 + "dev": true, 891 + "license": "Apache-2.0", 892 + "engines": { 893 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 894 + } 895 + }, 896 + "node_modules/@eslint/core": { 897 + "version": "0.15.1", 898 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", 899 + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", 900 + "dev": true, 901 + "license": "Apache-2.0", 902 + "dependencies": { 903 + "@types/json-schema": "^7.0.15" 904 + }, 905 + "engines": { 906 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 907 + } 908 + }, 909 + "node_modules/@eslint/eslintrc": { 910 + "version": "3.3.1", 911 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", 912 + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", 913 + "dev": true, 914 + "license": "MIT", 915 + "dependencies": { 916 + "ajv": "^6.12.4", 917 + "debug": "^4.3.2", 918 + "espree": "^10.0.1", 919 + "globals": "^14.0.0", 920 + "ignore": "^5.2.0", 921 + "import-fresh": "^3.2.1", 922 + "js-yaml": "^4.1.0", 923 + "minimatch": "^3.1.2", 924 + "strip-json-comments": "^3.1.1" 925 + }, 926 + "engines": { 927 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 928 + }, 929 + "funding": { 930 + "url": "https://opencollective.com/eslint" 931 + } 932 + }, 933 + "node_modules/@eslint/eslintrc/node_modules/globals": { 934 + "version": "14.0.0", 935 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 936 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 937 + "dev": true, 938 + "license": "MIT", 939 + "engines": { 940 + "node": ">=18" 941 + }, 942 + "funding": { 943 + "url": "https://github.com/sponsors/sindresorhus" 944 + } 945 + }, 946 + "node_modules/@eslint/js": { 947 + "version": "9.31.0", 948 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", 949 + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", 950 + "dev": true, 951 + "license": "MIT", 952 + "engines": { 953 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 954 + }, 955 + "funding": { 956 + "url": "https://eslint.org/donate" 957 + } 958 + }, 959 + "node_modules/@eslint/object-schema": { 960 + "version": "2.1.6", 961 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 962 + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 963 + "dev": true, 964 + "license": "Apache-2.0", 965 + "engines": { 966 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 967 + } 968 + }, 969 + "node_modules/@eslint/plugin-kit": { 970 + "version": "0.3.4", 971 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", 972 + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", 973 + "dev": true, 974 + "license": "Apache-2.0", 975 + "dependencies": { 976 + "@eslint/core": "^0.15.1", 977 + "levn": "^0.4.1" 978 + }, 979 + "engines": { 980 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 981 + } 982 + }, 983 + "node_modules/@humanfs/core": { 984 + "version": "0.19.1", 985 + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 986 + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 987 + "dev": true, 988 + "license": "Apache-2.0", 989 + "engines": { 990 + "node": ">=18.18.0" 991 + } 992 + }, 993 + "node_modules/@humanfs/node": { 994 + "version": "0.16.6", 995 + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", 996 + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", 997 + "dev": true, 998 + "license": "Apache-2.0", 999 + "dependencies": { 1000 + "@humanfs/core": "^0.19.1", 1001 + "@humanwhocodes/retry": "^0.3.0" 1002 + }, 1003 + "engines": { 1004 + "node": ">=18.18.0" 1005 + } 1006 + }, 1007 + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { 1008 + "version": "0.3.1", 1009 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", 1010 + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", 1011 + "dev": true, 1012 + "license": "Apache-2.0", 1013 + "engines": { 1014 + "node": ">=18.18" 1015 + }, 1016 + "funding": { 1017 + "type": "github", 1018 + "url": "https://github.com/sponsors/nzakas" 1019 + } 1020 + }, 1021 + "node_modules/@humanwhocodes/module-importer": { 1022 + "version": "1.0.1", 1023 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 1024 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 1025 + "dev": true, 1026 + "license": "Apache-2.0", 1027 + "engines": { 1028 + "node": ">=12.22" 1029 + }, 1030 + "funding": { 1031 + "type": "github", 1032 + "url": "https://github.com/sponsors/nzakas" 1033 + } 1034 + }, 1035 + "node_modules/@humanwhocodes/retry": { 1036 + "version": "0.4.3", 1037 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 1038 + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 1039 + "dev": true, 1040 + "license": "Apache-2.0", 1041 + "engines": { 1042 + "node": ">=18.18" 1043 + }, 1044 + "funding": { 1045 + "type": "github", 1046 + "url": "https://github.com/sponsors/nzakas" 1047 + } 1048 + }, 1049 + "node_modules/@jridgewell/gen-mapping": { 1050 + "version": "0.3.12", 1051 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", 1052 + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", 1053 + "dev": true, 1054 + "license": "MIT", 1055 + "dependencies": { 1056 + "@jridgewell/sourcemap-codec": "^1.5.0", 1057 + "@jridgewell/trace-mapping": "^0.3.24" 1058 + } 1059 + }, 1060 + "node_modules/@jridgewell/resolve-uri": { 1061 + "version": "3.1.2", 1062 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 1063 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 1064 + "dev": true, 1065 + "license": "MIT", 1066 + "engines": { 1067 + "node": ">=6.0.0" 1068 + } 1069 + }, 1070 + "node_modules/@jridgewell/sourcemap-codec": { 1071 + "version": "1.5.4", 1072 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", 1073 + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", 1074 + "dev": true, 1075 + "license": "MIT" 1076 + }, 1077 + "node_modules/@jridgewell/trace-mapping": { 1078 + "version": "0.3.29", 1079 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", 1080 + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", 1081 + "dev": true, 1082 + "license": "MIT", 1083 + "dependencies": { 1084 + "@jridgewell/resolve-uri": "^3.1.0", 1085 + "@jridgewell/sourcemap-codec": "^1.4.14" 1086 + } 1087 + }, 1088 + "node_modules/@nodelib/fs.scandir": { 1089 + "version": "2.1.5", 1090 + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 1091 + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 1092 + "dev": true, 1093 + "license": "MIT", 1094 + "dependencies": { 1095 + "@nodelib/fs.stat": "2.0.5", 1096 + "run-parallel": "^1.1.9" 1097 + }, 1098 + "engines": { 1099 + "node": ">= 8" 1100 + } 1101 + }, 1102 + "node_modules/@nodelib/fs.stat": { 1103 + "version": "2.0.5", 1104 + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 1105 + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 1106 + "dev": true, 1107 + "license": "MIT", 1108 + "engines": { 1109 + "node": ">= 8" 1110 + } 1111 + }, 1112 + "node_modules/@nodelib/fs.walk": { 1113 + "version": "1.2.8", 1114 + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 1115 + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 1116 + "dev": true, 1117 + "license": "MIT", 1118 + "dependencies": { 1119 + "@nodelib/fs.scandir": "2.1.5", 1120 + "fastq": "^1.6.0" 1121 + }, 1122 + "engines": { 1123 + "node": ">= 8" 1124 + } 1125 + }, 1126 + "node_modules/@rolldown/pluginutils": { 1127 + "version": "1.0.0-beta.27", 1128 + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", 1129 + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", 1130 + "dev": true, 1131 + "license": "MIT" 1132 + }, 1133 + "node_modules/@rollup/rollup-android-arm-eabi": { 1134 + "version": "4.45.1", 1135 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", 1136 + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", 1137 + "cpu": [ 1138 + "arm" 1139 + ], 1140 + "dev": true, 1141 + "license": "MIT", 1142 + "optional": true, 1143 + "os": [ 1144 + "android" 1145 + ] 1146 + }, 1147 + "node_modules/@rollup/rollup-android-arm64": { 1148 + "version": "4.45.1", 1149 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", 1150 + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", 1151 + "cpu": [ 1152 + "arm64" 1153 + ], 1154 + "dev": true, 1155 + "license": "MIT", 1156 + "optional": true, 1157 + "os": [ 1158 + "android" 1159 + ] 1160 + }, 1161 + "node_modules/@rollup/rollup-darwin-arm64": { 1162 + "version": "4.45.1", 1163 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", 1164 + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", 1165 + "cpu": [ 1166 + "arm64" 1167 + ], 1168 + "dev": true, 1169 + "license": "MIT", 1170 + "optional": true, 1171 + "os": [ 1172 + "darwin" 1173 + ] 1174 + }, 1175 + "node_modules/@rollup/rollup-darwin-x64": { 1176 + "version": "4.45.1", 1177 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", 1178 + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", 1179 + "cpu": [ 1180 + "x64" 1181 + ], 1182 + "dev": true, 1183 + "license": "MIT", 1184 + "optional": true, 1185 + "os": [ 1186 + "darwin" 1187 + ] 1188 + }, 1189 + "node_modules/@rollup/rollup-freebsd-arm64": { 1190 + "version": "4.45.1", 1191 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", 1192 + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", 1193 + "cpu": [ 1194 + "arm64" 1195 + ], 1196 + "dev": true, 1197 + "license": "MIT", 1198 + "optional": true, 1199 + "os": [ 1200 + "freebsd" 1201 + ] 1202 + }, 1203 + "node_modules/@rollup/rollup-freebsd-x64": { 1204 + "version": "4.45.1", 1205 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", 1206 + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", 1207 + "cpu": [ 1208 + "x64" 1209 + ], 1210 + "dev": true, 1211 + "license": "MIT", 1212 + "optional": true, 1213 + "os": [ 1214 + "freebsd" 1215 + ] 1216 + }, 1217 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1218 + "version": "4.45.1", 1219 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", 1220 + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", 1221 + "cpu": [ 1222 + "arm" 1223 + ], 1224 + "dev": true, 1225 + "license": "MIT", 1226 + "optional": true, 1227 + "os": [ 1228 + "linux" 1229 + ] 1230 + }, 1231 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1232 + "version": "4.45.1", 1233 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", 1234 + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", 1235 + "cpu": [ 1236 + "arm" 1237 + ], 1238 + "dev": true, 1239 + "license": "MIT", 1240 + "optional": true, 1241 + "os": [ 1242 + "linux" 1243 + ] 1244 + }, 1245 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1246 + "version": "4.45.1", 1247 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", 1248 + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", 1249 + "cpu": [ 1250 + "arm64" 1251 + ], 1252 + "dev": true, 1253 + "license": "MIT", 1254 + "optional": true, 1255 + "os": [ 1256 + "linux" 1257 + ] 1258 + }, 1259 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1260 + "version": "4.45.1", 1261 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", 1262 + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", 1263 + "cpu": [ 1264 + "arm64" 1265 + ], 1266 + "dev": true, 1267 + "license": "MIT", 1268 + "optional": true, 1269 + "os": [ 1270 + "linux" 1271 + ] 1272 + }, 1273 + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 1274 + "version": "4.45.1", 1275 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", 1276 + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", 1277 + "cpu": [ 1278 + "loong64" 1279 + ], 1280 + "dev": true, 1281 + "license": "MIT", 1282 + "optional": true, 1283 + "os": [ 1284 + "linux" 1285 + ] 1286 + }, 1287 + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 1288 + "version": "4.45.1", 1289 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", 1290 + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", 1291 + "cpu": [ 1292 + "ppc64" 1293 + ], 1294 + "dev": true, 1295 + "license": "MIT", 1296 + "optional": true, 1297 + "os": [ 1298 + "linux" 1299 + ] 1300 + }, 1301 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1302 + "version": "4.45.1", 1303 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", 1304 + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", 1305 + "cpu": [ 1306 + "riscv64" 1307 + ], 1308 + "dev": true, 1309 + "license": "MIT", 1310 + "optional": true, 1311 + "os": [ 1312 + "linux" 1313 + ] 1314 + }, 1315 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1316 + "version": "4.45.1", 1317 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", 1318 + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", 1319 + "cpu": [ 1320 + "riscv64" 1321 + ], 1322 + "dev": true, 1323 + "license": "MIT", 1324 + "optional": true, 1325 + "os": [ 1326 + "linux" 1327 + ] 1328 + }, 1329 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1330 + "version": "4.45.1", 1331 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", 1332 + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", 1333 + "cpu": [ 1334 + "s390x" 1335 + ], 1336 + "dev": true, 1337 + "license": "MIT", 1338 + "optional": true, 1339 + "os": [ 1340 + "linux" 1341 + ] 1342 + }, 1343 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1344 + "version": "4.45.1", 1345 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", 1346 + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", 1347 + "cpu": [ 1348 + "x64" 1349 + ], 1350 + "dev": true, 1351 + "license": "MIT", 1352 + "optional": true, 1353 + "os": [ 1354 + "linux" 1355 + ] 1356 + }, 1357 + "node_modules/@rollup/rollup-linux-x64-musl": { 1358 + "version": "4.45.1", 1359 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", 1360 + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", 1361 + "cpu": [ 1362 + "x64" 1363 + ], 1364 + "dev": true, 1365 + "license": "MIT", 1366 + "optional": true, 1367 + "os": [ 1368 + "linux" 1369 + ] 1370 + }, 1371 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1372 + "version": "4.45.1", 1373 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", 1374 + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", 1375 + "cpu": [ 1376 + "arm64" 1377 + ], 1378 + "dev": true, 1379 + "license": "MIT", 1380 + "optional": true, 1381 + "os": [ 1382 + "win32" 1383 + ] 1384 + }, 1385 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1386 + "version": "4.45.1", 1387 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", 1388 + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", 1389 + "cpu": [ 1390 + "ia32" 1391 + ], 1392 + "dev": true, 1393 + "license": "MIT", 1394 + "optional": true, 1395 + "os": [ 1396 + "win32" 1397 + ] 1398 + }, 1399 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1400 + "version": "4.45.1", 1401 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", 1402 + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", 1403 + "cpu": [ 1404 + "x64" 1405 + ], 1406 + "dev": true, 1407 + "license": "MIT", 1408 + "optional": true, 1409 + "os": [ 1410 + "win32" 1411 + ] 1412 + }, 1413 + "node_modules/@types/babel__core": { 1414 + "version": "7.20.5", 1415 + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", 1416 + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", 1417 + "dev": true, 1418 + "license": "MIT", 1419 + "dependencies": { 1420 + "@babel/parser": "^7.20.7", 1421 + "@babel/types": "^7.20.7", 1422 + "@types/babel__generator": "*", 1423 + "@types/babel__template": "*", 1424 + "@types/babel__traverse": "*" 1425 + } 1426 + }, 1427 + "node_modules/@types/babel__generator": { 1428 + "version": "7.27.0", 1429 + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", 1430 + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", 1431 + "dev": true, 1432 + "license": "MIT", 1433 + "dependencies": { 1434 + "@babel/types": "^7.0.0" 1435 + } 1436 + }, 1437 + "node_modules/@types/babel__template": { 1438 + "version": "7.4.4", 1439 + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", 1440 + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", 1441 + "dev": true, 1442 + "license": "MIT", 1443 + "dependencies": { 1444 + "@babel/parser": "^7.1.0", 1445 + "@babel/types": "^7.0.0" 1446 + } 1447 + }, 1448 + "node_modules/@types/babel__traverse": { 1449 + "version": "7.20.7", 1450 + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", 1451 + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", 1452 + "dev": true, 1453 + "license": "MIT", 1454 + "dependencies": { 1455 + "@babel/types": "^7.20.7" 1456 + } 1457 + }, 1458 + "node_modules/@types/estree": { 1459 + "version": "1.0.8", 1460 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 1461 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 1462 + "dev": true, 1463 + "license": "MIT" 1464 + }, 1465 + "node_modules/@types/json-schema": { 1466 + "version": "7.0.15", 1467 + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 1468 + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 1469 + "dev": true, 1470 + "license": "MIT" 1471 + }, 1472 + "node_modules/@types/react": { 1473 + "version": "19.1.8", 1474 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", 1475 + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", 1476 + "dev": true, 1477 + "license": "MIT", 1478 + "dependencies": { 1479 + "csstype": "^3.0.2" 1480 + } 1481 + }, 1482 + "node_modules/@types/react-dom": { 1483 + "version": "19.1.6", 1484 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", 1485 + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", 1486 + "dev": true, 1487 + "license": "MIT", 1488 + "peerDependencies": { 1489 + "@types/react": "^19.0.0" 1490 + } 1491 + }, 1492 + "node_modules/@typescript-eslint/eslint-plugin": { 1493 + "version": "8.38.0", 1494 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", 1495 + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", 1496 + "dev": true, 1497 + "license": "MIT", 1498 + "dependencies": { 1499 + "@eslint-community/regexpp": "^4.10.0", 1500 + "@typescript-eslint/scope-manager": "8.38.0", 1501 + "@typescript-eslint/type-utils": "8.38.0", 1502 + "@typescript-eslint/utils": "8.38.0", 1503 + "@typescript-eslint/visitor-keys": "8.38.0", 1504 + "graphemer": "^1.4.0", 1505 + "ignore": "^7.0.0", 1506 + "natural-compare": "^1.4.0", 1507 + "ts-api-utils": "^2.1.0" 1508 + }, 1509 + "engines": { 1510 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1511 + }, 1512 + "funding": { 1513 + "type": "opencollective", 1514 + "url": "https://opencollective.com/typescript-eslint" 1515 + }, 1516 + "peerDependencies": { 1517 + "@typescript-eslint/parser": "^8.38.0", 1518 + "eslint": "^8.57.0 || ^9.0.0", 1519 + "typescript": ">=4.8.4 <5.9.0" 1520 + } 1521 + }, 1522 + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1523 + "version": "7.0.5", 1524 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 1525 + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 1526 + "dev": true, 1527 + "license": "MIT", 1528 + "engines": { 1529 + "node": ">= 4" 1530 + } 1531 + }, 1532 + "node_modules/@typescript-eslint/parser": { 1533 + "version": "8.38.0", 1534 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", 1535 + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", 1536 + "dev": true, 1537 + "license": "MIT", 1538 + "dependencies": { 1539 + "@typescript-eslint/scope-manager": "8.38.0", 1540 + "@typescript-eslint/types": "8.38.0", 1541 + "@typescript-eslint/typescript-estree": "8.38.0", 1542 + "@typescript-eslint/visitor-keys": "8.38.0", 1543 + "debug": "^4.3.4" 1544 + }, 1545 + "engines": { 1546 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1547 + }, 1548 + "funding": { 1549 + "type": "opencollective", 1550 + "url": "https://opencollective.com/typescript-eslint" 1551 + }, 1552 + "peerDependencies": { 1553 + "eslint": "^8.57.0 || ^9.0.0", 1554 + "typescript": ">=4.8.4 <5.9.0" 1555 + } 1556 + }, 1557 + "node_modules/@typescript-eslint/project-service": { 1558 + "version": "8.38.0", 1559 + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", 1560 + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", 1561 + "dev": true, 1562 + "license": "MIT", 1563 + "dependencies": { 1564 + "@typescript-eslint/tsconfig-utils": "^8.38.0", 1565 + "@typescript-eslint/types": "^8.38.0", 1566 + "debug": "^4.3.4" 1567 + }, 1568 + "engines": { 1569 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1570 + }, 1571 + "funding": { 1572 + "type": "opencollective", 1573 + "url": "https://opencollective.com/typescript-eslint" 1574 + }, 1575 + "peerDependencies": { 1576 + "typescript": ">=4.8.4 <5.9.0" 1577 + } 1578 + }, 1579 + "node_modules/@typescript-eslint/scope-manager": { 1580 + "version": "8.38.0", 1581 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", 1582 + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", 1583 + "dev": true, 1584 + "license": "MIT", 1585 + "dependencies": { 1586 + "@typescript-eslint/types": "8.38.0", 1587 + "@typescript-eslint/visitor-keys": "8.38.0" 1588 + }, 1589 + "engines": { 1590 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1591 + }, 1592 + "funding": { 1593 + "type": "opencollective", 1594 + "url": "https://opencollective.com/typescript-eslint" 1595 + } 1596 + }, 1597 + "node_modules/@typescript-eslint/tsconfig-utils": { 1598 + "version": "8.38.0", 1599 + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", 1600 + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", 1601 + "dev": true, 1602 + "license": "MIT", 1603 + "engines": { 1604 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1605 + }, 1606 + "funding": { 1607 + "type": "opencollective", 1608 + "url": "https://opencollective.com/typescript-eslint" 1609 + }, 1610 + "peerDependencies": { 1611 + "typescript": ">=4.8.4 <5.9.0" 1612 + } 1613 + }, 1614 + "node_modules/@typescript-eslint/type-utils": { 1615 + "version": "8.38.0", 1616 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", 1617 + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", 1618 + "dev": true, 1619 + "license": "MIT", 1620 + "dependencies": { 1621 + "@typescript-eslint/types": "8.38.0", 1622 + "@typescript-eslint/typescript-estree": "8.38.0", 1623 + "@typescript-eslint/utils": "8.38.0", 1624 + "debug": "^4.3.4", 1625 + "ts-api-utils": "^2.1.0" 1626 + }, 1627 + "engines": { 1628 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1629 + }, 1630 + "funding": { 1631 + "type": "opencollective", 1632 + "url": "https://opencollective.com/typescript-eslint" 1633 + }, 1634 + "peerDependencies": { 1635 + "eslint": "^8.57.0 || ^9.0.0", 1636 + "typescript": ">=4.8.4 <5.9.0" 1637 + } 1638 + }, 1639 + "node_modules/@typescript-eslint/types": { 1640 + "version": "8.38.0", 1641 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", 1642 + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", 1643 + "dev": true, 1644 + "license": "MIT", 1645 + "engines": { 1646 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1647 + }, 1648 + "funding": { 1649 + "type": "opencollective", 1650 + "url": "https://opencollective.com/typescript-eslint" 1651 + } 1652 + }, 1653 + "node_modules/@typescript-eslint/typescript-estree": { 1654 + "version": "8.38.0", 1655 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", 1656 + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", 1657 + "dev": true, 1658 + "license": "MIT", 1659 + "dependencies": { 1660 + "@typescript-eslint/project-service": "8.38.0", 1661 + "@typescript-eslint/tsconfig-utils": "8.38.0", 1662 + "@typescript-eslint/types": "8.38.0", 1663 + "@typescript-eslint/visitor-keys": "8.38.0", 1664 + "debug": "^4.3.4", 1665 + "fast-glob": "^3.3.2", 1666 + "is-glob": "^4.0.3", 1667 + "minimatch": "^9.0.4", 1668 + "semver": "^7.6.0", 1669 + "ts-api-utils": "^2.1.0" 1670 + }, 1671 + "engines": { 1672 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1673 + }, 1674 + "funding": { 1675 + "type": "opencollective", 1676 + "url": "https://opencollective.com/typescript-eslint" 1677 + }, 1678 + "peerDependencies": { 1679 + "typescript": ">=4.8.4 <5.9.0" 1680 + } 1681 + }, 1682 + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 1683 + "version": "2.0.2", 1684 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1685 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1686 + "dev": true, 1687 + "license": "MIT", 1688 + "dependencies": { 1689 + "balanced-match": "^1.0.0" 1690 + } 1691 + }, 1692 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1693 + "version": "9.0.5", 1694 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1695 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1696 + "dev": true, 1697 + "license": "ISC", 1698 + "dependencies": { 1699 + "brace-expansion": "^2.0.1" 1700 + }, 1701 + "engines": { 1702 + "node": ">=16 || 14 >=14.17" 1703 + }, 1704 + "funding": { 1705 + "url": "https://github.com/sponsors/isaacs" 1706 + } 1707 + }, 1708 + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1709 + "version": "7.7.2", 1710 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", 1711 + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", 1712 + "dev": true, 1713 + "license": "ISC", 1714 + "bin": { 1715 + "semver": "bin/semver.js" 1716 + }, 1717 + "engines": { 1718 + "node": ">=10" 1719 + } 1720 + }, 1721 + "node_modules/@typescript-eslint/utils": { 1722 + "version": "8.38.0", 1723 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", 1724 + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", 1725 + "dev": true, 1726 + "license": "MIT", 1727 + "dependencies": { 1728 + "@eslint-community/eslint-utils": "^4.7.0", 1729 + "@typescript-eslint/scope-manager": "8.38.0", 1730 + "@typescript-eslint/types": "8.38.0", 1731 + "@typescript-eslint/typescript-estree": "8.38.0" 1732 + }, 1733 + "engines": { 1734 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1735 + }, 1736 + "funding": { 1737 + "type": "opencollective", 1738 + "url": "https://opencollective.com/typescript-eslint" 1739 + }, 1740 + "peerDependencies": { 1741 + "eslint": "^8.57.0 || ^9.0.0", 1742 + "typescript": ">=4.8.4 <5.9.0" 1743 + } 1744 + }, 1745 + "node_modules/@typescript-eslint/visitor-keys": { 1746 + "version": "8.38.0", 1747 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", 1748 + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", 1749 + "dev": true, 1750 + "license": "MIT", 1751 + "dependencies": { 1752 + "@typescript-eslint/types": "8.38.0", 1753 + "eslint-visitor-keys": "^4.2.1" 1754 + }, 1755 + "engines": { 1756 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1757 + }, 1758 + "funding": { 1759 + "type": "opencollective", 1760 + "url": "https://opencollective.com/typescript-eslint" 1761 + } 1762 + }, 1763 + "node_modules/@vitejs/plugin-react": { 1764 + "version": "4.7.0", 1765 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", 1766 + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", 1767 + "dev": true, 1768 + "license": "MIT", 1769 + "dependencies": { 1770 + "@babel/core": "^7.28.0", 1771 + "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1772 + "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1773 + "@rolldown/pluginutils": "1.0.0-beta.27", 1774 + "@types/babel__core": "^7.20.5", 1775 + "react-refresh": "^0.17.0" 1776 + }, 1777 + "engines": { 1778 + "node": "^14.18.0 || >=16.0.0" 1779 + }, 1780 + "peerDependencies": { 1781 + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 1782 + } 1783 + }, 1784 + "node_modules/acorn": { 1785 + "version": "8.15.0", 1786 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 1787 + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 1788 + "dev": true, 1789 + "license": "MIT", 1790 + "bin": { 1791 + "acorn": "bin/acorn" 1792 + }, 1793 + "engines": { 1794 + "node": ">=0.4.0" 1795 + } 1796 + }, 1797 + "node_modules/acorn-jsx": { 1798 + "version": "5.3.2", 1799 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 1800 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 1801 + "dev": true, 1802 + "license": "MIT", 1803 + "peerDependencies": { 1804 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 1805 + } 1806 + }, 1807 + "node_modules/ajv": { 1808 + "version": "6.12.6", 1809 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 1810 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 1811 + "dev": true, 1812 + "license": "MIT", 1813 + "dependencies": { 1814 + "fast-deep-equal": "^3.1.1", 1815 + "fast-json-stable-stringify": "^2.0.0", 1816 + "json-schema-traverse": "^0.4.1", 1817 + "uri-js": "^4.2.2" 1818 + }, 1819 + "funding": { 1820 + "type": "github", 1821 + "url": "https://github.com/sponsors/epoberezkin" 1822 + } 1823 + }, 1824 + "node_modules/ansi-styles": { 1825 + "version": "4.3.0", 1826 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1827 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1828 + "dev": true, 1829 + "license": "MIT", 1830 + "dependencies": { 1831 + "color-convert": "^2.0.1" 1832 + }, 1833 + "engines": { 1834 + "node": ">=8" 1835 + }, 1836 + "funding": { 1837 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1838 + } 1839 + }, 1840 + "node_modules/argparse": { 1841 + "version": "2.0.1", 1842 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1843 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1844 + "dev": true, 1845 + "license": "Python-2.0" 1846 + }, 1847 + "node_modules/balanced-match": { 1848 + "version": "1.0.2", 1849 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1850 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1851 + "dev": true, 1852 + "license": "MIT" 1853 + }, 1854 + "node_modules/brace-expansion": { 1855 + "version": "1.1.12", 1856 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 1857 + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 1858 + "dev": true, 1859 + "license": "MIT", 1860 + "dependencies": { 1861 + "balanced-match": "^1.0.0", 1862 + "concat-map": "0.0.1" 1863 + } 1864 + }, 1865 + "node_modules/braces": { 1866 + "version": "3.0.3", 1867 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 1868 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 1869 + "dev": true, 1870 + "license": "MIT", 1871 + "dependencies": { 1872 + "fill-range": "^7.1.1" 1873 + }, 1874 + "engines": { 1875 + "node": ">=8" 1876 + } 1877 + }, 1878 + "node_modules/browserslist": { 1879 + "version": "4.25.1", 1880 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", 1881 + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", 1882 + "dev": true, 1883 + "funding": [ 1884 + { 1885 + "type": "opencollective", 1886 + "url": "https://opencollective.com/browserslist" 1887 + }, 1888 + { 1889 + "type": "tidelift", 1890 + "url": "https://tidelift.com/funding/github/npm/browserslist" 1891 + }, 1892 + { 1893 + "type": "github", 1894 + "url": "https://github.com/sponsors/ai" 1895 + } 1896 + ], 1897 + "license": "MIT", 1898 + "dependencies": { 1899 + "caniuse-lite": "^1.0.30001726", 1900 + "electron-to-chromium": "^1.5.173", 1901 + "node-releases": "^2.0.19", 1902 + "update-browserslist-db": "^1.1.3" 1903 + }, 1904 + "bin": { 1905 + "browserslist": "cli.js" 1906 + }, 1907 + "engines": { 1908 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1909 + } 1910 + }, 1911 + "node_modules/callsites": { 1912 + "version": "3.1.0", 1913 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 1914 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 1915 + "dev": true, 1916 + "license": "MIT", 1917 + "engines": { 1918 + "node": ">=6" 1919 + } 1920 + }, 1921 + "node_modules/caniuse-lite": { 1922 + "version": "1.0.30001727", 1923 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", 1924 + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", 1925 + "dev": true, 1926 + "funding": [ 1927 + { 1928 + "type": "opencollective", 1929 + "url": "https://opencollective.com/browserslist" 1930 + }, 1931 + { 1932 + "type": "tidelift", 1933 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 1934 + }, 1935 + { 1936 + "type": "github", 1937 + "url": "https://github.com/sponsors/ai" 1938 + } 1939 + ], 1940 + "license": "CC-BY-4.0" 1941 + }, 1942 + "node_modules/chalk": { 1943 + "version": "4.1.2", 1944 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1945 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1946 + "dev": true, 1947 + "license": "MIT", 1948 + "dependencies": { 1949 + "ansi-styles": "^4.1.0", 1950 + "supports-color": "^7.1.0" 1951 + }, 1952 + "engines": { 1953 + "node": ">=10" 1954 + }, 1955 + "funding": { 1956 + "url": "https://github.com/chalk/chalk?sponsor=1" 1957 + } 1958 + }, 1959 + "node_modules/color-convert": { 1960 + "version": "2.0.1", 1961 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1962 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1963 + "dev": true, 1964 + "license": "MIT", 1965 + "dependencies": { 1966 + "color-name": "~1.1.4" 1967 + }, 1968 + "engines": { 1969 + "node": ">=7.0.0" 1970 + } 1971 + }, 1972 + "node_modules/color-name": { 1973 + "version": "1.1.4", 1974 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1975 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1976 + "dev": true, 1977 + "license": "MIT" 1978 + }, 1979 + "node_modules/concat-map": { 1980 + "version": "0.0.1", 1981 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1982 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1983 + "dev": true, 1984 + "license": "MIT" 1985 + }, 1986 + "node_modules/convert-source-map": { 1987 + "version": "2.0.0", 1988 + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1989 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1990 + "dev": true, 1991 + "license": "MIT" 1992 + }, 1993 + "node_modules/cross-spawn": { 1994 + "version": "7.0.6", 1995 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1996 + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1997 + "dev": true, 1998 + "license": "MIT", 1999 + "dependencies": { 2000 + "path-key": "^3.1.0", 2001 + "shebang-command": "^2.0.0", 2002 + "which": "^2.0.1" 2003 + }, 2004 + "engines": { 2005 + "node": ">= 8" 2006 + } 2007 + }, 2008 + "node_modules/csstype": { 2009 + "version": "3.1.3", 2010 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 2011 + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 2012 + "dev": true, 2013 + "license": "MIT" 2014 + }, 2015 + "node_modules/debug": { 2016 + "version": "4.4.1", 2017 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 2018 + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 2019 + "dev": true, 2020 + "license": "MIT", 2021 + "dependencies": { 2022 + "ms": "^2.1.3" 2023 + }, 2024 + "engines": { 2025 + "node": ">=6.0" 2026 + }, 2027 + "peerDependenciesMeta": { 2028 + "supports-color": { 2029 + "optional": true 2030 + } 2031 + } 2032 + }, 2033 + "node_modules/deep-is": { 2034 + "version": "0.1.4", 2035 + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 2036 + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 2037 + "dev": true, 2038 + "license": "MIT" 2039 + }, 2040 + "node_modules/electron-to-chromium": { 2041 + "version": "1.5.190", 2042 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz", 2043 + "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==", 2044 + "dev": true, 2045 + "license": "ISC" 2046 + }, 2047 + "node_modules/esbuild": { 2048 + "version": "0.25.8", 2049 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", 2050 + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", 2051 + "dev": true, 2052 + "hasInstallScript": true, 2053 + "license": "MIT", 2054 + "bin": { 2055 + "esbuild": "bin/esbuild" 2056 + }, 2057 + "engines": { 2058 + "node": ">=18" 2059 + }, 2060 + "optionalDependencies": { 2061 + "@esbuild/aix-ppc64": "0.25.8", 2062 + "@esbuild/android-arm": "0.25.8", 2063 + "@esbuild/android-arm64": "0.25.8", 2064 + "@esbuild/android-x64": "0.25.8", 2065 + "@esbuild/darwin-arm64": "0.25.8", 2066 + "@esbuild/darwin-x64": "0.25.8", 2067 + "@esbuild/freebsd-arm64": "0.25.8", 2068 + "@esbuild/freebsd-x64": "0.25.8", 2069 + "@esbuild/linux-arm": "0.25.8", 2070 + "@esbuild/linux-arm64": "0.25.8", 2071 + "@esbuild/linux-ia32": "0.25.8", 2072 + "@esbuild/linux-loong64": "0.25.8", 2073 + "@esbuild/linux-mips64el": "0.25.8", 2074 + "@esbuild/linux-ppc64": "0.25.8", 2075 + "@esbuild/linux-riscv64": "0.25.8", 2076 + "@esbuild/linux-s390x": "0.25.8", 2077 + "@esbuild/linux-x64": "0.25.8", 2078 + "@esbuild/netbsd-arm64": "0.25.8", 2079 + "@esbuild/netbsd-x64": "0.25.8", 2080 + "@esbuild/openbsd-arm64": "0.25.8", 2081 + "@esbuild/openbsd-x64": "0.25.8", 2082 + "@esbuild/openharmony-arm64": "0.25.8", 2083 + "@esbuild/sunos-x64": "0.25.8", 2084 + "@esbuild/win32-arm64": "0.25.8", 2085 + "@esbuild/win32-ia32": "0.25.8", 2086 + "@esbuild/win32-x64": "0.25.8" 2087 + } 2088 + }, 2089 + "node_modules/escalade": { 2090 + "version": "3.2.0", 2091 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 2092 + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 2093 + "dev": true, 2094 + "license": "MIT", 2095 + "engines": { 2096 + "node": ">=6" 2097 + } 2098 + }, 2099 + "node_modules/escape-string-regexp": { 2100 + "version": "4.0.0", 2101 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 2102 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 2103 + "dev": true, 2104 + "license": "MIT", 2105 + "engines": { 2106 + "node": ">=10" 2107 + }, 2108 + "funding": { 2109 + "url": "https://github.com/sponsors/sindresorhus" 2110 + } 2111 + }, 2112 + "node_modules/eslint": { 2113 + "version": "9.31.0", 2114 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", 2115 + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", 2116 + "dev": true, 2117 + "license": "MIT", 2118 + "dependencies": { 2119 + "@eslint-community/eslint-utils": "^4.2.0", 2120 + "@eslint-community/regexpp": "^4.12.1", 2121 + "@eslint/config-array": "^0.21.0", 2122 + "@eslint/config-helpers": "^0.3.0", 2123 + "@eslint/core": "^0.15.0", 2124 + "@eslint/eslintrc": "^3.3.1", 2125 + "@eslint/js": "9.31.0", 2126 + "@eslint/plugin-kit": "^0.3.1", 2127 + "@humanfs/node": "^0.16.6", 2128 + "@humanwhocodes/module-importer": "^1.0.1", 2129 + "@humanwhocodes/retry": "^0.4.2", 2130 + "@types/estree": "^1.0.6", 2131 + "@types/json-schema": "^7.0.15", 2132 + "ajv": "^6.12.4", 2133 + "chalk": "^4.0.0", 2134 + "cross-spawn": "^7.0.6", 2135 + "debug": "^4.3.2", 2136 + "escape-string-regexp": "^4.0.0", 2137 + "eslint-scope": "^8.4.0", 2138 + "eslint-visitor-keys": "^4.2.1", 2139 + "espree": "^10.4.0", 2140 + "esquery": "^1.5.0", 2141 + "esutils": "^2.0.2", 2142 + "fast-deep-equal": "^3.1.3", 2143 + "file-entry-cache": "^8.0.0", 2144 + "find-up": "^5.0.0", 2145 + "glob-parent": "^6.0.2", 2146 + "ignore": "^5.2.0", 2147 + "imurmurhash": "^0.1.4", 2148 + "is-glob": "^4.0.0", 2149 + "json-stable-stringify-without-jsonify": "^1.0.1", 2150 + "lodash.merge": "^4.6.2", 2151 + "minimatch": "^3.1.2", 2152 + "natural-compare": "^1.4.0", 2153 + "optionator": "^0.9.3" 2154 + }, 2155 + "bin": { 2156 + "eslint": "bin/eslint.js" 2157 + }, 2158 + "engines": { 2159 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2160 + }, 2161 + "funding": { 2162 + "url": "https://eslint.org/donate" 2163 + }, 2164 + "peerDependencies": { 2165 + "jiti": "*" 2166 + }, 2167 + "peerDependenciesMeta": { 2168 + "jiti": { 2169 + "optional": true 2170 + } 2171 + } 2172 + }, 2173 + "node_modules/eslint-plugin-react-hooks": { 2174 + "version": "5.2.0", 2175 + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", 2176 + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", 2177 + "dev": true, 2178 + "license": "MIT", 2179 + "engines": { 2180 + "node": ">=10" 2181 + }, 2182 + "peerDependencies": { 2183 + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" 2184 + } 2185 + }, 2186 + "node_modules/eslint-plugin-react-refresh": { 2187 + "version": "0.4.20", 2188 + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", 2189 + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", 2190 + "dev": true, 2191 + "license": "MIT", 2192 + "peerDependencies": { 2193 + "eslint": ">=8.40" 2194 + } 2195 + }, 2196 + "node_modules/eslint-scope": { 2197 + "version": "8.4.0", 2198 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 2199 + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 2200 + "dev": true, 2201 + "license": "BSD-2-Clause", 2202 + "dependencies": { 2203 + "esrecurse": "^4.3.0", 2204 + "estraverse": "^5.2.0" 2205 + }, 2206 + "engines": { 2207 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2208 + }, 2209 + "funding": { 2210 + "url": "https://opencollective.com/eslint" 2211 + } 2212 + }, 2213 + "node_modules/eslint-visitor-keys": { 2214 + "version": "4.2.1", 2215 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 2216 + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 2217 + "dev": true, 2218 + "license": "Apache-2.0", 2219 + "engines": { 2220 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2221 + }, 2222 + "funding": { 2223 + "url": "https://opencollective.com/eslint" 2224 + } 2225 + }, 2226 + "node_modules/esm-env": { 2227 + "version": "1.2.2", 2228 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 2229 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 2230 + "license": "MIT" 2231 + }, 2232 + "node_modules/espree": { 2233 + "version": "10.4.0", 2234 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 2235 + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 2236 + "dev": true, 2237 + "license": "BSD-2-Clause", 2238 + "dependencies": { 2239 + "acorn": "^8.15.0", 2240 + "acorn-jsx": "^5.3.2", 2241 + "eslint-visitor-keys": "^4.2.1" 2242 + }, 2243 + "engines": { 2244 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2245 + }, 2246 + "funding": { 2247 + "url": "https://opencollective.com/eslint" 2248 + } 2249 + }, 2250 + "node_modules/esquery": { 2251 + "version": "1.6.0", 2252 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 2253 + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 2254 + "dev": true, 2255 + "license": "BSD-3-Clause", 2256 + "dependencies": { 2257 + "estraverse": "^5.1.0" 2258 + }, 2259 + "engines": { 2260 + "node": ">=0.10" 2261 + } 2262 + }, 2263 + "node_modules/esrecurse": { 2264 + "version": "4.3.0", 2265 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 2266 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 2267 + "dev": true, 2268 + "license": "BSD-2-Clause", 2269 + "dependencies": { 2270 + "estraverse": "^5.2.0" 2271 + }, 2272 + "engines": { 2273 + "node": ">=4.0" 2274 + } 2275 + }, 2276 + "node_modules/estraverse": { 2277 + "version": "5.3.0", 2278 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 2279 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 2280 + "dev": true, 2281 + "license": "BSD-2-Clause", 2282 + "engines": { 2283 + "node": ">=4.0" 2284 + } 2285 + }, 2286 + "node_modules/esutils": { 2287 + "version": "2.0.3", 2288 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 2289 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 2290 + "dev": true, 2291 + "license": "BSD-2-Clause", 2292 + "engines": { 2293 + "node": ">=0.10.0" 2294 + } 2295 + }, 2296 + "node_modules/fast-deep-equal": { 2297 + "version": "3.1.3", 2298 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 2299 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 2300 + "dev": true, 2301 + "license": "MIT" 2302 + }, 2303 + "node_modules/fast-glob": { 2304 + "version": "3.3.3", 2305 + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 2306 + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 2307 + "dev": true, 2308 + "license": "MIT", 2309 + "dependencies": { 2310 + "@nodelib/fs.stat": "^2.0.2", 2311 + "@nodelib/fs.walk": "^1.2.3", 2312 + "glob-parent": "^5.1.2", 2313 + "merge2": "^1.3.0", 2314 + "micromatch": "^4.0.8" 2315 + }, 2316 + "engines": { 2317 + "node": ">=8.6.0" 2318 + } 2319 + }, 2320 + "node_modules/fast-glob/node_modules/glob-parent": { 2321 + "version": "5.1.2", 2322 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 2323 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 2324 + "dev": true, 2325 + "license": "ISC", 2326 + "dependencies": { 2327 + "is-glob": "^4.0.1" 2328 + }, 2329 + "engines": { 2330 + "node": ">= 6" 2331 + } 2332 + }, 2333 + "node_modules/fast-json-stable-stringify": { 2334 + "version": "2.1.0", 2335 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 2336 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 2337 + "dev": true, 2338 + "license": "MIT" 2339 + }, 2340 + "node_modules/fast-levenshtein": { 2341 + "version": "2.0.6", 2342 + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 2343 + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 2344 + "dev": true, 2345 + "license": "MIT" 2346 + }, 2347 + "node_modules/fastq": { 2348 + "version": "1.19.1", 2349 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 2350 + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 2351 + "dev": true, 2352 + "license": "ISC", 2353 + "dependencies": { 2354 + "reusify": "^1.0.4" 2355 + } 2356 + }, 2357 + "node_modules/file-entry-cache": { 2358 + "version": "8.0.0", 2359 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 2360 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 2361 + "dev": true, 2362 + "license": "MIT", 2363 + "dependencies": { 2364 + "flat-cache": "^4.0.0" 2365 + }, 2366 + "engines": { 2367 + "node": ">=16.0.0" 2368 + } 2369 + }, 2370 + "node_modules/fill-range": { 2371 + "version": "7.1.1", 2372 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 2373 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 2374 + "dev": true, 2375 + "license": "MIT", 2376 + "dependencies": { 2377 + "to-regex-range": "^5.0.1" 2378 + }, 2379 + "engines": { 2380 + "node": ">=8" 2381 + } 2382 + }, 2383 + "node_modules/find-up": { 2384 + "version": "5.0.0", 2385 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 2386 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 2387 + "dev": true, 2388 + "license": "MIT", 2389 + "dependencies": { 2390 + "locate-path": "^6.0.0", 2391 + "path-exists": "^4.0.0" 2392 + }, 2393 + "engines": { 2394 + "node": ">=10" 2395 + }, 2396 + "funding": { 2397 + "url": "https://github.com/sponsors/sindresorhus" 2398 + } 2399 + }, 2400 + "node_modules/flat-cache": { 2401 + "version": "4.0.1", 2402 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 2403 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 2404 + "dev": true, 2405 + "license": "MIT", 2406 + "dependencies": { 2407 + "flatted": "^3.2.9", 2408 + "keyv": "^4.5.4" 2409 + }, 2410 + "engines": { 2411 + "node": ">=16" 2412 + } 2413 + }, 2414 + "node_modules/flatted": { 2415 + "version": "3.3.3", 2416 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 2417 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 2418 + "dev": true, 2419 + "license": "ISC" 2420 + }, 2421 + "node_modules/fsevents": { 2422 + "version": "2.3.3", 2423 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 2424 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 2425 + "dev": true, 2426 + "hasInstallScript": true, 2427 + "license": "MIT", 2428 + "optional": true, 2429 + "os": [ 2430 + "darwin" 2431 + ], 2432 + "engines": { 2433 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2434 + } 2435 + }, 2436 + "node_modules/gensync": { 2437 + "version": "1.0.0-beta.2", 2438 + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 2439 + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 2440 + "dev": true, 2441 + "license": "MIT", 2442 + "engines": { 2443 + "node": ">=6.9.0" 2444 + } 2445 + }, 2446 + "node_modules/glob-parent": { 2447 + "version": "6.0.2", 2448 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 2449 + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 2450 + "dev": true, 2451 + "license": "ISC", 2452 + "dependencies": { 2453 + "is-glob": "^4.0.3" 2454 + }, 2455 + "engines": { 2456 + "node": ">=10.13.0" 2457 + } 2458 + }, 2459 + "node_modules/globals": { 2460 + "version": "16.3.0", 2461 + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", 2462 + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", 2463 + "dev": true, 2464 + "license": "MIT", 2465 + "engines": { 2466 + "node": ">=18" 2467 + }, 2468 + "funding": { 2469 + "url": "https://github.com/sponsors/sindresorhus" 2470 + } 2471 + }, 2472 + "node_modules/graphemer": { 2473 + "version": "1.4.0", 2474 + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 2475 + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 2476 + "dev": true, 2477 + "license": "MIT" 2478 + }, 2479 + "node_modules/has-flag": { 2480 + "version": "4.0.0", 2481 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 2482 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 2483 + "dev": true, 2484 + "license": "MIT", 2485 + "engines": { 2486 + "node": ">=8" 2487 + } 2488 + }, 2489 + "node_modules/ignore": { 2490 + "version": "5.3.2", 2491 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 2492 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 2493 + "dev": true, 2494 + "license": "MIT", 2495 + "engines": { 2496 + "node": ">= 4" 2497 + } 2498 + }, 2499 + "node_modules/import-fresh": { 2500 + "version": "3.3.1", 2501 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 2502 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 2503 + "dev": true, 2504 + "license": "MIT", 2505 + "dependencies": { 2506 + "parent-module": "^1.0.0", 2507 + "resolve-from": "^4.0.0" 2508 + }, 2509 + "engines": { 2510 + "node": ">=6" 2511 + }, 2512 + "funding": { 2513 + "url": "https://github.com/sponsors/sindresorhus" 2514 + } 2515 + }, 2516 + "node_modules/imurmurhash": { 2517 + "version": "0.1.4", 2518 + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 2519 + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 2520 + "dev": true, 2521 + "license": "MIT", 2522 + "engines": { 2523 + "node": ">=0.8.19" 2524 + } 2525 + }, 2526 + "node_modules/is-extglob": { 2527 + "version": "2.1.1", 2528 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 2529 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 2530 + "dev": true, 2531 + "license": "MIT", 2532 + "engines": { 2533 + "node": ">=0.10.0" 2534 + } 2535 + }, 2536 + "node_modules/is-glob": { 2537 + "version": "4.0.3", 2538 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 2539 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 2540 + "dev": true, 2541 + "license": "MIT", 2542 + "dependencies": { 2543 + "is-extglob": "^2.1.1" 2544 + }, 2545 + "engines": { 2546 + "node": ">=0.10.0" 2547 + } 2548 + }, 2549 + "node_modules/is-number": { 2550 + "version": "7.0.0", 2551 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 2552 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 2553 + "dev": true, 2554 + "license": "MIT", 2555 + "engines": { 2556 + "node": ">=0.12.0" 2557 + } 2558 + }, 2559 + "node_modules/isexe": { 2560 + "version": "2.0.0", 2561 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2562 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 2563 + "dev": true, 2564 + "license": "ISC" 2565 + }, 2566 + "node_modules/js-tokens": { 2567 + "version": "4.0.0", 2568 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 2569 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 2570 + "dev": true, 2571 + "license": "MIT" 2572 + }, 2573 + "node_modules/js-yaml": { 2574 + "version": "4.1.0", 2575 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 2576 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 2577 + "dev": true, 2578 + "license": "MIT", 2579 + "dependencies": { 2580 + "argparse": "^2.0.1" 2581 + }, 2582 + "bin": { 2583 + "js-yaml": "bin/js-yaml.js" 2584 + } 2585 + }, 2586 + "node_modules/jsesc": { 2587 + "version": "3.1.0", 2588 + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 2589 + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 2590 + "dev": true, 2591 + "license": "MIT", 2592 + "bin": { 2593 + "jsesc": "bin/jsesc" 2594 + }, 2595 + "engines": { 2596 + "node": ">=6" 2597 + } 2598 + }, 2599 + "node_modules/json-buffer": { 2600 + "version": "3.0.1", 2601 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 2602 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 2603 + "dev": true, 2604 + "license": "MIT" 2605 + }, 2606 + "node_modules/json-schema-traverse": { 2607 + "version": "0.4.1", 2608 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 2609 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 2610 + "dev": true, 2611 + "license": "MIT" 2612 + }, 2613 + "node_modules/json-stable-stringify-without-jsonify": { 2614 + "version": "1.0.1", 2615 + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 2616 + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 2617 + "dev": true, 2618 + "license": "MIT" 2619 + }, 2620 + "node_modules/json5": { 2621 + "version": "2.2.3", 2622 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2623 + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 2624 + "dev": true, 2625 + "license": "MIT", 2626 + "bin": { 2627 + "json5": "lib/cli.js" 2628 + }, 2629 + "engines": { 2630 + "node": ">=6" 2631 + } 2632 + }, 2633 + "node_modules/keyv": { 2634 + "version": "4.5.4", 2635 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 2636 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 2637 + "dev": true, 2638 + "license": "MIT", 2639 + "dependencies": { 2640 + "json-buffer": "3.0.1" 2641 + } 2642 + }, 2643 + "node_modules/levn": { 2644 + "version": "0.4.1", 2645 + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 2646 + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 2647 + "dev": true, 2648 + "license": "MIT", 2649 + "dependencies": { 2650 + "prelude-ls": "^1.2.1", 2651 + "type-check": "~0.4.0" 2652 + }, 2653 + "engines": { 2654 + "node": ">= 0.8.0" 2655 + } 2656 + }, 2657 + "node_modules/locate-path": { 2658 + "version": "6.0.0", 2659 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 2660 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 2661 + "dev": true, 2662 + "license": "MIT", 2663 + "dependencies": { 2664 + "p-locate": "^5.0.0" 2665 + }, 2666 + "engines": { 2667 + "node": ">=10" 2668 + }, 2669 + "funding": { 2670 + "url": "https://github.com/sponsors/sindresorhus" 2671 + } 2672 + }, 2673 + "node_modules/lodash.merge": { 2674 + "version": "4.6.2", 2675 + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 2676 + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 2677 + "dev": true, 2678 + "license": "MIT" 2679 + }, 2680 + "node_modules/lru-cache": { 2681 + "version": "5.1.1", 2682 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 2683 + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 2684 + "dev": true, 2685 + "license": "ISC", 2686 + "dependencies": { 2687 + "yallist": "^3.0.2" 2688 + } 2689 + }, 2690 + "node_modules/merge2": { 2691 + "version": "1.4.1", 2692 + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 2693 + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 2694 + "dev": true, 2695 + "license": "MIT", 2696 + "engines": { 2697 + "node": ">= 8" 2698 + } 2699 + }, 2700 + "node_modules/micromatch": { 2701 + "version": "4.0.8", 2702 + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 2703 + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 2704 + "dev": true, 2705 + "license": "MIT", 2706 + "dependencies": { 2707 + "braces": "^3.0.3", 2708 + "picomatch": "^2.3.1" 2709 + }, 2710 + "engines": { 2711 + "node": ">=8.6" 2712 + } 2713 + }, 2714 + "node_modules/minimatch": { 2715 + "version": "3.1.2", 2716 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 2717 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 2718 + "dev": true, 2719 + "license": "ISC", 2720 + "dependencies": { 2721 + "brace-expansion": "^1.1.7" 2722 + }, 2723 + "engines": { 2724 + "node": "*" 2725 + } 2726 + }, 2727 + "node_modules/ms": { 2728 + "version": "2.1.3", 2729 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2730 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2731 + "dev": true, 2732 + "license": "MIT" 2733 + }, 2734 + "node_modules/nanoid": { 2735 + "version": "3.3.11", 2736 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 2737 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 2738 + "dev": true, 2739 + "funding": [ 2740 + { 2741 + "type": "github", 2742 + "url": "https://github.com/sponsors/ai" 2743 + } 2744 + ], 2745 + "license": "MIT", 2746 + "bin": { 2747 + "nanoid": "bin/nanoid.cjs" 2748 + }, 2749 + "engines": { 2750 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2751 + } 2752 + }, 2753 + "node_modules/natural-compare": { 2754 + "version": "1.4.0", 2755 + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 2756 + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 2757 + "dev": true, 2758 + "license": "MIT" 2759 + }, 2760 + "node_modules/node-releases": { 2761 + "version": "2.0.19", 2762 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", 2763 + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", 2764 + "dev": true, 2765 + "license": "MIT" 2766 + }, 2767 + "node_modules/optionator": { 2768 + "version": "0.9.4", 2769 + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 2770 + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 2771 + "dev": true, 2772 + "license": "MIT", 2773 + "dependencies": { 2774 + "deep-is": "^0.1.3", 2775 + "fast-levenshtein": "^2.0.6", 2776 + "levn": "^0.4.1", 2777 + "prelude-ls": "^1.2.1", 2778 + "type-check": "^0.4.0", 2779 + "word-wrap": "^1.2.5" 2780 + }, 2781 + "engines": { 2782 + "node": ">= 0.8.0" 2783 + } 2784 + }, 2785 + "node_modules/p-limit": { 2786 + "version": "3.1.0", 2787 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 2788 + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 2789 + "dev": true, 2790 + "license": "MIT", 2791 + "dependencies": { 2792 + "yocto-queue": "^0.1.0" 2793 + }, 2794 + "engines": { 2795 + "node": ">=10" 2796 + }, 2797 + "funding": { 2798 + "url": "https://github.com/sponsors/sindresorhus" 2799 + } 2800 + }, 2801 + "node_modules/p-locate": { 2802 + "version": "5.0.0", 2803 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 2804 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 2805 + "dev": true, 2806 + "license": "MIT", 2807 + "dependencies": { 2808 + "p-limit": "^3.0.2" 2809 + }, 2810 + "engines": { 2811 + "node": ">=10" 2812 + }, 2813 + "funding": { 2814 + "url": "https://github.com/sponsors/sindresorhus" 2815 + } 2816 + }, 2817 + "node_modules/parent-module": { 2818 + "version": "1.0.1", 2819 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 2820 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 2821 + "dev": true, 2822 + "license": "MIT", 2823 + "dependencies": { 2824 + "callsites": "^3.0.0" 2825 + }, 2826 + "engines": { 2827 + "node": ">=6" 2828 + } 2829 + }, 2830 + "node_modules/path-exists": { 2831 + "version": "4.0.0", 2832 + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 2833 + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 2834 + "dev": true, 2835 + "license": "MIT", 2836 + "engines": { 2837 + "node": ">=8" 2838 + } 2839 + }, 2840 + "node_modules/path-key": { 2841 + "version": "3.1.1", 2842 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2843 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2844 + "dev": true, 2845 + "license": "MIT", 2846 + "engines": { 2847 + "node": ">=8" 2848 + } 2849 + }, 2850 + "node_modules/picocolors": { 2851 + "version": "1.1.1", 2852 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2853 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 2854 + "dev": true, 2855 + "license": "ISC" 2856 + }, 2857 + "node_modules/picomatch": { 2858 + "version": "2.3.1", 2859 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 2860 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 2861 + "dev": true, 2862 + "license": "MIT", 2863 + "engines": { 2864 + "node": ">=8.6" 2865 + }, 2866 + "funding": { 2867 + "url": "https://github.com/sponsors/jonschlinkert" 2868 + } 2869 + }, 2870 + "node_modules/postcss": { 2871 + "version": "8.5.6", 2872 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 2873 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 2874 + "dev": true, 2875 + "funding": [ 2876 + { 2877 + "type": "opencollective", 2878 + "url": "https://opencollective.com/postcss/" 2879 + }, 2880 + { 2881 + "type": "tidelift", 2882 + "url": "https://tidelift.com/funding/github/npm/postcss" 2883 + }, 2884 + { 2885 + "type": "github", 2886 + "url": "https://github.com/sponsors/ai" 2887 + } 2888 + ], 2889 + "license": "MIT", 2890 + "dependencies": { 2891 + "nanoid": "^3.3.11", 2892 + "picocolors": "^1.1.1", 2893 + "source-map-js": "^1.2.1" 2894 + }, 2895 + "engines": { 2896 + "node": "^10 || ^12 || >=14" 2897 + } 2898 + }, 2899 + "node_modules/prelude-ls": { 2900 + "version": "1.2.1", 2901 + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 2902 + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 2903 + "dev": true, 2904 + "license": "MIT", 2905 + "engines": { 2906 + "node": ">= 0.8.0" 2907 + } 2908 + }, 2909 + "node_modules/punycode": { 2910 + "version": "2.3.1", 2911 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 2912 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 2913 + "dev": true, 2914 + "license": "MIT", 2915 + "engines": { 2916 + "node": ">=6" 2917 + } 2918 + }, 2919 + "node_modules/queue-microtask": { 2920 + "version": "1.2.3", 2921 + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2922 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2923 + "dev": true, 2924 + "funding": [ 2925 + { 2926 + "type": "github", 2927 + "url": "https://github.com/sponsors/feross" 2928 + }, 2929 + { 2930 + "type": "patreon", 2931 + "url": "https://www.patreon.com/feross" 2932 + }, 2933 + { 2934 + "type": "consulting", 2935 + "url": "https://feross.org/support" 2936 + } 2937 + ], 2938 + "license": "MIT" 2939 + }, 2940 + "node_modules/react": { 2941 + "version": "19.1.0", 2942 + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", 2943 + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", 2944 + "license": "MIT", 2945 + "engines": { 2946 + "node": ">=0.10.0" 2947 + } 2948 + }, 2949 + "node_modules/react-dom": { 2950 + "version": "19.1.0", 2951 + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", 2952 + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", 2953 + "license": "MIT", 2954 + "dependencies": { 2955 + "scheduler": "^0.26.0" 2956 + }, 2957 + "peerDependencies": { 2958 + "react": "^19.1.0" 2959 + } 2960 + }, 2961 + "node_modules/react-refresh": { 2962 + "version": "0.17.0", 2963 + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", 2964 + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", 2965 + "dev": true, 2966 + "license": "MIT", 2967 + "engines": { 2968 + "node": ">=0.10.0" 2969 + } 2970 + }, 2971 + "node_modules/resolve-from": { 2972 + "version": "4.0.0", 2973 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 2974 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 2975 + "dev": true, 2976 + "license": "MIT", 2977 + "engines": { 2978 + "node": ">=4" 2979 + } 2980 + }, 2981 + "node_modules/reusify": { 2982 + "version": "1.1.0", 2983 + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 2984 + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 2985 + "dev": true, 2986 + "license": "MIT", 2987 + "engines": { 2988 + "iojs": ">=1.0.0", 2989 + "node": ">=0.10.0" 2990 + } 2991 + }, 2992 + "node_modules/rollup": { 2993 + "version": "4.45.1", 2994 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", 2995 + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", 2996 + "dev": true, 2997 + "license": "MIT", 2998 + "dependencies": { 2999 + "@types/estree": "1.0.8" 3000 + }, 3001 + "bin": { 3002 + "rollup": "dist/bin/rollup" 3003 + }, 3004 + "engines": { 3005 + "node": ">=18.0.0", 3006 + "npm": ">=8.0.0" 3007 + }, 3008 + "optionalDependencies": { 3009 + "@rollup/rollup-android-arm-eabi": "4.45.1", 3010 + "@rollup/rollup-android-arm64": "4.45.1", 3011 + "@rollup/rollup-darwin-arm64": "4.45.1", 3012 + "@rollup/rollup-darwin-x64": "4.45.1", 3013 + "@rollup/rollup-freebsd-arm64": "4.45.1", 3014 + "@rollup/rollup-freebsd-x64": "4.45.1", 3015 + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", 3016 + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", 3017 + "@rollup/rollup-linux-arm64-gnu": "4.45.1", 3018 + "@rollup/rollup-linux-arm64-musl": "4.45.1", 3019 + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", 3020 + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", 3021 + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", 3022 + "@rollup/rollup-linux-riscv64-musl": "4.45.1", 3023 + "@rollup/rollup-linux-s390x-gnu": "4.45.1", 3024 + "@rollup/rollup-linux-x64-gnu": "4.45.1", 3025 + "@rollup/rollup-linux-x64-musl": "4.45.1", 3026 + "@rollup/rollup-win32-arm64-msvc": "4.45.1", 3027 + "@rollup/rollup-win32-ia32-msvc": "4.45.1", 3028 + "@rollup/rollup-win32-x64-msvc": "4.45.1", 3029 + "fsevents": "~2.3.2" 3030 + } 3031 + }, 3032 + "node_modules/run-parallel": { 3033 + "version": "1.2.0", 3034 + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 3035 + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 3036 + "dev": true, 3037 + "funding": [ 3038 + { 3039 + "type": "github", 3040 + "url": "https://github.com/sponsors/feross" 3041 + }, 3042 + { 3043 + "type": "patreon", 3044 + "url": "https://www.patreon.com/feross" 3045 + }, 3046 + { 3047 + "type": "consulting", 3048 + "url": "https://feross.org/support" 3049 + } 3050 + ], 3051 + "license": "MIT", 3052 + "dependencies": { 3053 + "queue-microtask": "^1.2.2" 3054 + } 3055 + }, 3056 + "node_modules/scheduler": { 3057 + "version": "0.26.0", 3058 + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", 3059 + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", 3060 + "license": "MIT" 3061 + }, 3062 + "node_modules/semver": { 3063 + "version": "6.3.1", 3064 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 3065 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 3066 + "dev": true, 3067 + "license": "ISC", 3068 + "bin": { 3069 + "semver": "bin/semver.js" 3070 + } 3071 + }, 3072 + "node_modules/shebang-command": { 3073 + "version": "2.0.0", 3074 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 3075 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 3076 + "dev": true, 3077 + "license": "MIT", 3078 + "dependencies": { 3079 + "shebang-regex": "^3.0.0" 3080 + }, 3081 + "engines": { 3082 + "node": ">=8" 3083 + } 3084 + }, 3085 + "node_modules/shebang-regex": { 3086 + "version": "3.0.0", 3087 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 3088 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 3089 + "dev": true, 3090 + "license": "MIT", 3091 + "engines": { 3092 + "node": ">=8" 3093 + } 3094 + }, 3095 + "node_modules/source-map-js": { 3096 + "version": "1.2.1", 3097 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 3098 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 3099 + "dev": true, 3100 + "license": "BSD-3-Clause", 3101 + "engines": { 3102 + "node": ">=0.10.0" 3103 + } 3104 + }, 3105 + "node_modules/strip-json-comments": { 3106 + "version": "3.1.1", 3107 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 3108 + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 3109 + "dev": true, 3110 + "license": "MIT", 3111 + "engines": { 3112 + "node": ">=8" 3113 + }, 3114 + "funding": { 3115 + "url": "https://github.com/sponsors/sindresorhus" 3116 + } 3117 + }, 3118 + "node_modules/supports-color": { 3119 + "version": "7.2.0", 3120 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 3121 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 3122 + "dev": true, 3123 + "license": "MIT", 3124 + "dependencies": { 3125 + "has-flag": "^4.0.0" 3126 + }, 3127 + "engines": { 3128 + "node": ">=8" 3129 + } 3130 + }, 3131 + "node_modules/tinyglobby": { 3132 + "version": "0.2.14", 3133 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 3134 + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 3135 + "dev": true, 3136 + "license": "MIT", 3137 + "dependencies": { 3138 + "fdir": "^6.4.4", 3139 + "picomatch": "^4.0.2" 3140 + }, 3141 + "engines": { 3142 + "node": ">=12.0.0" 3143 + }, 3144 + "funding": { 3145 + "url": "https://github.com/sponsors/SuperchupuDev" 3146 + } 3147 + }, 3148 + "node_modules/tinyglobby/node_modules/fdir": { 3149 + "version": "6.4.6", 3150 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", 3151 + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", 3152 + "dev": true, 3153 + "license": "MIT", 3154 + "peerDependencies": { 3155 + "picomatch": "^3 || ^4" 3156 + }, 3157 + "peerDependenciesMeta": { 3158 + "picomatch": { 3159 + "optional": true 3160 + } 3161 + } 3162 + }, 3163 + "node_modules/tinyglobby/node_modules/picomatch": { 3164 + "version": "4.0.3", 3165 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 3166 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 3167 + "dev": true, 3168 + "license": "MIT", 3169 + "engines": { 3170 + "node": ">=12" 3171 + }, 3172 + "funding": { 3173 + "url": "https://github.com/sponsors/jonschlinkert" 3174 + } 3175 + }, 3176 + "node_modules/to-regex-range": { 3177 + "version": "5.0.1", 3178 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 3179 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 3180 + "dev": true, 3181 + "license": "MIT", 3182 + "dependencies": { 3183 + "is-number": "^7.0.0" 3184 + }, 3185 + "engines": { 3186 + "node": ">=8.0" 3187 + } 3188 + }, 3189 + "node_modules/ts-api-utils": { 3190 + "version": "2.1.0", 3191 + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", 3192 + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", 3193 + "dev": true, 3194 + "license": "MIT", 3195 + "engines": { 3196 + "node": ">=18.12" 3197 + }, 3198 + "peerDependencies": { 3199 + "typescript": ">=4.8.4" 3200 + } 3201 + }, 3202 + "node_modules/type-check": { 3203 + "version": "0.4.0", 3204 + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 3205 + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 3206 + "dev": true, 3207 + "license": "MIT", 3208 + "dependencies": { 3209 + "prelude-ls": "^1.2.1" 3210 + }, 3211 + "engines": { 3212 + "node": ">= 0.8.0" 3213 + } 3214 + }, 3215 + "node_modules/typescript": { 3216 + "version": "5.8.3", 3217 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 3218 + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 3219 + "dev": true, 3220 + "license": "Apache-2.0", 3221 + "bin": { 3222 + "tsc": "bin/tsc", 3223 + "tsserver": "bin/tsserver" 3224 + }, 3225 + "engines": { 3226 + "node": ">=14.17" 3227 + } 3228 + }, 3229 + "node_modules/typescript-eslint": { 3230 + "version": "8.38.0", 3231 + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz", 3232 + "integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==", 3233 + "dev": true, 3234 + "license": "MIT", 3235 + "dependencies": { 3236 + "@typescript-eslint/eslint-plugin": "8.38.0", 3237 + "@typescript-eslint/parser": "8.38.0", 3238 + "@typescript-eslint/typescript-estree": "8.38.0", 3239 + "@typescript-eslint/utils": "8.38.0" 3240 + }, 3241 + "engines": { 3242 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3243 + }, 3244 + "funding": { 3245 + "type": "opencollective", 3246 + "url": "https://opencollective.com/typescript-eslint" 3247 + }, 3248 + "peerDependencies": { 3249 + "eslint": "^8.57.0 || ^9.0.0", 3250 + "typescript": ">=4.8.4 <5.9.0" 3251 + } 3252 + }, 3253 + "node_modules/update-browserslist-db": { 3254 + "version": "1.1.3", 3255 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", 3256 + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", 3257 + "dev": true, 3258 + "funding": [ 3259 + { 3260 + "type": "opencollective", 3261 + "url": "https://opencollective.com/browserslist" 3262 + }, 3263 + { 3264 + "type": "tidelift", 3265 + "url": "https://tidelift.com/funding/github/npm/browserslist" 3266 + }, 3267 + { 3268 + "type": "github", 3269 + "url": "https://github.com/sponsors/ai" 3270 + } 3271 + ], 3272 + "license": "MIT", 3273 + "dependencies": { 3274 + "escalade": "^3.2.0", 3275 + "picocolors": "^1.1.1" 3276 + }, 3277 + "bin": { 3278 + "update-browserslist-db": "cli.js" 3279 + }, 3280 + "peerDependencies": { 3281 + "browserslist": ">= 4.21.0" 3282 + } 3283 + }, 3284 + "node_modules/uri-js": { 3285 + "version": "4.4.1", 3286 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 3287 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 3288 + "dev": true, 3289 + "license": "BSD-2-Clause", 3290 + "dependencies": { 3291 + "punycode": "^2.1.0" 3292 + } 3293 + }, 3294 + "node_modules/vite": { 3295 + "version": "7.0.6", 3296 + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", 3297 + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", 3298 + "dev": true, 3299 + "license": "MIT", 3300 + "dependencies": { 3301 + "esbuild": "^0.25.0", 3302 + "fdir": "^6.4.6", 3303 + "picomatch": "^4.0.3", 3304 + "postcss": "^8.5.6", 3305 + "rollup": "^4.40.0", 3306 + "tinyglobby": "^0.2.14" 3307 + }, 3308 + "bin": { 3309 + "vite": "bin/vite.js" 3310 + }, 3311 + "engines": { 3312 + "node": "^20.19.0 || >=22.12.0" 3313 + }, 3314 + "funding": { 3315 + "url": "https://github.com/vitejs/vite?sponsor=1" 3316 + }, 3317 + "optionalDependencies": { 3318 + "fsevents": "~2.3.3" 3319 + }, 3320 + "peerDependencies": { 3321 + "@types/node": "^20.19.0 || >=22.12.0", 3322 + "jiti": ">=1.21.0", 3323 + "less": "^4.0.0", 3324 + "lightningcss": "^1.21.0", 3325 + "sass": "^1.70.0", 3326 + "sass-embedded": "^1.70.0", 3327 + "stylus": ">=0.54.8", 3328 + "sugarss": "^5.0.0", 3329 + "terser": "^5.16.0", 3330 + "tsx": "^4.8.1", 3331 + "yaml": "^2.4.2" 3332 + }, 3333 + "peerDependenciesMeta": { 3334 + "@types/node": { 3335 + "optional": true 3336 + }, 3337 + "jiti": { 3338 + "optional": true 3339 + }, 3340 + "less": { 3341 + "optional": true 3342 + }, 3343 + "lightningcss": { 3344 + "optional": true 3345 + }, 3346 + "sass": { 3347 + "optional": true 3348 + }, 3349 + "sass-embedded": { 3350 + "optional": true 3351 + }, 3352 + "stylus": { 3353 + "optional": true 3354 + }, 3355 + "sugarss": { 3356 + "optional": true 3357 + }, 3358 + "terser": { 3359 + "optional": true 3360 + }, 3361 + "tsx": { 3362 + "optional": true 3363 + }, 3364 + "yaml": { 3365 + "optional": true 3366 + } 3367 + } 3368 + }, 3369 + "node_modules/vite/node_modules/fdir": { 3370 + "version": "6.4.6", 3371 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", 3372 + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", 3373 + "dev": true, 3374 + "license": "MIT", 3375 + "peerDependencies": { 3376 + "picomatch": "^3 || ^4" 3377 + }, 3378 + "peerDependenciesMeta": { 3379 + "picomatch": { 3380 + "optional": true 3381 + } 3382 + } 3383 + }, 3384 + "node_modules/vite/node_modules/picomatch": { 3385 + "version": "4.0.3", 3386 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 3387 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 3388 + "dev": true, 3389 + "license": "MIT", 3390 + "engines": { 3391 + "node": ">=12" 3392 + }, 3393 + "funding": { 3394 + "url": "https://github.com/sponsors/jonschlinkert" 3395 + } 3396 + }, 3397 + "node_modules/which": { 3398 + "version": "2.0.2", 3399 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 3400 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 3401 + "dev": true, 3402 + "license": "ISC", 3403 + "dependencies": { 3404 + "isexe": "^2.0.0" 3405 + }, 3406 + "bin": { 3407 + "node-which": "bin/node-which" 3408 + }, 3409 + "engines": { 3410 + "node": ">= 8" 3411 + } 3412 + }, 3413 + "node_modules/word-wrap": { 3414 + "version": "1.2.5", 3415 + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 3416 + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 3417 + "dev": true, 3418 + "license": "MIT", 3419 + "engines": { 3420 + "node": ">=0.10.0" 3421 + } 3422 + }, 3423 + "node_modules/yallist": { 3424 + "version": "3.1.1", 3425 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 3426 + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 3427 + "dev": true, 3428 + "license": "ISC" 3429 + }, 3430 + "node_modules/yocto-queue": { 3431 + "version": "0.1.0", 3432 + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 3433 + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 3434 + "dev": true, 3435 + "license": "MIT", 3436 + "engines": { 3437 + "node": ">=10" 3438 + }, 3439 + "funding": { 3440 + "url": "https://github.com/sponsors/sindresorhus" 3441 + } 3442 + } 3443 + } 3444 + }
+32
live-embed/package.json
··· 1 + { 2 + "name": "live-embed", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "tsc -b && vite build", 9 + "bsky": "vite build --base=/zero-bluesky-realtime-embed/", 10 + "lint": "eslint .", 11 + "preview": "vite preview" 12 + }, 13 + "dependencies": { 14 + "@atcute/client": "^4.0.3", 15 + "@atcute/identity-resolver": "^1.1.3", 16 + "react": "^19.1.0", 17 + "react-dom": "^19.1.0" 18 + }, 19 + "devDependencies": { 20 + "@eslint/js": "^9.30.1", 21 + "@types/react": "^19.1.8", 22 + "@types/react-dom": "^19.1.6", 23 + "@vitejs/plugin-react": "^4.6.0", 24 + "eslint": "^9.30.1", 25 + "eslint-plugin-react-hooks": "^5.2.0", 26 + "eslint-plugin-react-refresh": "^0.4.20", 27 + "globals": "^16.3.0", 28 + "typescript": "~5.8.3", 29 + "typescript-eslint": "^8.35.1", 30 + "vite": "^7.0.4" 31 + } 32 + }
live-embed/public/.gitkeep

This is a binary file and will not be displayed.

+22
live-embed/src/App.css
··· 1 + #root { 2 + max-width: 51em; 3 + margin: 0 auto; 4 + padding: 2rem; 5 + text-align: center; 6 + } 7 + 8 + .with { 9 + margin: 0.5em 0 1.5em; 10 + font-size: 1.2em; 11 + } 12 + 13 + .posts { 14 + display: flex; 15 + flex-wrap: wrap; 16 + gap: 1rem; 17 + justify-content: center; 18 + } 19 + 20 + .explain { 21 + text-align: left; 22 + }
+76
live-embed/src/App.tsx
··· 1 + import { useEffect, useState } from 'react'; 2 + import { Post } from './Post'; 3 + import { rotatingPair } from './samplePosts'; 4 + import { Spacedust } from './spacedust'; 5 + 6 + import './App.css' 7 + 8 + function App() { 9 + const [currentPair, setCurrentPair] = useState([]); 10 + const [updates, setUpdates] = useState({}); 11 + 12 + useEffect(() => { 13 + let iMightBeAZombie = false; 14 + 15 + const spacedust = new Spacedust(handleLink); 16 + const cancelRotation = rotatingPair(updatedPair => { 17 + if (iMightBeAZombie) return; 18 + 19 + setCurrentPair(updatedPair); 20 + spacedust.setSubjects(updatedPair); 21 + setUpdates(current => { // cleanup, probably should combine with pair directly 22 + const next = {}; 23 + updatedPair.forEach(uri => next[uri] = current[uri] ?? {}); 24 + return next; 25 + }); 26 + }); 27 + 28 + function handleLink({ subject, source }) { 29 + setUpdates(current => ({ 30 + ...current, 31 + [subject]: { 32 + ...current[subject], 33 + [source]: (current[subject][source] ?? 0) + 1 34 + }, 35 + })); 36 + } 37 + 38 + return () => { 39 + cancelRotation(); 40 + spacedust.close(); 41 + iMightBeAZombie = true; 42 + }; 43 + }, []); 44 + 45 + return ( 46 + <> 47 + <h1>zero bluesky infra post rendering (WIP)</h1> 48 + <p className="with">with real-time interaction count updates</p> 49 + <div className="posts"> 50 + {currentPair.map(p => ( 51 + <Post key={p} atUri={p} updatedLinks={updates[p]} /> 52 + ))} 53 + </div> 54 + 55 + <div className="explain"> 56 + <h2>How does it work?</h2> 57 + <ul> 58 + <li><strong>Post content</strong>: fetches direct from PDS with <a href="https://github.com/mary-ext/atcute" target="_blank">atcute</a>.</li> 59 + <li><strong>Interaction counts</strong>: queries <a href="https://constellation.microcosm.blue/" target="_blank">constellation</a>.</li> 60 + <li><strong>Interaction updates</strong>: subscribes to <a href="https://spacedust.microcosm.blue/" target="_blank">spacedust</a>.</li> 61 + <li>There is no backend.</li> 62 + </ul> 63 + <p>The post selection takes a couple top posts from the public bluesky Discover feed so I guess it's kind of cheating but hey. And media files load from Bluesky's CDN (or, will soon) so that's also cheating.</p> 64 + 65 + <p>Source code is on <a href="https://tangled.sh/@bad-example.com/spacedust-utils/tree/main/live-embed" target="_blank">tangled</a>. Still a work in progress.</p> 66 + 67 + <h2>PS</h2> 68 + <p>If you actually want to embed a post on a web page, check out <a href="https://mary-ext.codeberg.page/bluesky-embed/" target="_blank"><code>&lt;bluesky-embed&gt;</code></a> from <a href="https://mary.my.id" target="_blank">mary</a>. It's a very solid post renderer, unlike this demo.</p> 69 + 70 + </div> 71 + 72 + </> 73 + ) 74 + } 75 + 76 + export default App
+67
live-embed/src/Fetch.tsx
··· 1 + import { useContext, useEffect, useState } from 'react'; 2 + 3 + const loadingDefault = () => ( 4 + <em>Loading&hellip;</em> 5 + ); 6 + 7 + const errorDefault = err => ( 8 + <span className="error"> 9 + <strong>Error</strong>:<br/>{`${err}`} 10 + </span> 11 + ); 12 + 13 + export function Fetch({ using, args, ok, loading, error }) { 14 + const [asyncData, setAsyncData] = useState({ state: null }); 15 + 16 + useEffect(() => { 17 + let ignore = false; 18 + setAsyncData({ state: 'loading' }); 19 + (async () => { 20 + try { 21 + const data = await using(...args); 22 + !ignore && setAsyncData({ state: 'done', data }); 23 + } catch (err) { 24 + !ignore && setAsyncData({ state: 'error', err }); 25 + } 26 + })(); 27 + return () => { ignore = true; } 28 + }, args); 29 + 30 + if (asyncData.state === 'loading') { 31 + return (loading || loadingDefault)(...args); 32 + } else if (asyncData.state === 'error') { 33 + return (error || errorDefault)(asyncData.err); 34 + } else if (asyncData.state === null) { 35 + return <span>wat, request has not started (bug?)</span>; 36 + } else { 37 + if (asyncData.state !== 'done') { console.warn(`unexpected async data state: ${asyncData.state}`); } 38 + return ok(asyncData.data); 39 + } 40 + } 41 + 42 + ///// 43 + 44 + async function getJson(url, credentials) { 45 + const opts = {}; 46 + if (credentials) opts.credentials = 'include'; 47 + const res = await fetch(url, opts); 48 + if (!res.ok) { 49 + const m = await res.text(); 50 + throw new Error(`Failed to fetch: ${m}`); 51 + } 52 + return await res.json(); 53 + } 54 + 55 + export function GetJson({ url, params, credentials, ...forFetch }) { 56 + const u = new URL(url); 57 + for (let [key, val] of Object.entries(params ?? {})) { 58 + u.searchParams.append(key, val); 59 + } 60 + return ( 61 + <Fetch 62 + using={getJson} 63 + args={[u.toString(), credentials]} 64 + {...forFetch} 65 + /> 66 + ); 67 + }
+42
live-embed/src/Post.css
··· 1 + .post { 2 + background-color: rgb(32, 41, 53); 3 + border: 1px solid rgb(46, 64, 82); 4 + border-radius: 0.1em; 5 + display: flex; 6 + flex-basis: 20em; 7 + flex-direction: column; 8 + min-width: 12em; 9 + max-width: 24em; 10 + } 11 + 12 + .record-contents { 13 + flex-grow: 1; 14 + padding: 1em; 15 + text-align: left; 16 + align-content: center; 17 + } 18 + 19 + .stats { 20 + border-top: 0.5px solid rgb(46, 64, 82); 21 + display: flex; 22 + justify-content: space-around; 23 + margin: 0; 24 + gap: 1em; 25 + padding: 0 1em; 26 + opacity: 0.667; 27 + } 28 + .stat.reply:before { 29 + content: "๐Ÿซง "; 30 + } 31 + .stat.repost:before { 32 + content: "โ™ป "; 33 + } 34 + .stat.like:before { 35 + content: "โ™ก "; 36 + } 37 + 38 + @media (prefers-color-scheme: light) { 39 + .post { 40 + background-color: rgb(241, 243, 245); 41 + } 42 + }
+68
live-embed/src/Post.tsx
··· 1 + import { useEffect, useState } from 'react'; 2 + import { getPostStats } from './constellation'; 3 + import { getAtUri } from './getPost'; 4 + import linkSources from './linkSources'; 5 + 6 + import './Post.css'; 7 + 8 + export function Post({ atUri, updatedLinks }) { 9 + const [record, setRecord] = useState({ state: 'loading' }); 10 + const [baseStats, setBaseStats] = useState({}); 11 + const liveStats = { ...baseStats }; 12 + 13 + for (const [key, val] of Object.entries(updatedLinks)) { 14 + const name = linkSources[key]; 15 + if (!liveStats[name]) liveStats[name] = 0; 16 + liveStats[name] += val; 17 + } 18 + 19 + useEffect(() => { 20 + let alive = true; 21 + 22 + getAtUri(atUri).then( 23 + record => alive && setRecord({ state: 'loaded', record }), 24 + error => alive && setRecord({ state: 'failed', error })); 25 + 26 + getPostStats(atUri).then( 27 + stats => alive && setBaseStats(stats), 28 + e => console.warn('fetching base stats failed', e)); 29 + 30 + return () => alive = false; 31 + }, [atUri]); 32 + 33 + return ( 34 + <div className="post"> 35 + {record.state === 'loading' 36 + ? <p className="loading">loading post&hellip;</p> 37 + : record.state === 'failed' 38 + ? <p className="failed">failed to load post :/ {`${record.error}`}</p> 39 + : <RecordContents data={record.record} /> 40 + } 41 + <p className="stats"> 42 + {liveStats.reply && ( 43 + <span className="stat reply" title="total replies"> 44 + {liveStats.reply.toLocaleString()} 45 + </span> 46 + )} 47 + {liveStats.repost && ( 48 + <span className="stat repost" title="total reposts"> 49 + {liveStats.repost.toLocaleString()} 50 + </span> 51 + )} 52 + {liveStats.like && ( 53 + <span className="stat like" title="total likes"> 54 + {liveStats.like.toLocaleString()} 55 + </span> 56 + )} 57 + </p> 58 + </div> 59 + ); 60 + } 61 + 62 + function RecordContents({ data }) { 63 + return ( 64 + <div className="record-contents"> 65 + {data.text} 66 + </div> 67 + ); 68 + }
live-embed/src/assets/.gitkeep

This is a binary file and will not be displayed.

+39
live-embed/src/constellation.ts
··· 1 + import linkSources from './linkSources'; 2 + 3 + /** 4 + * get nice historical counts from constellation 5 + * 6 + * constellation's api still uses separated collection/path sources, and 7 + * likes should be distinct where everything else is record counts. 8 + * 9 + * constellation still can only specify one link source per request or /all 10 + * 11 + * handles stuff like that 12 + **/ 13 + export async function getPostStats( 14 + atUri: string, 15 + endpoint: string = 'https://constellation.microcosm.blue' 16 + ) { 17 + const url = new URL('/links/all', endpoint); 18 + url.searchParams.set('target', atUri); 19 + const res = await fetch(url, { signal: AbortSignal.timeout(4000) }); 20 + if (!res.ok) throw new Error(res); 21 + const { links } = await res.json(); 22 + 23 + const niceLinks = {}; 24 + 25 + for (const [collection, paths] of Object.entries(links)) { 26 + for (const [oldStylePath, counts] of Object.entries(paths)) { 27 + const newStylePath = `${collection}:${oldStylePath.slice(1)}`; 28 + const name = linkSources[newStylePath]; 29 + if (!name) continue; // perils of constellation's (soon-deprecated) /all 30 + if (name === 'like') { 31 + niceLinks[name] = counts.distinct_dids; 32 + } else { 33 + niceLinks[name] = counts.records; 34 + } 35 + } 36 + } 37 + 38 + return niceLinks; 39 + }
+73
live-embed/src/getPost.ts
··· 1 + // heyyyyyy if you're reading this: 2 + // 3 + // it's all copy-pasta from the atproto notifications demo, pasted over from 4 + // previous experimental stuff. 5 + // 6 + // all to say it probably needs some attention and cleanup, in particular with 7 + // handling failures. i'll get back to it hopefully soon. 8 + 9 + import { Client, CredentialManager, ok, simpleFetchHandler } from '@atcute/client'; 10 + import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'; 11 + 12 + const docResolver = new CompositeDidDocumentResolver({ 13 + methods: { 14 + plc: new PlcDidDocumentResolver(), 15 + web: new WebDidDocumentResolver(), 16 + }, 17 + }); 18 + 19 + async function resolve_did(did) { 20 + return await docResolver.resolve(did); 21 + } 22 + 23 + function pds({ service }) { 24 + if (!service) { 25 + throw new Error('missing service from identity doc'); 26 + } 27 + const { serviceEndpoint } = service[0]; 28 + if (!serviceEndpoint) { 29 + throw new Error('missing serviceEndpoint from identity service array'); 30 + } 31 + return serviceEndpoint; 32 + } 33 + 34 + 35 + async function get_pds_record(endpoint, did, collection, rkey) { 36 + const handler = simpleFetchHandler({ service: endpoint }); 37 + const rpc = new Client({ handler }); 38 + const { ok, data } = await rpc.get('com.atproto.repo.getRecord', { 39 + params: { repo: did, collection, rkey }, 40 + }); 41 + if (!ok) throw new Error('fetching pds record failed'); 42 + return data; 43 + } 44 + 45 + function parse_at_uri(uri) { 46 + let collection, rkey; 47 + if (!uri.startsWith('at://')) { 48 + throw new Error('invalid at-uri: did not start with "at://"'); 49 + } 50 + let remaining = uri.slice('at://'.length); // remove the at:// prefix 51 + remaining = remaining.split('#')[0]; // hash is valid in at-uri but we don't handle them 52 + remaining = remaining.split('?')[0]; // query is valid in at-uri but we don't handle it 53 + const segments = remaining.split('/'); 54 + if (segments.length === 0) { 55 + throw new Error('invalid at-uri: could not find did after "at://"'); 56 + } 57 + const did = segments[0]; 58 + if (segments.length > 1) { 59 + collection = segments[1]; 60 + } 61 + if (segments.length > 2) { 62 + rkey = segments.slice(2).join('/'); // hmm are slashes actually valid in rkey? 63 + } 64 + return { did, collection, rkey }; 65 + } 66 + 67 + export async function getAtUri(atUri) { 68 + const { did, collection, rkey } = parse_at_uri(atUri); 69 + const doc = await resolve_did(did); 70 + const endpoint = pds(doc); 71 + const { value } = await get_pds_record(endpoint, did, collection, rkey); 72 + return value; 73 + }
+73
live-embed/src/index.css
··· 1 + :root { 2 + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 + line-height: 1.5; 4 + font-weight: 400; 5 + 6 + color-scheme: light dark; 7 + color: rgba(255, 255, 255, 0.87); 8 + background-color: rgb(24, 30, 38); 9 + 10 + font-synthesis: none; 11 + text-rendering: optimizeLegibility; 12 + -webkit-font-smoothing: antialiased; 13 + -moz-osx-font-smoothing: grayscale; 14 + } 15 + 16 + a { 17 + font-weight: 500; 18 + color: #646cff; 19 + text-decoration: inherit; 20 + } 21 + a:hover { 22 + color: #535bf2; 23 + } 24 + 25 + body { 26 + margin: 0; 27 + display: flex; 28 + place-items: center; 29 + min-width: 320px; 30 + min-height: 100vh; 31 + } 32 + 33 + h1 { 34 + font-size: 2em; 35 + line-height: 1.1; 36 + margin: 0; 37 + } 38 + 39 + h2 { 40 + margin-top: 2em; 41 + } 42 + 43 + button { 44 + border-radius: 8px; 45 + border: 1px solid transparent; 46 + padding: 0.6em 1.2em; 47 + font-size: 1em; 48 + font-weight: 500; 49 + font-family: inherit; 50 + background-color: #1a1a1a; 51 + cursor: pointer; 52 + transition: border-color 0.25s; 53 + } 54 + button:hover { 55 + border-color: #646cff; 56 + } 57 + button:focus, 58 + button:focus-visible { 59 + outline: 4px auto -webkit-focus-ring-color; 60 + } 61 + 62 + @media (prefers-color-scheme: light) { 63 + :root { 64 + color: #213547; 65 + background-color: #ffffff; 66 + } 67 + a:hover { 68 + color: #747bff; 69 + } 70 + button { 71 + background-color: #f9f9f9; 72 + } 73 + }
+9
live-embed/src/linkSources.ts
··· 1 + const linkSources = { 2 + 'app.bsky.feed.like:subject.uri': 'like', 3 + 'app.bsky.feed.repost:subject.uri': 'repost', // actual repost 4 + 'app.bsky.feed.post:embed.record.uri': 'repost', // normal quote (grouped for count) 5 + 'app.bsky.feed.post:embed.record.record.uri': 'repost', // RecordWithMedia quote (grouped for count) 6 + 'app.bsky.feed.post:reply.root.uri': 'reply', // root: count all descendent replies 7 + }; 8 + 9 + export { linkSources as default };
+10
live-embed/src/main.tsx
··· 1 + import { StrictMode } from 'react' 2 + import { createRoot } from 'react-dom/client' 3 + import './index.css' 4 + import App from './App.tsx' 5 + 6 + createRoot(document.getElementById('root')!).render( 7 + <StrictMode> 8 + <App /> 9 + </StrictMode>, 10 + )
+85
live-embed/src/samplePosts.ts
··· 1 + import { getPostStats } from './constellation'; 2 + 3 + const SEKELETON_API = 'https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton'; 4 + const FEED = 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot'; 5 + const POLL_DELAY = 9000; 6 + const POST_LIMIT = 5; 7 + 8 + async function getFeed() { 9 + const url = new URL(SEKELETON_API); 10 + url.searchParams.append('feed', FEED); 11 + url.searchParams.append('limit', POST_LIMIT.toString()); 12 + const res = await fetch(url); 13 + const data = await res.json(); 14 + return data; 15 + } 16 + 17 + /** 18 + * fetch a pair of posts from discover and alternately replace the first/second 19 + * 20 + * TODO: check w constellation to prioritize popular posts 21 + **/ 22 + export function rotatingPair(onRotate: any) { 23 + let timer: number; 24 + let dying = false; 25 + const seen = new Set(); // TODO: mem leak, slowly 26 + let A: string | null = null; 27 + let B: string | null = null; 28 + let which: 'A' | 'B' = 'A'; 29 + 30 + async function next() { 31 + console.info('[sample posts: next]'); 32 + try { 33 + const { feed } = await getFeed(); 34 + if (dying) return; 35 + 36 + const withStats = await Promise.all(feed.map(async ({ post }) => { 37 + if (seen.has(post)) return { post, total: 0 }; 38 + let stats = {}; 39 + try { 40 + stats = await getPostStats(post); 41 + } catch (e) { 42 + console.warn('failed to get stats from constellation', e); 43 + } 44 + const total = Array.from(Object.values(stats)).reduce((a, b) => a + b, 0); 45 + return ({ post, total }) 46 + })) 47 + if (dying) return; 48 + 49 + // idk if sorting by most interactions yields more-interactive posts but eh 50 + withStats.sort(({ total: a }, { total: b }) => b - a); 51 + 52 + // special case: first load 53 + if (A === null && B === null) { 54 + if (withStats.length < 2) throw new Error('withStats returned fewer than two posts to start'); 55 + seen.add(A = withStats[0].post); 56 + seen.add(B = withStats[1].post); 57 + } else { 58 + for (const { post } of withStats) { 59 + if (seen.has(post)) { 60 + continue; 61 + } 62 + if (which === 'A') { 63 + seen.add(B = post); 64 + which = 'B'; 65 + } else { 66 + seen.add(A = post); 67 + which = 'A'; 68 + } 69 + break; 70 + } 71 + } 72 + onRotate([A, B]); 73 + } catch (e) { 74 + console.error('hmm, failed to get withStats', e); 75 + } 76 + timer = setTimeout(next, POLL_DELAY); 77 + } 78 + setTimeout(next); 79 + 80 + return () => { 81 + console.log('clearing'); 82 + clearTimeout(timer); 83 + dying = true; 84 + } 85 + }
+160
live-embed/src/spacedust.ts
··· 1 + import linkSources from './linkSources'; 2 + 3 + type SpacedustStatus = 'disconnected' | 'connecting' | 'connected'; 4 + 5 + type Linkrement = { // this name should send me to jail 6 + subject: String // at-uri 7 + source: String // link source 8 + }; 9 + type LinkHandler = (l: Linkrement) => void; 10 + 11 + /** 12 + * simple spacedust demo client 13 + * 14 + * only purpose for now is to serve this demo so it might contain hacks 15 + **/ 16 + export class Spacedust { 17 + #callback: LinkHandler; 18 + #endpoint: string; 19 + 20 + // #socket must be null when #status is 'disconnected' 21 + #socket: WebSocket | null = null; 22 + #status: SpacedustStatus = 'disconnected'; 23 + 24 + #subjects: string[]; 25 + #subjectsDirty: boolean = false; // in case we try to update while disconnected 26 + #sources: string[] = Object.keys(linkSources); // hard-coding for demo 27 + #eol: boolean = false; // flag: we should shut down 28 + 29 + constructor( 30 + onLink: LinkHandler, 31 + endpoint: string = 'https://spacedust.microcosm.blue', 32 + subjects: string[] = [], 33 + ) { 34 + this.#callback = onLink; 35 + this.#endpoint = endpoint; 36 + this.#subjects = subjects; 37 + this.#connect(); 38 + } 39 + 40 + async #connect(reconnecting: boolean = false) { 41 + this.#status = 'connecting'; 42 + 43 + if (reconnecting) { 44 + const wait = Math.round(1000 + (Math.random() * 1800)); 45 + console.info(`waiting ${(wait / 1000).toFixed(1)}s to reconnect...`); 46 + await new Promise(r => setTimeout(r, wait)); 47 + } 48 + if (this.#eol) return this.close(); 49 + 50 + // up to date as of this connection init 51 + this.#subjectsDirty = false; 52 + 53 + if (this.#subjects.length === 0) { 54 + console.info('no subjects, not connecting spacedust to avoid getting firehosed'); 55 + this.#status = 'disconnected'; 56 + return; 57 + } 58 + 59 + const url = new URL('/subscribe', this.#endpoint); 60 + url.searchParams.set('instant', 'true'); 61 + 62 + for (const source of this.#sources) { 63 + url.searchParams.append('wantedSources', source); 64 + } 65 + 66 + // note: here we put all subjects in the url 67 + // that's fine since we only have a few for this demo 68 + // but spacedust accepts up to 50,000 subjects! more than fit in a url-- you 69 + // have to send a subscriber sourced message in that case after reconnect. 70 + for (const subject of this.#subjects) { 71 + url.searchParams.append('wantedSubjects', subject); 72 + } 73 + 74 + this.#socket = new WebSocket(url); 75 + 76 + this.#socket.onopen = () => { 77 + console.info('spacedust connected.'); 78 + if (this.#eol) return this.close(); 79 + this.#status = 'connected'; 80 + // in case the subjects were changed while connecting 81 + if (this.#subjectsDirty) { 82 + this.setSubjects(this.#subjects); 83 + } 84 + }; 85 + 86 + this.#socket.onmessage = message => { 87 + if (this.#eol) return this.close(); 88 + this.#handleMessage(message); 89 + }; 90 + 91 + this.#socket.onerror = err => { 92 + console.warn('spacedust socket errored. reconnecting...', err); 93 + this.#status = 'disconnected'; 94 + this.#connect(true); 95 + }; 96 + 97 + this.#socket.onclose = () => { 98 + if (this.#eol) { 99 + console.info('spacedust socket closed and we\'re EOL, not restarting'); 100 + return; 101 + } 102 + console.info('spacedust socket closed. restarting...'); 103 + this.#status = 'disconnected'; 104 + this.#connect(true); 105 + }; 106 + } 107 + 108 + #handleMessage(m: MessageEvent) { 109 + if (this.#eol) return; 110 + const data = JSON.parse(m.data); 111 + if (data.kind !== "link" || data.link.operation !== "create") { 112 + console.info('ignoring non-link-create event', data); 113 + return; 114 + } 115 + const { link: { subject, source } } = data; 116 + this.#callback({ subject, source }); 117 + } 118 + 119 + setSubjects(newSubjects: string[]) { 120 + this.#subjects = newSubjects; 121 + 122 + if (this.#subjects.length === 0) { 123 + // no subjects specified: just disconnect (would get firehose from spacedust) 124 + this.#socket?.close(); 125 + // closing should trigger the .onclose handler to take it from here 126 + return; 127 + } 128 + 129 + if (this.#status === 'disconnected') { 130 + console.info('spacedust currently disconnected; connecting for updated subjects'); 131 + this.#subjectsDirty = true; 132 + this.#connect(); 133 + return; 134 + } else if (this.#status === 'connecting') { 135 + console.info('spacedust currently connecting; just flagging subjects as dirty'); 136 + this.#subjectsDirty = true; // on connect it should automatically update 137 + return; 138 + } 139 + 140 + if (!this.#socket) { 141 + throw new Error(`spacedust status is "${this.#status}" but the socket is null -- a bug?`); 142 + } 143 + 144 + this.#socket.send(JSON.stringify({ 145 + type: 'options_update', 146 + payload: { 147 + wantedSources: this.#sources, 148 + wantedSubjects: this.#subjects, 149 + }, 150 + })); 151 + 152 + this.#subjectsDirty = false; 153 + } 154 + 155 + close() { 156 + this.#eol = true; 157 + this.#socket?.close(); 158 + } 159 + 160 + }
+1
live-embed/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" />
+27
live-embed/tsconfig.app.json
··· 1 + { 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 + "target": "ES2022", 5 + "useDefineForClassFields": true, 6 + "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 + "module": "ESNext", 8 + "skipLibCheck": true, 9 + 10 + /* Bundler mode */ 11 + "moduleResolution": "bundler", 12 + "allowImportingTsExtensions": true, 13 + "verbatimModuleSyntax": true, 14 + "moduleDetection": "force", 15 + "noEmit": true, 16 + "jsx": "react-jsx", 17 + 18 + /* Linting */ 19 + "strict": true, 20 + "noUnusedLocals": true, 21 + "noUnusedParameters": true, 22 + "erasableSyntaxOnly": true, 23 + "noFallthroughCasesInSwitch": true, 24 + "noUncheckedSideEffectImports": true 25 + }, 26 + "include": ["src"] 27 + }
+7
live-embed/tsconfig.json
··· 1 + { 2 + "files": [], 3 + "references": [ 4 + { "path": "./tsconfig.app.json" }, 5 + { "path": "./tsconfig.node.json" } 6 + ] 7 + }
+25
live-embed/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 + "target": "ES2023", 5 + "lib": ["ES2023"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 + 9 + /* Bundler mode */ 10 + "moduleResolution": "bundler", 11 + "allowImportingTsExtensions": true, 12 + "verbatimModuleSyntax": true, 13 + "moduleDetection": "force", 14 + "noEmit": true, 15 + 16 + /* Linting */ 17 + "strict": true, 18 + "noUnusedLocals": true, 19 + "noUnusedParameters": true, 20 + "erasableSyntaxOnly": true, 21 + "noFallthroughCasesInSwitch": true, 22 + "noUncheckedSideEffectImports": true 23 + }, 24 + "include": ["vite.config.ts"] 25 + }
+7
live-embed/vite.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + import react from '@vitejs/plugin-react' 3 + 4 + // https://vite.dev/config/ 5 + export default defineConfig({ 6 + plugins: [react()], 7 + })
+51 -1
server/api.js
··· 158 158 }; 159 159 160 160 const handleTopSecret = async (db, user, req, res) => { 161 - console.log('ts'); 162 161 // TODO: succeed early if they're already in? 163 162 const body = await getRequesBody(req); 164 163 const { secret_password } = JSON.parse(body); ··· 171 170 return forbidden(res); 172 171 } 173 172 }; 173 + 174 + const handleGetGlobalNotifySettings = async (db, user, res) => { 175 + const settings = db.getNotifyAccountGlobals(user.did); 176 + return ok(res, settings); 177 + }; 178 + 179 + const handleSetGlobalNotifySettings = async (db, user, req, res) => { 180 + const body = await getRequesBody(req); 181 + const { notify_enabled, notify_self } = JSON.parse(body); 182 + db.setNotifyAccountGlobals(user.did, { notify_enabled, notify_self }); 183 + return gotIt(res); 184 + }; 185 + 186 + const handleGetNotificationFilter = async (db, user, searchParams, res) => { 187 + const selector = searchParams.get('selector'); 188 + if (!selector) return badRequest(res, '"selector" required in search query'); 189 + 190 + const selection = searchParams.get('selection'); 191 + if (!selection) return badRequest(res, '"selection" required in search query'); 192 + 193 + const { did } = user; 194 + 195 + const notify = db.getNotificationFilter(did, selector, selection) ?? null; 196 + return ok(res, { notify }); 197 + }; 198 + 199 + const handleSetNotificationFilter = async (db, user, req, res) => { 200 + const body = await getRequesBody(req); 201 + const { selector, selection, notify } = JSON.parse(body); 202 + const { did } = user; 203 + db.setNotificationFilter(did, selector, selection, notify); 204 + return ok(res, { notify }); 205 + }; 206 + 207 + /// admin stuff 174 208 175 209 const handleListSecrets = async (db, res) => { 176 210 const secrets = db.getSecrets(); ··· 265 299 if (method === 'POST' && pathname === '/super-top-secret-access') { 266 300 if (!user) return unauthorized(res); 267 301 return handleTopSecret(db, user, req, res); 302 + } 303 + if (method === 'GET' && pathname === '/global-notify') { 304 + if (!user) return unauthorized(res); 305 + return handleGetGlobalNotifySettings(db, user, res); 306 + } 307 + if (method === 'POST' && pathname === '/global-notify') { 308 + if (!user) return unauthorized(res); 309 + return handleSetGlobalNotifySettings(db, user, req, res); 310 + } 311 + if (method === 'GET' && pathname === '/notification-filter') { 312 + if (!user) return unauthorized(res); 313 + return handleGetNotificationFilter(db, user, searchParams, res); 314 + } 315 + if (method === 'POST' && pathname === '/notification-filter') { 316 + if (!user) return unauthorized(res); 317 + return handleSetNotificationFilter(db, user, req, res); 268 318 } 269 319 270 320 // non-public access required
+86
server/db.js
··· 2 2 import Database from 'better-sqlite3'; 3 3 4 4 const SUBS_PER_ACCOUNT_LIMIT = 5; 5 + const SECONDARY_FILTERS_LIMIT = 100; 6 + 5 7 const SCHEMA_FNAME = './schema.sql'; 6 8 7 9 export class DB { ··· 16 18 #stmt_delete_push_sub; 17 19 #stmt_get_push_info; 18 20 #stmt_set_role; 21 + #stmt_get_notify_account_globals; 22 + #stmt_set_notify_account_globals; 23 + #stmt_set_notification_filter; 24 + #stmt_get_notification_filter; 25 + #stmt_count_notification_filters; 26 + #stmt_rm_notification_filter; 27 + 19 28 #stmt_admin_add_secret; 20 29 #stmt_admin_expire_secret; 21 30 #stmt_admin_get_secrets; ··· 112 121 and :secret_password in (select password 113 122 from top_secret_passwords)`); 114 123 124 + this.#stmt_get_notify_account_globals = db.prepare( 125 + `select notify_enabled, 126 + notify_self 127 + from accounts 128 + where did = :did`); 129 + 130 + this.#stmt_set_notify_account_globals = db.prepare( 131 + `update accounts 132 + set notify_enabled = :notify_enabled, 133 + notify_self = :notify_self 134 + where did = :did`); 135 + 136 + this.#stmt_set_notification_filter = db.prepare( 137 + `insert into notification_filters (account_did, selector, selection, notify) 138 + values (:did, :selector, :selection, :notify) 139 + on conflict do update 140 + set notify = excluded.notify`); 141 + 142 + this.#stmt_get_notification_filter = db.prepare( 143 + `select notify 144 + from notification_filters 145 + where account_did = :did 146 + and selector = :selector 147 + and selection = :selection`); 148 + 149 + this.#stmt_count_notification_filters = db.prepare( 150 + `select count(*) as n 151 + from notification_filters 152 + where account_did = :did`); 153 + 154 + this.#stmt_rm_notification_filter = db.prepare( 155 + `delete from notification_filters 156 + where account_did = :did 157 + and selector = :selector 158 + and selection = :selection`); 159 + 160 + 115 161 this.#stmt_admin_add_secret = db.prepare( 116 162 `insert into top_secret_passwords (password) 117 163 values (?)`); ··· 204 250 let res = this.#stmt_set_role.run(params); 205 251 return res.changes > 0; 206 252 } 253 + 254 + getNotifyAccountGlobals(did) { 255 + return this.#stmt_get_notify_account_globals.get({ did }); 256 + } 257 + 258 + setNotifyAccountGlobals(did, globals) { 259 + this.#transactionally(() => { 260 + const update = this.getNotifyAccountGlobals(did); 261 + if (globals.notify_enabled !== undefined) update.notify_enabled = +globals.notify_enabled; 262 + if (globals.notify_self !== undefined) update.notify_self = +globals.notify_self; 263 + update.did = did; 264 + this.#stmt_set_notify_account_globals.run(update); 265 + }); 266 + } 267 + 268 + getNotificationFilter(did, selector, selection) { 269 + const res = this.#stmt_get_notification_filter.get({ did, selector, selection }); 270 + const dbNotify = res?.notify; 271 + if (dbNotify === 1) return true; 272 + else if (dbNotify === 0) return false; 273 + else return null; 274 + } 275 + 276 + setNotificationFilter(did, selector, selection, notify) { 277 + if (notify === null) { 278 + this.#stmt_rm_notification_filter.run({ did, selector, selection }); 279 + } else { 280 + this.#transactionally(() => { 281 + const { n } = this.#stmt_count_notification_filters.get({ did }); 282 + if (n >= SECONDARY_FILTERS_LIMIT) { 283 + throw new Error('max filters set for account'); 284 + } 285 + let dbNotify = null; 286 + if (notify === true) dbNotify = 1; 287 + else if (notify === false) dbNotify = 0; 288 + this.#stmt_set_notification_filter.run({ did, selector, selection, notify: dbNotify }); 289 + }); 290 + } 291 + } 292 + 207 293 208 294 addTopSecret(secretPassword) { 209 295 this.#stmt_admin_add_secret.run(secretPassword);
+20
server/index.js
··· 3 3 import { createRemoteJWKSet } from 'jose'; 4 4 import fs from 'node:fs'; 5 5 import { randomBytes } from 'node:crypto'; 6 + import https from 'node:https'; 6 7 import webpush from 'web-push'; 7 8 import { DB } from './db.js'; 8 9 import { connectSpacedust } from './notifications.js'; ··· 26 27 return secrets; 27 28 } 28 29 30 + function startHealthcheckPing(endpoint) { 31 + const next = () => setTimeout(() => startHealthcheckPing(endpoint), 90 * 1000); 32 + 33 + https 34 + .get(endpoint, res => { 35 + if (res.statusCode !== 200) console.warn('non-200 health check response', res.statusCode); 36 + res 37 + .on('data', () => {}) 38 + .on('end', next); 39 + }) 40 + .on('error', err => { 41 + console.warn('healthcheck request errored', err); 42 + next(); 43 + }); 44 + } 45 + 29 46 const main = env => { 30 47 if (!env.ADMIN_DID) throw new Error('ADMIN_DID is required to run'); 31 48 const adminDid = env.ADMIN_DID; ··· 53 70 const port = parseInt(env.PORT ?? 8000, 10); 54 71 55 72 const allowedOrigin = env.ALLOWED_ORIGIN ?? 'http://127.0.0.1:5173'; 73 + 74 + if (env.HEALTHCHECK) startHealthcheckPing(env.HEALTHCHECK); 75 + else console.warn('no HEALTHCHECK in env, not sending healthcheck pings'); 56 76 57 77 server(secrets, jwks, allowedOrigin, whoamiHost, db, updateSubs, push, adminDid).listen( 58 78 port,
+60 -9
server/notifications.js
··· 1 - import lexicons from 'lexicons'; 1 + import { default as lexicons, getBits } from 'lexicons'; 2 2 import psl from 'psl'; 3 3 import webpush from 'web-push'; 4 4 import WebSocket from 'ws'; ··· 69 69 parts.reverse(); 70 70 parts = parts.join('.'); 71 71 72 - // const unreversed = parts.toReversed().join('.'); 73 - 74 72 const app = psl.parse(parts)?.domain ?? 'unknown'; 75 73 76 74 let appPrefix = app.split('.'); ··· 84 82 } 85 83 }; 86 84 85 + const extractUriDid = at_uri => { 86 + if (!at_uri.startsWith('at://')) { 87 + console.warn(`ignoring non-at-uri: ${at_uri}`); 88 + return null; 89 + } 90 + const [id, ..._] = at_uri.slice('at://'.length).split('/'); 91 + if (!id) { 92 + console.warn(`ignoring at-uri with missing id segment: ${at_uri}`); 93 + return null; 94 + } 95 + if (id.startsWith('@')) { 96 + console.warn(`ignoring @handle at-uri: ${at_uri}`); 97 + return null; 98 + } 99 + if (!id.startsWith('did:')) { 100 + console.warn(`ignoring non-did at-uri: ${at_uri}`); 101 + return null; 102 + } 103 + return id; 104 + }; 105 + 87 106 const handleDust = db => async event => { 88 107 console.log('got', event.data); 89 108 let data; ··· 100 119 } 101 120 const timestamp = +new Date(); 102 121 103 - let did; 104 - if (subject.startsWith('did:')) did = subject; 105 - else if (subject.startsWith('at://')) { 106 - const [id, ..._] = subject.slice('at://'.length).split('/'); 107 - if (id.startsWith('did:')) did = id; 108 - } 122 + const did = subject.startsWith('did:') ? subject : extractUriDid(subject); 109 123 if (!did) { 110 124 console.warn(`ignoring link with non-DID subject: ${subject}`) 111 125 return; 126 + } 127 + 128 + // this works for now since only the account owner is assumed to be a notification target 129 + // but for "replies on post" etc that won't hold 130 + const { notify_enabled, notify_self } = db.getNotifyAccountGlobals(did); 131 + if (!notify_enabled) { 132 + console.warn('dropping notification for global not-enabled setting'); 133 + return; 134 + } 135 + if (!notify_self) { 136 + const source_did = extractUriDid(source_record); 137 + if (!source_did) { 138 + console.warn(`ignoring link with non-DID source_record: ${source_record}`) 139 + return; 140 + } 141 + if (source_did === did) { 142 + console.warn(`ignoring self-notification`); 143 + return; 144 + } 145 + } 146 + 147 + // like above, this over-assumes that did is the only recipient we could care about for now 148 + const { app, group } = getBits(source); 149 + for (const [selector, selection] of [ 150 + ['source', source], 151 + ['group', group], 152 + ['app', app], 153 + ]) { 154 + const notify = db.getNotificationFilter(did, selector, selection); 155 + if (notify === true) { 156 + console.info(`explicitly allowing notification by filter for ${selector}=${selection}`); 157 + break; 158 + }; 159 + if (notify === false) { 160 + console.warn(`ignoring filtered notification for ${selector}=${selection}`); 161 + return; 162 + } 112 163 } 113 164 114 165 const subs = db.getSubsByDid(did);
+15
server/schema.sql
··· 3 3 first_seen text not null default CURRENT_TIMESTAMP, 4 4 role text null, 5 5 secret_password text null, 6 + notify_enabled integer not null default false, 7 + notify_self integer not null default false, 6 8 7 9 check(did like 'did:%') 8 10 ) strict; ··· 28 30 29 31 check(length(password) >= 3) 30 32 ) strict; 33 + 34 + create table if not exists notification_filters ( 35 + account_did text not null, 36 + selector text not null, 37 + selection text not null, 38 + notify integer null, 39 + 40 + primary key(account_did, selector, selection), 41 + check(selector in ('all', 'app', 'group', 'source')), 42 + 43 + foreign key(account_did) references accounts(did) 44 + on delete cascade on update cascade 45 + ) strict;