A tool for people curious about the React Server Components protocol rscexplorer.dev/
rsc react

add eslint and prettier

+4275 -2032
+5
.husky/pre-commit
··· 1 + echo "Running lint-staged (Prettier) ..." 2 + npx lint-staged 3 + 4 + echo "Running eslint ..." 5 + npm run lint
+11 -11
README.md
··· 12 12 13 13 ## Examples 14 14 15 - * [Hello World](https://rscexplorer.dev/?s=hello) 16 - * [Async Component](https://rscexplorer.dev/?s=async) 17 - * [Counter](https://rscexplorer.dev/?s=counter) 18 - * [Form Action](https://rscexplorer.dev/?s=form) 19 - * [Pagination](https://rscexplorer.dev/?s=pagination) 20 - * [Router Refresh](https://rscexplorer.dev/?s=refresh) 21 - * [Error Handling](https://rscexplorer.dev/?s=errors) 22 - * [Client Reference](https://rscexplorer.dev/?s=clientref) 23 - * [Bound Actions](https://rscexplorer.dev/?s=bound) 24 - * [Kitchen Sink](https://rscexplorer.dev/?s=kitchensink) 25 - * [CVE-2025-55182](https://rscexplorer.dev/?s=cve) 15 + - [Hello World](https://rscexplorer.dev/?s=hello) 16 + - [Async Component](https://rscexplorer.dev/?s=async) 17 + - [Counter](https://rscexplorer.dev/?s=counter) 18 + - [Form Action](https://rscexplorer.dev/?s=form) 19 + - [Pagination](https://rscexplorer.dev/?s=pagination) 20 + - [Router Refresh](https://rscexplorer.dev/?s=refresh) 21 + - [Error Handling](https://rscexplorer.dev/?s=errors) 22 + - [Client Reference](https://rscexplorer.dev/?s=clientref) 23 + - [Bound Actions](https://rscexplorer.dev/?s=bound) 24 + - [Kitchen Sink](https://rscexplorer.dev/?s=kitchensink) 25 + - [CVE-2025-55182](https://rscexplorer.dev/?s=cve) 26 26 27 27 ## Embedding 28 28
+50 -46
embed.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 - <title>RSC Explorer Embed</title> 7 - <style> 8 - * { 9 - box-sizing: border-box; 10 - } 11 - :root { 12 - --bg: #1a1a1a; 13 - --surface: #242424; 14 - --border: #333; 15 - --text: #a0a0a0; 16 - --text-dim: #666; 17 - --text-bright: #e0e0e0; 18 - --font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', Menlo, monospace; 19 - } 20 - html, body { 21 - margin: 0; 22 - padding: 0; 23 - height: 100%; 24 - overflow: hidden; 25 - position: fixed; 26 - width: 100%; 27 - top: 0; 28 - left: 0; 29 - } 30 - body { 31 - font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif; 32 - background: var(--bg); 33 - color: var(--text); 34 - border-radius: 8px; 35 - } 36 - #embed-root { 37 - display: flex; 38 - flex-direction: column; 39 - height: 100%; 40 - overflow: hidden; 41 - } 42 - </style> 43 - </head> 44 - <body> 45 - <div id="embed-root"></div> 46 - <script type="module" src="/src/client/embed.jsx"></script> 47 - </body> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta 6 + name="viewport" 7 + content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" 8 + /> 9 + <title>RSC Explorer Embed</title> 10 + <style> 11 + * { 12 + box-sizing: border-box; 13 + } 14 + :root { 15 + --bg: #1a1a1a; 16 + --surface: #242424; 17 + --border: #333; 18 + --text: #a0a0a0; 19 + --text-dim: #666; 20 + --text-bright: #e0e0e0; 21 + --font-mono: "SF Mono", "Fira Code", "JetBrains Mono", Menlo, monospace; 22 + } 23 + html, 24 + body { 25 + margin: 0; 26 + padding: 0; 27 + height: 100%; 28 + overflow: hidden; 29 + position: fixed; 30 + width: 100%; 31 + top: 0; 32 + left: 0; 33 + } 34 + body { 35 + font-family: -apple-system, BlinkMacSystemFont, "Inter", sans-serif; 36 + background: var(--bg); 37 + color: var(--text); 38 + border-radius: 8px; 39 + } 40 + #embed-root { 41 + display: flex; 42 + flex-direction: column; 43 + height: 100%; 44 + overflow: hidden; 45 + } 46 + </style> 47 + </head> 48 + <body> 49 + <div id="embed-root"></div> 50 + <script type="module" src="/src/client/embed.jsx"></script> 51 + </body> 48 52 </html>
+16
eslint.config.js
··· 1 + // @ts-check 2 + 3 + import { defineConfig } from "eslint/config"; 4 + import reactHooks from "eslint-plugin-react-hooks"; 5 + import tseslint from "typescript-eslint"; 6 + 7 + export default defineConfig([ 8 + { 9 + ignores: ["node_modules/", "dist/"], 10 + }, 11 + { 12 + files: ["**/*.ts", "**/*.js", "**/*.tsx", "**/*.jsx"], 13 + }, 14 + reactHooks.configs.flat.recommended, 15 + tseslint.configs.base, 16 + ]);
+1183 -1123
index.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 - <title>RSC Explorer</title> 7 - <style> 8 - * { 9 - box-sizing: border-box; 10 - } 11 - :root { 12 - --bg: #1a1a1a; 13 - --surface: #242424; 14 - --border: #333; 15 - --text: #a0a0a0; 16 - --text-dim: #666; 17 - --text-bright: #e0e0e0; 18 - --font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', Menlo, monospace; 19 - } 20 - html, body { 21 - margin: 0; 22 - padding: 0; 23 - height: 100%; 24 - overflow: hidden; 25 - position: fixed; 26 - width: 100%; 27 - top: 0; 28 - left: 0; 29 - } 30 - body { 31 - font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif; 32 - background: var(--bg); 33 - color: var(--text); 34 - } 35 - header { 36 - height: 44px; 37 - padding: 0 16px; 38 - display: flex; 39 - align-items: center; 40 - gap: 16px; 41 - border-bottom: 1px solid var(--border); 42 - background: var(--surface); 43 - flex-shrink: 0; 44 - position: relative; 45 - z-index: 100; 46 - } 47 - h1 { 48 - margin: 0; 49 - font-size: 13px; 50 - font-weight: 600; 51 - color: var(--text); 52 - } 53 - .example-select-wrapper { 54 - display: flex; 55 - align-items: center; 56 - gap: 10px; 57 - padding-left: 16px; 58 - border-left: 1px solid var(--border); 59 - } 60 - .example-select-wrapper label { 61 - font-size: 11px; 62 - color: var(--text-dim); 63 - text-transform: uppercase; 64 - letter-spacing: 0.5px; 65 - } 66 - header select { 67 - -webkit-appearance: none; 68 - appearance: none; 69 - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center, linear-gradient(to bottom, #333, #2a2a2a); 70 - border: 1px solid #555; 71 - color: #fff; 72 - padding: 5px 28px 5px 10px; 73 - border-radius: 4px; 74 - font-size: 13px; 75 - font-weight: 500; 76 - cursor: pointer; 77 - min-width: 150px; 78 - box-shadow: 0 1px 2px rgba(0,0,0,0.2); 79 - } 80 - header select:hover { 81 - border-color: #666; 82 - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23bbb' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center, linear-gradient(to bottom, #3a3a3a, #333); 83 - } 84 - header select:focus { 85 - outline: none; 86 - border-color: #ffd54f; 87 - } 88 - header select option { 89 - background: #2a2a2a; 90 - color: #e0e0e0; 91 - padding: 8px; 92 - font-size: 13px; 93 - } 94 - header select option:hover, 95 - header select option:checked { 96 - background: #3a3a3a; 97 - color: #ffd54f; 98 - } 99 - header .save-btn { 100 - background: var(--surface); 101 - border: 1px solid var(--border); 102 - color: var(--text); 103 - padding: 5px 8px; 104 - border-radius: 4px; 105 - cursor: pointer; 106 - display: flex; 107 - align-items: center; 108 - justify-content: center; 109 - } 110 - header .save-btn:hover:not(:disabled) { 111 - border-color: #444; 112 - background: #2a2a2a; 113 - } 114 - header .save-btn:disabled { 115 - opacity: 0.4; 116 - cursor: not-allowed; 117 - } 118 - header .embed-btn { 119 - background: var(--surface); 120 - border: 1px solid var(--border); 121 - color: var(--text); 122 - padding: 5px 8px; 123 - border-radius: 4px; 124 - cursor: pointer; 125 - display: flex; 126 - align-items: center; 127 - justify-content: center; 128 - } 129 - header .embed-btn:hover:not(:disabled) { 130 - border-color: #444; 131 - background: #2a2a2a; 132 - } 133 - header .embed-btn:disabled { 134 - opacity: 0.4; 135 - cursor: not-allowed; 136 - } 137 - /* Modal */ 138 - .modal-overlay { 139 - position: fixed; 140 - inset: 0; 141 - background: rgba(0, 0, 0, 0.7); 142 - display: flex; 143 - align-items: center; 144 - justify-content: center; 145 - z-index: 1000; 146 - } 147 - .modal { 148 - background: var(--surface); 149 - border: 1px solid var(--border); 150 - border-radius: 8px; 151 - width: 90%; 152 - max-width: 600px; 153 - max-height: 80vh; 154 - display: flex; 155 - flex-direction: column; 156 - } 157 - .modal-header { 158 - display: flex; 159 - align-items: center; 160 - justify-content: space-between; 161 - padding: 16px; 162 - border-bottom: 1px solid var(--border); 163 - } 164 - .modal-header h2 { 165 - margin: 0; 166 - font-size: 16px; 167 - font-weight: 600; 168 - color: var(--text-bright); 169 - } 170 - .modal-close { 171 - background: none; 172 - border: none; 173 - color: var(--text-dim); 174 - font-size: 24px; 175 - cursor: pointer; 176 - padding: 0; 177 - line-height: 1; 178 - } 179 - .modal-close:hover { 180 - color: var(--text-bright); 181 - } 182 - .modal-body { 183 - padding: 16px; 184 - overflow: auto; 185 - } 186 - .modal-body p { 187 - margin: 0 0 12px; 188 - font-size: 13px; 189 - color: var(--text); 190 - } 191 - .modal-body textarea { 192 - width: 100%; 193 - height: 250px; 194 - background: var(--bg); 195 - border: 1px solid var(--border); 196 - border-radius: 4px; 197 - color: var(--text); 198 - font-family: var(--font-mono); 199 - font-size: 12px; 200 - padding: 12px; 201 - resize: none; 202 - } 203 - .modal-body textarea:focus { 204 - outline: none; 205 - border-color: #555; 206 - } 207 - .modal-footer { 208 - padding: 16px; 209 - border-top: 1px solid var(--border); 210 - display: flex; 211 - justify-content: flex-end; 212 - } 213 - .copy-btn { 214 - background: #ffd54f; 215 - border: none; 216 - color: #000; 217 - padding: 8px 16px; 218 - border-radius: 4px; 219 - font-size: 13px; 220 - font-weight: 500; 221 - cursor: pointer; 222 - } 223 - .copy-btn:hover { 224 - background: #ffe566; 225 - } 226 - .header-spacer { 227 - flex: 1; 228 - } 229 - .build-switcher { 230 - display: flex; 231 - align-items: center; 232 - gap: 8px; 233 - } 234 - .build-switcher label { 235 - font-size: 11px; 236 - color: var(--text-dim); 237 - text-transform: uppercase; 238 - letter-spacing: 0.5px; 239 - } 240 - .build-switcher .mode-select { 241 - min-width: 70px; 242 - } 243 - .header-links { 244 - display: flex; 245 - align-items: center; 246 - gap: 8px; 247 - padding-right: 16px; 248 - border-right: 1px solid var(--border); 249 - } 250 - .github-link, 251 - .tangled-link { 252 - display: flex; 253 - align-items: center; 254 - justify-content: center; 255 - color: var(--text-dim); 256 - padding: 4px; 257 - border-radius: 4px; 258 - transition: color 0.15s; 259 - } 260 - .github-link:hover, 261 - .tangled-link:hover { 262 - color: var(--text-bright); 263 - } 264 - @media (max-width: 900px) { 265 - .header-links { 266 - display: none; 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta 6 + name="viewport" 7 + content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" 8 + /> 9 + <title>RSC Explorer</title> 10 + <style> 11 + * { 12 + box-sizing: border-box; 13 + } 14 + :root { 15 + --bg: #1a1a1a; 16 + --surface: #242424; 17 + --border: #333; 18 + --text: #a0a0a0; 19 + --text-dim: #666; 20 + --text-bright: #e0e0e0; 21 + --font-mono: "SF Mono", "Fira Code", "JetBrains Mono", Menlo, monospace; 22 + } 23 + html, 24 + body { 25 + margin: 0; 26 + padding: 0; 27 + height: 100%; 28 + overflow: hidden; 29 + position: fixed; 30 + width: 100%; 31 + top: 0; 32 + left: 0; 33 + } 34 + body { 35 + font-family: -apple-system, BlinkMacSystemFont, "Inter", sans-serif; 36 + background: var(--bg); 37 + color: var(--text); 267 38 } 268 - } 269 - @media (max-width: 768px) { 270 39 header { 271 - padding: 0 8px; 272 - gap: 4px; 40 + height: 44px; 41 + padding: 0 16px; 42 + display: flex; 43 + align-items: center; 44 + gap: 16px; 45 + border-bottom: 1px solid var(--border); 46 + background: var(--surface); 47 + flex-shrink: 0; 48 + position: relative; 49 + z-index: 100; 273 50 } 274 51 h1 { 275 - font-size: 11px; 276 - } 277 - .header-spacer { 278 - flex: 0; 279 - min-width: 0; 52 + margin: 0; 53 + font-size: 13px; 54 + font-weight: 600; 55 + color: var(--text); 280 56 } 281 57 .example-select-wrapper { 282 - padding-left: 6px; 283 - margin-left: 2px; 284 - gap: 4px; 58 + display: flex; 59 + align-items: center; 60 + gap: 10px; 61 + padding-left: 16px; 62 + border-left: 1px solid var(--border); 285 63 } 286 64 .example-select-wrapper label { 287 - display: none; 65 + font-size: 11px; 66 + color: var(--text-dim); 67 + text-transform: uppercase; 68 + letter-spacing: 0.5px; 288 69 } 289 - header select, 70 + header select { 71 + -webkit-appearance: none; 72 + appearance: none; 73 + background: 74 + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") 75 + no-repeat right 8px center, 76 + linear-gradient(to bottom, #333, #2a2a2a); 77 + border: 1px solid #555; 78 + color: #fff; 79 + padding: 5px 28px 5px 10px; 80 + border-radius: 4px; 81 + font-size: 13px; 82 + font-weight: 500; 83 + cursor: pointer; 84 + min-width: 150px; 85 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 86 + } 290 87 header select:hover { 291 - min-width: 0; 292 - width: auto; 293 - padding: 4px 20px 4px 6px; 294 - font-size: 16px; /* Prevents iOS zoom */ 295 - background-position: right 4px center; 88 + border-color: #666; 89 + background: 90 + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23bbb' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") 91 + no-repeat right 8px center, 92 + linear-gradient(to bottom, #3a3a3a, #333); 93 + } 94 + header select:focus { 95 + outline: none; 96 + border-color: #ffd54f; 97 + } 98 + header select option { 99 + background: #2a2a2a; 100 + color: #e0e0e0; 101 + padding: 8px; 102 + font-size: 13px; 103 + } 104 + header select option:hover, 105 + header select option:checked { 106 + background: #3a3a3a; 107 + color: #ffd54f; 108 + } 109 + header .save-btn { 110 + background: var(--surface); 111 + border: 1px solid var(--border); 112 + color: var(--text); 113 + padding: 5px 8px; 114 + border-radius: 4px; 115 + cursor: pointer; 116 + display: flex; 117 + align-items: center; 118 + justify-content: center; 119 + } 120 + header .save-btn:hover:not(:disabled) { 121 + border-color: #444; 122 + background: #2a2a2a; 123 + } 124 + header .save-btn:disabled { 125 + opacity: 0.4; 126 + cursor: not-allowed; 127 + } 128 + header .embed-btn { 129 + background: var(--surface); 130 + border: 1px solid var(--border); 131 + color: var(--text); 132 + padding: 5px 8px; 133 + border-radius: 4px; 134 + cursor: pointer; 135 + display: flex; 136 + align-items: center; 137 + justify-content: center; 138 + } 139 + header .embed-btn:hover:not(:disabled) { 140 + border-color: #444; 141 + background: #2a2a2a; 142 + } 143 + header .embed-btn:disabled { 144 + opacity: 0.4; 145 + cursor: not-allowed; 146 + } 147 + /* Modal */ 148 + .modal-overlay { 149 + position: fixed; 150 + inset: 0; 151 + background: rgba(0, 0, 0, 0.7); 152 + display: flex; 153 + align-items: center; 154 + justify-content: center; 155 + z-index: 1000; 156 + } 157 + .modal { 158 + background: var(--surface); 159 + border: 1px solid var(--border); 160 + border-radius: 8px; 161 + width: 90%; 162 + max-width: 600px; 163 + max-height: 80vh; 164 + display: flex; 165 + flex-direction: column; 166 + } 167 + .modal-header { 168 + display: flex; 169 + align-items: center; 170 + justify-content: space-between; 171 + padding: 16px; 172 + border-bottom: 1px solid var(--border); 173 + } 174 + .modal-header h2 { 175 + margin: 0; 176 + font-size: 16px; 177 + font-weight: 600; 178 + color: var(--text-bright); 179 + } 180 + .modal-close { 181 + background: none; 182 + border: none; 183 + color: var(--text-dim); 184 + font-size: 24px; 185 + cursor: pointer; 186 + padding: 0; 187 + line-height: 1; 188 + } 189 + .modal-close:hover { 190 + color: var(--text-bright); 191 + } 192 + .modal-body { 193 + padding: 16px; 194 + overflow: auto; 195 + } 196 + .modal-body p { 197 + margin: 0 0 12px; 198 + font-size: 13px; 199 + color: var(--text); 200 + } 201 + .modal-body textarea { 202 + width: 100%; 203 + height: 250px; 204 + background: var(--bg); 205 + border: 1px solid var(--border); 206 + border-radius: 4px; 207 + color: var(--text); 208 + font-family: var(--font-mono); 209 + font-size: 12px; 210 + padding: 12px; 211 + resize: none; 212 + } 213 + .modal-body textarea:focus { 214 + outline: none; 215 + border-color: #555; 216 + } 217 + .modal-footer { 218 + padding: 16px; 219 + border-top: 1px solid var(--border); 220 + display: flex; 221 + justify-content: flex-end; 222 + } 223 + .copy-btn { 224 + background: #ffd54f; 225 + border: none; 226 + color: #000; 227 + padding: 8px 16px; 228 + border-radius: 4px; 229 + font-size: 13px; 230 + font-weight: 500; 231 + cursor: pointer; 232 + } 233 + .copy-btn:hover { 234 + background: #ffe566; 235 + } 236 + .header-spacer { 237 + flex: 1; 296 238 } 297 239 .build-switcher { 298 - padding-left: 6px; 299 - gap: 4px; 300 - margin-left: auto; 240 + display: flex; 241 + align-items: center; 242 + gap: 8px; 301 243 } 302 244 .build-switcher label { 303 - display: none; 245 + font-size: 11px; 246 + color: var(--text-dim); 247 + text-transform: uppercase; 248 + letter-spacing: 0.5px; 249 + } 250 + .build-switcher .mode-select { 251 + min-width: 70px; 252 + } 253 + .header-links { 254 + display: flex; 255 + align-items: center; 256 + gap: 8px; 257 + padding-right: 16px; 258 + border-right: 1px solid var(--border); 259 + } 260 + .github-link, 261 + .tangled-link { 262 + display: flex; 263 + align-items: center; 264 + justify-content: center; 265 + color: var(--text-dim); 266 + padding: 4px; 267 + border-radius: 4px; 268 + transition: color 0.15s; 269 + } 270 + .github-link:hover, 271 + .tangled-link:hover { 272 + color: var(--text-bright); 273 + } 274 + @media (max-width: 900px) { 275 + .header-links { 276 + display: none; 277 + } 278 + } 279 + @media (max-width: 768px) { 280 + header { 281 + padding: 0 8px; 282 + gap: 4px; 283 + } 284 + h1 { 285 + font-size: 11px; 286 + } 287 + .header-spacer { 288 + flex: 0; 289 + min-width: 0; 290 + } 291 + .example-select-wrapper { 292 + padding-left: 6px; 293 + margin-left: 2px; 294 + gap: 4px; 295 + } 296 + .example-select-wrapper label { 297 + display: none; 298 + } 299 + header select, 300 + header select:hover { 301 + min-width: 0; 302 + width: auto; 303 + padding: 4px 20px 4px 6px; 304 + font-size: 16px; /* Prevents iOS zoom */ 305 + background-position: right 4px center; 306 + } 307 + .build-switcher { 308 + padding-left: 6px; 309 + gap: 4px; 310 + margin-left: auto; 311 + } 312 + .build-switcher label { 313 + display: none; 314 + } 315 + .build-switcher select { 316 + min-width: 0; 317 + } 318 + main { 319 + grid-template-columns: 100%; 320 + grid-template-rows: 1fr 1fr 1fr 1fr; 321 + } 322 + .pane:nth-child(1), 323 + .pane:nth-child(3) { 324 + border-right: none; 325 + } 326 + .pane { 327 + border-bottom: 1px solid var(--border); 328 + } 329 + /* Prevent iOS zoom on all form elements */ 330 + input, 331 + textarea, 332 + select { 333 + font-size: 16px !important; 334 + } 335 + .raw-input-payload { 336 + font-size: 16px !important; 337 + } 338 + /* Playback controls responsive */ 339 + .playback-container { 340 + padding: 6px 8px; 341 + gap: 6px; 342 + } 343 + .playback-controls { 344 + gap: 2px; 345 + } 346 + .control-btn { 347 + width: 26px; 348 + height: 26px; 349 + } 350 + .step-info { 351 + min-width: 50px; 352 + font-size: 10px; 353 + } 304 354 } 305 - .build-switcher select { 306 - min-width: 0; 355 + @media (max-width: 480px) { 356 + header { 357 + padding: 0 6px; 358 + gap: 3px; 359 + } 360 + h1 { 361 + font-size: 10px; 362 + } 363 + header select { 364 + padding: 3px 18px 3px 5px; 365 + font-size: 16px; 366 + max-width: 80px; 367 + text-overflow: ellipsis; 368 + overflow: hidden; 369 + white-space: nowrap; 370 + } 371 + .example-select-wrapper select { 372 + max-width: 110px; 373 + } 374 + .example-select-wrapper { 375 + padding-left: 4px; 376 + margin-left: 0; 377 + border-left: none; 378 + } 379 + .build-switcher { 380 + padding-left: 4px; 381 + border-left: none; 382 + } 383 + /* Playback: hide slider and status, keep buttons compact */ 384 + .step-slider, 385 + .step-info { 386 + display: none; 387 + } 388 + .playback-container { 389 + padding: 4px 6px; 390 + gap: 4px; 391 + } 392 + .control-btn { 393 + width: 24px; 394 + height: 24px; 395 + } 396 + .control-btn svg { 397 + width: 14px; 398 + height: 14px; 399 + } 400 + } 401 + @media (max-width: 360px) { 402 + h1 { 403 + display: none; 404 + } 405 + header select { 406 + max-width: 75px; 407 + } 307 408 } 308 409 main { 309 - grid-template-columns: 100%; 310 - grid-template-rows: 1fr 1fr 1fr 1fr; 410 + flex: 1; 411 + min-height: 0; 412 + display: grid; 413 + grid-template-columns: 50% 50%; 414 + grid-template-rows: 50% 50%; 415 + overflow: hidden; 416 + } 417 + .pane { 418 + display: flex; 419 + flex-direction: column; 420 + overflow: hidden; 311 421 } 422 + /* Left column border */ 312 423 .pane:nth-child(1), 313 424 .pane:nth-child(3) { 314 - border-right: none; 425 + border-right: 1px solid var(--border); 315 426 } 316 - .pane { 427 + /* Top row border */ 428 + .pane:nth-child(1), 429 + .pane:nth-child(2) { 317 430 border-bottom: 1px solid var(--border); 318 431 } 319 - /* Prevent iOS zoom on all form elements */ 320 - input, textarea, select { 321 - font-size: 16px !important; 432 + .pane-header { 433 + padding: 6px 12px; 434 + font-size: 10px; 435 + text-transform: uppercase; 436 + letter-spacing: 1px; 437 + color: var(--text-dim); 438 + flex-shrink: 0; 439 + border-bottom: 1px solid var(--border); 322 440 } 323 - .raw-input-payload { 324 - font-size: 16px !important; 441 + .editor-container { 442 + flex: 1; 443 + min-height: 0; 444 + position: relative; 445 + overflow: hidden; 446 + background: var(--bg); 325 447 } 326 - /* Playback controls responsive */ 327 - .playback-container { 328 - padding: 6px 8px; 448 + .editor-container .cm-editor { 449 + position: absolute !important; 450 + top: 0; 451 + left: 0; 452 + right: 0; 453 + bottom: 0; 454 + height: auto !important; 455 + background: transparent; 456 + } 457 + .editor-container .cm-editor .cm-scroller { 458 + overflow: auto !important; 459 + } 460 + .flight-output { 461 + flex: 1; 462 + min-height: 0; 463 + margin: 0; 464 + padding: 12px; 465 + font-family: var(--font-mono); 466 + font-size: 12px; 467 + line-height: 1.6; 468 + overflow: auto; 469 + white-space: pre-wrap; 470 + word-break: break-all; 471 + background: var(--bg); 472 + color: var(--text-dim); 473 + } 474 + .flight-output.error { 475 + color: #e57373; 476 + } 477 + .action-args { 478 + color: var(--text); 479 + } 480 + 481 + /* Flight log */ 482 + .flight-log { 483 + flex: 1; 484 + overflow: auto; 485 + padding: 8px; 486 + display: flex; 487 + flex-direction: column; 329 488 gap: 6px; 330 489 } 331 - .playback-controls { 332 - gap: 2px; 490 + .log-entry { 491 + background: var(--surface); 492 + border: 1px solid var(--border); 493 + border-radius: 4px; 494 + transition: all 0.15s ease; 495 + border-left: 3px solid #555; 333 496 } 334 - .control-btn { 335 - width: 26px; 336 - height: 26px; 497 + .log-entry + .log-entry { 498 + margin-top: 12px; 337 499 } 338 - .step-info { 339 - min-width: 50px; 340 - font-size: 10px; 500 + .log-entry.active { 501 + border-left-color: #ffd54f; 341 502 } 342 - } 343 - @media (max-width: 480px) { 344 - header { 345 - padding: 0 6px; 346 - gap: 3px; 503 + .log-entry.done-entry { 504 + border-left-color: #555; 505 + opacity: 0.8; 347 506 } 348 - h1 { 507 + .log-entry.pending-entry { 508 + border-left-color: #333; 509 + opacity: 0.4; 510 + } 511 + .log-entry-header { 512 + display: flex; 513 + align-items: center; 514 + justify-content: space-between; 515 + gap: 8px; 516 + padding: 6px 10px; 517 + font-size: 11px; 518 + border-bottom: 1px solid var(--border); 519 + } 520 + .log-entry-direction { 521 + font-family: var(--font-mono); 522 + font-weight: 600; 523 + color: var(--text-dim); 524 + } 525 + .log-entry.active .log-entry-direction { 526 + color: #ffd54f; 527 + } 528 + .log-entry-args { 529 + padding: 6px 10px; 530 + font-family: var(--font-mono); 531 + font-size: 11px; 532 + border-bottom: 1px solid var(--border); 533 + color: var(--text-dim); 534 + } 535 + .action-args-label { 536 + margin-right: 6px; 537 + color: var(--text-dim); 538 + } 539 + .log-entry-label { 540 + color: var(--text); 541 + font-weight: 500; 542 + } 543 + .log-entry-count { 544 + margin-left: auto; 545 + color: var(--text-dim); 546 + font-family: var(--font-mono); 547 + } 548 + .log-entry-content { 549 + margin: 0; 550 + padding: 8px 10px; 551 + font-family: var(--font-mono); 552 + font-size: 11px; 553 + line-height: 1.5; 554 + white-space: pre-wrap; 555 + word-break: break-all; 556 + } 557 + .log-entry .action-args { 558 + color: #81c784; 559 + } 560 + 561 + /* Action request (args display) */ 562 + .log-entry-request { 563 + padding: 8px 10px; 564 + background: rgba(0, 0, 0, 0.2); 565 + border-bottom: 1px solid var(--border); 566 + } 567 + .log-entry-request-args { 568 + margin: 0; 569 + font-family: var(--font-mono); 570 + font-size: 11px; 571 + line-height: 1.4; 572 + color: #81c784; 573 + white-space: pre-wrap; 574 + word-break: break-all; 575 + } 576 + 577 + /* Log entry preview (embedded scrubber) */ 578 + .log-entry-preview { 579 + border-top: 1px solid var(--border); 580 + padding: 8px; 581 + } 582 + .log-entry-preview-controls { 583 + display: flex; 584 + align-items: center; 585 + gap: 8px; 586 + margin-bottom: 8px; 587 + } 588 + .log-step-btn { 589 + background: var(--border); 590 + border: none; 591 + color: #ffd54f; 592 + width: 24px; 593 + height: 24px; 594 + border-radius: 3px; 595 + cursor: pointer; 349 596 font-size: 10px; 597 + display: flex; 598 + align-items: center; 599 + justify-content: center; 350 600 } 351 - header select { 352 - padding: 3px 18px 3px 5px; 353 - font-size: 16px; 354 - max-width: 80px; 355 - text-overflow: ellipsis; 356 - overflow: hidden; 357 - white-space: nowrap; 601 + .log-step-btn:hover:not(:disabled) { 602 + background: #444; 358 603 } 359 - .example-select-wrapper select { 360 - max-width: 110px; 604 + .log-step-btn:disabled { 605 + opacity: 0.3; 606 + cursor: not-allowed; 361 607 } 362 - .example-select-wrapper { 363 - padding-left: 4px; 608 + .log-entry-slider { 609 + flex: 1; 610 + height: 4px; 611 + -webkit-appearance: none; 612 + appearance: none; 613 + background: var(--border); 614 + border-radius: 2px; 615 + outline: none; 616 + } 617 + .log-entry-slider::-webkit-slider-thumb { 618 + -webkit-appearance: none; 619 + width: 14px; 620 + height: 14px; 621 + background: #ffd54f; 622 + border-radius: 50%; 623 + cursor: pointer; 624 + } 625 + .log-entry-slider::-moz-range-thumb { 626 + width: 14px; 627 + height: 14px; 628 + background: #ffd54f; 629 + border-radius: 50%; 630 + cursor: pointer; 631 + border: none; 632 + } 633 + .value-pending { 634 + color: #999; 635 + font-style: italic; 636 + font-size: 11px; 637 + } 638 + .value-loading { 639 + color: #999; 640 + font-style: italic; 641 + } 642 + .log-entry-step-info { 643 + font-size: 11px; 644 + color: var(--text-dim); 645 + font-family: var(--font-mono); 646 + } 647 + .log-entry-flight-lines { 648 + margin: 0; 649 + padding: 6px; 650 + background: var(--bg); 651 + border-radius: 3px; 652 + font-size: 11px; 653 + line-height: 1.4; 654 + overflow: auto; 655 + } 656 + .log-entry-flight-lines .flight-line { 657 + display: block; 658 + padding: 6px 8px; 659 + margin-bottom: 3px; 660 + border-radius: 4px; 661 + word-break: break-all; 662 + white-space: pre-wrap; 663 + border-left: 2px solid transparent; 664 + transition: all 0.15s ease; 665 + } 666 + .log-entry-flight-lines .flight-line:last-child { 667 + margin-bottom: 0; 668 + } 669 + .log-entry-flight-lines .line-done { 670 + color: #999; 671 + background: rgba(255, 255, 255, 0.03); 672 + border-left-color: #555; 673 + } 674 + .log-entry-flight-lines .line-next { 675 + color: #e0e0e0; 676 + background: rgba(255, 213, 79, 0.12); 677 + border-left-color: #ffd54f; 678 + } 679 + .log-entry-flight-lines .line-pending { 680 + color: #444; 681 + background: transparent; 682 + border-left-color: #333; 683 + opacity: 0.4; 684 + } 685 + 686 + /* Split layout: left=stream (scrolls), right=tree (dictates height) */ 687 + .log-entry-split { 688 + display: flex; 689 + gap: 8px; 690 + align-items: stretch; 691 + } 692 + .log-entry-split .log-entry-flight-lines-wrapper { 693 + flex: 1; 694 + min-width: 0; 695 + position: relative; 696 + min-height: 150px; 697 + } 698 + .log-entry-split .log-entry-flight-lines { 699 + position: absolute; 700 + top: 0; 701 + left: 0; 702 + right: 0; 703 + bottom: 0; 704 + } 705 + .log-entry-split .log-entry-tree { 706 + flex: 1; 707 + min-width: 0; 708 + display: flex; 709 + flex-direction: column; 710 + } 711 + /* Tree view in log entry - expands to fill height */ 712 + .log-entry-tree { 713 + flex: 1; 714 + min-width: 0; 715 + border-radius: 4px; 716 + overflow: auto; 717 + border: 1px solid var(--border); 718 + } 719 + .log-entry-tree:has(.flight-tree) { 720 + background: #000; 721 + } 722 + .log-entry-tree.full-width { 723 + flex: none; 724 + width: 100%; 725 + } 726 + .log-entry-tree .flight-tree { 727 + padding: 8px; 728 + font-size: 11px; 729 + line-height: 1.6; 730 + } 731 + .jsx-output { 732 + margin: 0; 733 + white-space: pre-wrap; 734 + word-break: break-word; 735 + color: var(--text); 736 + } 737 + .value-content { 738 + background: #f8f8f8; 739 + color: #111; 740 + font-family: -apple-system, BlinkMacSystemFont, sans-serif; 741 + padding: 8px; 742 + border-radius: 3px; 743 + margin: -8px; 744 + } 745 + .value-string { 746 + color: #22863a; 747 + } 748 + .value-number { 749 + color: #005cc5; 750 + } 751 + .value-boolean { 752 + color: #d73a49; 753 + } 754 + .value-null, 755 + .value-undefined { 756 + color: #6a737d; 757 + font-style: italic; 758 + } 759 + .value-key { 760 + color: #005cc5; 761 + } 762 + .value-indent { 763 + display: block; 764 + padding-left: 16px; 765 + } 766 + .value-array-item, 767 + .value-object-entry { 768 + display: block; 769 + } 770 + .value-react-element { 771 + display: inline; 772 + background: rgba(0, 0, 0, 0.04); 773 + padding: 2px 4px; 774 + border-radius: 3px; 775 + } 776 + .value-error { 777 + color: #e57373; 778 + font-style: italic; 779 + } 780 + .value-loading { 781 + color: var(--text-dim); 782 + font-style: italic; 783 + } 784 + 785 + /* Flight Tree View */ 786 + .flight-tree { 787 + flex: 1; 788 + min-height: 0; 789 + padding: 12px; 790 + font-family: var(--font-mono); 791 + font-size: 12px; 792 + line-height: 1.8; 793 + overflow: auto; 794 + background: var(--bg); 795 + } 796 + .tree-empty { 797 + color: var(--text-dim); 798 + font-style: italic; 799 + } 800 + .tree-element, 801 + .tree-client-component, 802 + .tree-suspense, 803 + .tree-lazy { 364 804 margin-left: 0; 365 - border-left: none; 366 805 } 367 - .build-switcher { 368 - padding-left: 4px; 369 - border-left: none; 806 + .tree-children { 807 + margin-left: 16px; 808 + border-left: 1px solid #333; 809 + padding-left: 12px; 370 810 } 371 - /* Playback: hide slider and status, keep buttons compact */ 372 - .step-slider, 373 - .step-info { 374 - display: none; 811 + .tree-tag { 812 + color: #e06c75; 813 + } 814 + .tree-client-tag { 815 + color: #c678dd; 816 + } 817 + .tree-client-ref { 818 + color: #5c6370; 819 + font-size: 10px; 820 + } 821 + .tree-react-tag { 822 + color: #e5c07b; 823 + } 824 + .tree-pending { 825 + display: inline-block; 826 + color: #64b5f6; 827 + background: rgba(100, 181, 246, 0.15); 828 + padding: 2px 8px; 829 + border-radius: 10px; 830 + border: 1px solid rgba(100, 181, 246, 0.4); 831 + font-size: 10px; 832 + font-weight: 500; 833 + letter-spacing: 0.5px; 834 + animation: pending-pulse 2s ease-in-out infinite; 835 + } 836 + @keyframes pending-pulse { 837 + 0%, 838 + 100% { 839 + opacity: 0.7; 840 + } 841 + 50% { 842 + opacity: 1; 843 + } 844 + } 845 + .tree-string { 846 + color: #98c379; 847 + } 848 + .tree-number { 849 + color: #d19a66; 850 + } 851 + .tree-boolean { 852 + color: #56b6c2; 853 + } 854 + .tree-null, 855 + .tree-undefined { 856 + color: #5c6370; 857 + font-style: italic; 375 858 } 859 + .tree-key, 860 + .tree-prop-name { 861 + color: #61afef; 862 + } 863 + .tree-ref { 864 + color: #c678dd; 865 + } 866 + .tree-object { 867 + color: var(--text-dim); 868 + } 869 + .tree-array { 870 + /* Arrays render children inline */ 871 + } 872 + .tree-props { 873 + color: var(--text-dim); 874 + } 875 + .tree-prop { 876 + color: var(--text-dim); 877 + } 878 + .tree-error { 879 + display: inline-block; 880 + color: #e57373; 881 + background: rgba(229, 115, 115, 0.15); 882 + padding: 2px 8px; 883 + border-radius: 10px; 884 + border: 1px solid rgba(229, 115, 115, 0.4); 885 + font-size: 10px; 886 + font-weight: 500; 887 + letter-spacing: 0.5px; 888 + } 889 + .tree-pending-tag { 890 + color: #ffd54f; 891 + background: rgba(255, 213, 79, 0.1); 892 + padding: 1px 4px; 893 + border-radius: 3px; 894 + border: 1px solid rgba(255, 213, 79, 0.3); 895 + } 896 + .tree-error-keyword { 897 + color: #c678dd; 898 + font-weight: 600; 899 + } 900 + .tree-error-message { 901 + color: #e57373; 902 + } 903 + .tree-function { 904 + color: #61afef; 905 + font-style: italic; 906 + } 907 + .tree-placeholder { 908 + color: #444; 909 + font-style: italic; 910 + } 911 + 912 + /* Playback controls */ 376 913 .playback-container { 377 - padding: 4px 6px; 914 + display: flex; 915 + align-items: center; 916 + gap: 10px; 917 + padding: 8px 12px; 918 + background: var(--surface); 919 + border-bottom: 1px solid var(--border); 920 + } 921 + .playback-controls { 922 + display: flex; 923 + align-items: center; 378 924 gap: 4px; 379 925 } 380 926 .control-btn { 381 - width: 24px; 382 - height: 24px; 927 + background: transparent; 928 + border: none; 929 + color: var(--text); 930 + width: 30px; 931 + height: 30px; 932 + border-radius: 4px; 933 + cursor: pointer; 934 + display: flex; 935 + align-items: center; 936 + justify-content: center; 937 + transition: all 0.1s; 383 938 } 384 939 .control-btn svg { 940 + width: 16px; 941 + height: 16px; 942 + } 943 + .control-btn:hover:not(:disabled) { 944 + background: var(--border); 945 + color: var(--text-bright); 946 + } 947 + .control-btn:disabled { 948 + opacity: 0.3; 949 + cursor: not-allowed; 950 + } 951 + .control-btn.play-btn.playing { 952 + color: #ffd54f; 953 + } 954 + .control-btn.step-btn { 955 + background: #ffd54f; 956 + color: #000; 957 + animation: pulse-step 1.5s ease-in-out infinite; 958 + } 959 + .control-btn.step-btn:hover:not(:disabled) { 960 + background: #ffe566; 961 + color: #000; 962 + animation: none; 963 + } 964 + .control-btn.step-btn:disabled { 965 + background: transparent; 966 + color: var(--text); 967 + animation: none; 968 + } 969 + @keyframes pulse-step { 970 + 0%, 971 + 100% { 972 + opacity: 1; 973 + } 974 + 50% { 975 + opacity: 0.7; 976 + } 977 + } 978 + .step-slider { 979 + flex: 1; 980 + height: 4px; 981 + -webkit-appearance: none; 982 + appearance: none; 983 + background: var(--border); 984 + border-radius: 2px; 985 + outline: none; 986 + } 987 + .step-slider::-webkit-slider-thumb { 988 + -webkit-appearance: none; 989 + appearance: none; 385 990 width: 14px; 386 991 height: 14px; 992 + background: #ffd54f; 993 + border-radius: 50%; 994 + cursor: pointer; 995 + border: none; 387 996 } 388 - } 389 - @media (max-width: 360px) { 390 - h1 { 391 - display: none; 997 + .step-slider::-moz-range-thumb { 998 + width: 14px; 999 + height: 14px; 1000 + background: #ffd54f; 1001 + border-radius: 50%; 1002 + cursor: pointer; 1003 + border: none; 1004 + } 1005 + .step-slider:disabled { 1006 + opacity: 0.5; 392 1007 } 393 - header select { 394 - max-width: 75px; 1008 + .step-info { 1009 + font-size: 11px; 1010 + color: var(--text-dim); 1011 + font-family: var(--font-mono); 1012 + min-width: 60px; 1013 + text-align: right; 395 1014 } 396 - } 397 - main { 398 - flex: 1; 399 - min-height: 0; 400 - display: grid; 401 - grid-template-columns: 50% 50%; 402 - grid-template-rows: 50% 50%; 403 - overflow: hidden; 404 - } 405 - .pane { 406 - display: flex; 407 - flex-direction: column; 408 - overflow: hidden; 409 - } 410 - /* Left column border */ 411 - .pane:nth-child(1), 412 - .pane:nth-child(3) { 413 - border-right: 1px solid var(--border); 414 - } 415 - /* Top row border */ 416 - .pane:nth-child(1), 417 - .pane:nth-child(2) { 418 - border-bottom: 1px solid var(--border); 419 - } 420 - .pane-header { 421 - padding: 6px 12px; 422 - font-size: 10px; 423 - text-transform: uppercase; 424 - letter-spacing: 1px; 425 - color: var(--text-dim); 426 - flex-shrink: 0; 427 - border-bottom: 1px solid var(--border); 428 - } 429 - .editor-container { 430 - flex: 1; 431 - min-height: 0; 432 - position: relative; 433 - overflow: hidden; 434 - background: var(--bg); 435 - } 436 - .editor-container .cm-editor { 437 - position: absolute !important; 438 - top: 0; 439 - left: 0; 440 - right: 0; 441 - bottom: 0; 442 - height: auto !important; 443 - background: transparent; 444 - } 445 - .editor-container .cm-editor .cm-scroller { 446 - overflow: auto !important; 447 - } 448 - .flight-output { 449 - flex: 1; 450 - min-height: 0; 451 - margin: 0; 452 - padding: 12px; 453 - font-family: var(--font-mono); 454 - font-size: 12px; 455 - line-height: 1.6; 456 - overflow: auto; 457 - white-space: pre-wrap; 458 - word-break: break-all; 459 - background: var(--bg); 460 - color: var(--text-dim); 461 - } 462 - .flight-output.error { 463 - color: #e57373; 464 - } 465 - .action-args { 466 - color: var(--text); 467 - } 468 1015 469 - /* Flight log */ 470 - .flight-log { 471 - flex: 1; 472 - overflow: auto; 473 - padding: 8px; 474 - display: flex; 475 - flex-direction: column; 476 - gap: 6px; 477 - } 478 - .log-entry { 479 - background: var(--surface); 480 - border: 1px solid var(--border); 481 - border-radius: 4px; 482 - transition: all 0.15s ease; 483 - border-left: 3px solid #555; 484 - } 485 - .log-entry + .log-entry { 486 - margin-top: 12px; 487 - } 488 - .log-entry.active { 489 - border-left-color: #ffd54f; 490 - } 491 - .log-entry.done-entry { 492 - border-left-color: #555; 493 - opacity: 0.8; 494 - } 495 - .log-entry.pending-entry { 496 - border-left-color: #333; 497 - opacity: 0.4; 498 - } 499 - .log-entry-header { 500 - display: flex; 501 - align-items: center; 502 - justify-content: space-between; 503 - gap: 8px; 504 - padding: 6px 10px; 505 - font-size: 11px; 506 - border-bottom: 1px solid var(--border); 507 - } 508 - .log-entry-direction { 509 - font-family: var(--font-mono); 510 - font-weight: 600; 511 - color: var(--text-dim); 512 - } 513 - .log-entry.active .log-entry-direction { 514 - color: #ffd54f; 515 - } 516 - .log-entry-args { 517 - padding: 6px 10px; 518 - font-family: var(--font-mono); 519 - font-size: 11px; 520 - border-bottom: 1px solid var(--border); 521 - color: var(--text-dim); 522 - } 523 - .action-args-label { 524 - margin-right: 6px; 525 - color: var(--text-dim); 526 - } 527 - .log-entry-label { 528 - color: var(--text); 529 - font-weight: 500; 530 - } 531 - .log-entry-count { 532 - margin-left: auto; 533 - color: var(--text-dim); 534 - font-family: var(--font-mono); 535 - } 536 - .log-entry-content { 537 - margin: 0; 538 - padding: 8px 10px; 539 - font-family: var(--font-mono); 540 - font-size: 11px; 541 - line-height: 1.5; 542 - white-space: pre-wrap; 543 - word-break: break-all; 544 - } 545 - .log-entry .action-args { 546 - color: #81c784; 547 - } 548 - 549 - /* Action request (args display) */ 550 - .log-entry-request { 551 - padding: 8px 10px; 552 - background: rgba(0, 0, 0, 0.2); 553 - border-bottom: 1px solid var(--border); 554 - } 555 - .log-entry-request-args { 556 - margin: 0; 557 - font-family: var(--font-mono); 558 - font-size: 11px; 559 - line-height: 1.4; 560 - color: #81c784; 561 - white-space: pre-wrap; 562 - word-break: break-all; 563 - } 1016 + /* Preview pane */ 1017 + .preview-container { 1018 + flex: 1; 1019 + padding: 20px; 1020 + background: #fff; 1021 + color: #111; 1022 + overflow: auto; 1023 + font-family: -apple-system, BlinkMacSystemFont, sans-serif; 1024 + font-size: 16px; 1025 + line-height: 1.5; 1026 + } 1027 + .preview-container h1 { 1028 + font-size: 28px; 1029 + color: #000; 1030 + margin: 0 0 16px; 1031 + } 1032 + .preview-container h2 { 1033 + font-size: 22px; 1034 + color: #000; 1035 + } 1036 + .preview-container h3 { 1037 + font-size: 18px; 1038 + color: #000; 1039 + } 1040 + .preview-container p { 1041 + color: #111; 1042 + margin: 8px 0; 1043 + } 1044 + .preview-container button { 1045 + background: #333; 1046 + color: #fff; 1047 + border: none; 1048 + padding: 8px 14px; 1049 + border-radius: 4px; 1050 + cursor: pointer; 1051 + font-size: 14px; 1052 + } 1053 + .preview-container button:hover { 1054 + background: #444; 1055 + } 1056 + .preview-container .empty { 1057 + color: #999; 1058 + font-style: italic; 1059 + } 1060 + .preview-container .empty.error { 1061 + color: #c0392b; 1062 + } 1063 + .flight-output .empty { 1064 + color: var(--text-dim); 1065 + font-style: italic; 1066 + } 1067 + /* Pulsing dots for empty/waiting states */ 1068 + .empty::after { 1069 + content: ""; 1070 + animation: none; 1071 + } 1072 + .waiting-dots::after { 1073 + content: "..."; 1074 + animation: pulse 1.5s ease-in-out infinite; 1075 + } 1076 + @keyframes pulse { 1077 + 0%, 1078 + 100% { 1079 + opacity: 0.3; 1080 + } 1081 + 50% { 1082 + opacity: 1; 1083 + } 1084 + } 564 1085 565 - /* Log entry preview (embedded scrubber) */ 566 - .log-entry-preview { 567 - border-top: 1px solid var(--border); 568 - padding: 8px; 569 - } 570 - .log-entry-preview-controls { 571 - display: flex; 572 - align-items: center; 573 - gap: 8px; 574 - margin-bottom: 8px; 575 - } 576 - .log-step-btn { 577 - background: var(--border); 578 - border: none; 579 - color: #ffd54f; 580 - width: 24px; 581 - height: 24px; 582 - border-radius: 3px; 583 - cursor: pointer; 584 - font-size: 10px; 585 - display: flex; 586 - align-items: center; 587 - justify-content: center; 588 - } 589 - .log-step-btn:hover:not(:disabled) { 590 - background: #444; 591 - } 592 - .log-step-btn:disabled { 593 - opacity: 0.3; 594 - cursor: not-allowed; 595 - } 596 - .log-entry-slider { 597 - flex: 1; 598 - height: 4px; 599 - -webkit-appearance: none; 600 - appearance: none; 601 - background: var(--border); 602 - border-radius: 2px; 603 - outline: none; 604 - } 605 - .log-entry-slider::-webkit-slider-thumb { 606 - -webkit-appearance: none; 607 - width: 14px; 608 - height: 14px; 609 - background: #ffd54f; 610 - border-radius: 50%; 611 - cursor: pointer; 612 - } 613 - .log-entry-slider::-moz-range-thumb { 614 - width: 14px; 615 - height: 14px; 616 - background: #ffd54f; 617 - border-radius: 50%; 618 - cursor: pointer; 619 - border: none; 620 - } 621 - .value-pending { 622 - color: #999; 623 - font-style: italic; 624 - font-size: 11px; 625 - } 626 - .value-loading { 627 - color: #999; 628 - font-style: italic; 629 - } 630 - .log-entry-step-info { 631 - font-size: 11px; 632 - color: var(--text-dim); 633 - font-family: var(--font-mono); 634 - } 635 - .log-entry-flight-lines { 636 - margin: 0; 637 - padding: 6px; 638 - background: var(--bg); 639 - border-radius: 3px; 640 - font-size: 11px; 641 - line-height: 1.4; 642 - overflow: auto; 643 - } 644 - .log-entry-flight-lines .flight-line { 645 - display: block; 646 - padding: 6px 8px; 647 - margin-bottom: 3px; 648 - border-radius: 4px; 649 - word-break: break-all; 650 - white-space: pre-wrap; 651 - border-left: 2px solid transparent; 652 - transition: all 0.15s ease; 653 - } 654 - .log-entry-flight-lines .flight-line:last-child { 655 - margin-bottom: 0; 656 - } 657 - .log-entry-flight-lines .line-done { 658 - color: #999; 659 - background: rgba(255, 255, 255, 0.03); 660 - border-left-color: #555; 661 - } 662 - .log-entry-flight-lines .line-next { 663 - color: #e0e0e0; 664 - background: rgba(255, 213, 79, 0.12); 665 - border-left-color: #ffd54f; 666 - } 667 - .log-entry-flight-lines .line-pending { 668 - color: #444; 669 - background: transparent; 670 - border-left-color: #333; 671 - opacity: 0.4; 672 - } 673 - 674 - /* Split layout: left=stream (scrolls), right=tree (dictates height) */ 675 - .log-entry-split { 676 - display: flex; 677 - gap: 8px; 678 - align-items: stretch; 679 - } 680 - .log-entry-split .log-entry-flight-lines-wrapper { 681 - flex: 1; 682 - min-width: 0; 683 - position: relative; 684 - min-height: 150px; 685 - } 686 - .log-entry-split .log-entry-flight-lines { 687 - position: absolute; 688 - top: 0; 689 - left: 0; 690 - right: 0; 691 - bottom: 0; 692 - } 693 - .log-entry-split .log-entry-tree { 694 - flex: 1; 695 - min-width: 0; 696 - display: flex; 697 - flex-direction: column; 698 - } 699 - /* Tree view in log entry - expands to fill height */ 700 - .log-entry-tree { 701 - flex: 1; 702 - min-width: 0; 703 - border-radius: 4px; 704 - overflow: auto; 705 - border: 1px solid var(--border); 706 - } 707 - .log-entry-tree:has(.flight-tree) { 708 - background: #000; 709 - } 710 - .log-entry-tree.full-width { 711 - flex: none; 712 - width: 100%; 713 - } 714 - .log-entry-tree .flight-tree { 715 - padding: 8px; 716 - font-size: 11px; 717 - line-height: 1.6; 718 - } 719 - .jsx-output { 720 - margin: 0; 721 - white-space: pre-wrap; 722 - word-break: break-word; 723 - color: var(--text); 724 - } 725 - .value-content { 726 - background: #f8f8f8; 727 - color: #111; 728 - font-family: -apple-system, BlinkMacSystemFont, sans-serif; 729 - padding: 8px; 730 - border-radius: 3px; 731 - margin: -8px; 732 - } 733 - .value-string { color: #22863a; } 734 - .value-number { color: #005cc5; } 735 - .value-boolean { color: #d73a49; } 736 - .value-null, .value-undefined { color: #6a737d; font-style: italic; } 737 - .value-key { color: #005cc5; } 738 - .value-indent { 739 - display: block; 740 - padding-left: 16px; 741 - } 742 - .value-array-item, .value-object-entry { 743 - display: block; 744 - } 745 - .value-react-element { 746 - display: inline; 747 - background: rgba(0, 0, 0, 0.04); 748 - padding: 2px 4px; 749 - border-radius: 3px; 750 - } 751 - .value-error { 752 - color: #e57373; 753 - font-style: italic; 754 - } 755 - .value-loading { 756 - color: var(--text-dim); 757 - font-style: italic; 758 - } 759 - 760 - /* Flight Tree View */ 761 - .flight-tree { 762 - flex: 1; 763 - min-height: 0; 764 - padding: 12px; 765 - font-family: var(--font-mono); 766 - font-size: 12px; 767 - line-height: 1.8; 768 - overflow: auto; 769 - background: var(--bg); 770 - } 771 - .tree-empty { 772 - color: var(--text-dim); 773 - font-style: italic; 774 - } 775 - .tree-element, 776 - .tree-client-component, 777 - .tree-suspense, 778 - .tree-lazy { 779 - margin-left: 0; 780 - } 781 - .tree-children { 782 - margin-left: 16px; 783 - border-left: 1px solid #333; 784 - padding-left: 12px; 785 - } 786 - .tree-tag { 787 - color: #e06c75; 788 - } 789 - .tree-client-tag { 790 - color: #c678dd; 791 - } 792 - .tree-client-ref { 793 - color: #5c6370; 794 - font-size: 10px; 795 - } 796 - .tree-react-tag { 797 - color: #e5c07b; 798 - } 799 - .tree-pending { 800 - display: inline-block; 801 - color: #64b5f6; 802 - background: rgba(100, 181, 246, 0.15); 803 - padding: 2px 8px; 804 - border-radius: 10px; 805 - border: 1px solid rgba(100, 181, 246, 0.4); 806 - font-size: 10px; 807 - font-weight: 500; 808 - letter-spacing: 0.5px; 809 - animation: pending-pulse 2s ease-in-out infinite; 810 - } 811 - @keyframes pending-pulse { 812 - 0%, 100% { opacity: 0.7; } 813 - 50% { opacity: 1; } 814 - } 815 - .tree-string { 816 - color: #98c379; 817 - } 818 - .tree-number { 819 - color: #d19a66; 820 - } 821 - .tree-boolean { 822 - color: #56b6c2; 823 - } 824 - .tree-null, 825 - .tree-undefined { 826 - color: #5c6370; 827 - font-style: italic; 828 - } 829 - .tree-key, 830 - .tree-prop-name { 831 - color: #61afef; 832 - } 833 - .tree-ref { 834 - color: #c678dd; 835 - } 836 - .tree-object { 837 - color: var(--text-dim); 838 - } 839 - .tree-array { 840 - /* Arrays render children inline */ 841 - } 842 - .tree-props { 843 - color: var(--text-dim); 844 - } 845 - .tree-prop { 846 - color: var(--text-dim); 847 - } 848 - .tree-error { 849 - display: inline-block; 850 - color: #e57373; 851 - background: rgba(229, 115, 115, 0.15); 852 - padding: 2px 8px; 853 - border-radius: 10px; 854 - border: 1px solid rgba(229, 115, 115, 0.4); 855 - font-size: 10px; 856 - font-weight: 500; 857 - letter-spacing: 0.5px; 858 - } 859 - .tree-pending-tag { 860 - color: #ffd54f; 861 - background: rgba(255, 213, 79, 0.1); 862 - padding: 1px 4px; 863 - border-radius: 3px; 864 - border: 1px solid rgba(255, 213, 79, 0.3); 865 - } 866 - .tree-error-keyword { 867 - color: #c678dd; 868 - font-weight: 600; 869 - } 870 - .tree-error-message { 871 - color: #e57373; 872 - } 873 - .tree-function { 874 - color: #61afef; 875 - font-style: italic; 876 - } 877 - .tree-placeholder { 878 - color: #444; 879 - font-style: italic; 880 - } 881 - 882 - /* Playback controls */ 883 - .playback-container { 884 - display: flex; 885 - align-items: center; 886 - gap: 10px; 887 - padding: 8px 12px; 888 - background: var(--surface); 889 - border-bottom: 1px solid var(--border); 890 - } 891 - .playback-controls { 892 - display: flex; 893 - align-items: center; 894 - gap: 4px; 895 - } 896 - .control-btn { 897 - background: transparent; 898 - border: none; 899 - color: var(--text); 900 - width: 30px; 901 - height: 30px; 902 - border-radius: 4px; 903 - cursor: pointer; 904 - display: flex; 905 - align-items: center; 906 - justify-content: center; 907 - transition: all 0.1s; 908 - } 909 - .control-btn svg { 910 - width: 16px; 911 - height: 16px; 912 - } 913 - .control-btn:hover:not(:disabled) { 914 - background: var(--border); 915 - color: var(--text-bright); 916 - } 917 - .control-btn:disabled { 918 - opacity: 0.3; 919 - cursor: not-allowed; 920 - } 921 - .control-btn.play-btn.playing { 922 - color: #ffd54f; 923 - } 924 - .control-btn.step-btn { 925 - background: #ffd54f; 926 - color: #000; 927 - animation: pulse-step 1.5s ease-in-out infinite; 928 - } 929 - .control-btn.step-btn:hover:not(:disabled) { 930 - background: #ffe566; 931 - color: #000; 932 - animation: none; 933 - } 934 - .control-btn.step-btn:disabled { 935 - background: transparent; 936 - color: var(--text); 937 - animation: none; 938 - } 939 - @keyframes pulse-step { 940 - 0%, 100% { opacity: 1; } 941 - 50% { opacity: 0.7; } 942 - } 943 - .step-slider { 944 - flex: 1; 945 - height: 4px; 946 - -webkit-appearance: none; 947 - appearance: none; 948 - background: var(--border); 949 - border-radius: 2px; 950 - outline: none; 951 - } 952 - .step-slider::-webkit-slider-thumb { 953 - -webkit-appearance: none; 954 - appearance: none; 955 - width: 14px; 956 - height: 14px; 957 - background: #ffd54f; 958 - border-radius: 50%; 959 - cursor: pointer; 960 - border: none; 961 - } 962 - .step-slider::-moz-range-thumb { 963 - width: 14px; 964 - height: 14px; 965 - background: #ffd54f; 966 - border-radius: 50%; 967 - cursor: pointer; 968 - border: none; 969 - } 970 - .step-slider:disabled { 971 - opacity: 0.5; 972 - } 973 - .step-info { 974 - font-size: 11px; 975 - color: var(--text-dim); 976 - font-family: var(--font-mono); 977 - min-width: 60px; 978 - text-align: right; 979 - } 980 - 981 - /* Preview pane */ 982 - .preview-container { 983 - flex: 1; 984 - padding: 20px; 985 - background: #fff; 986 - color: #111; 987 - overflow: auto; 988 - font-family: -apple-system, BlinkMacSystemFont, sans-serif; 989 - font-size: 16px; 990 - line-height: 1.5; 991 - } 992 - .preview-container h1 { 993 - font-size: 28px; 994 - color: #000; 995 - margin: 0 0 16px; 996 - } 997 - .preview-container h2 { 998 - font-size: 22px; 999 - color: #000; 1000 - } 1001 - .preview-container h3 { 1002 - font-size: 18px; 1003 - color: #000; 1004 - } 1005 - .preview-container p { 1006 - color: #111; 1007 - margin: 8px 0; 1008 - } 1009 - .preview-container button { 1010 - background: #333; 1011 - color: #fff; 1012 - border: none; 1013 - padding: 8px 14px; 1014 - border-radius: 4px; 1015 - cursor: pointer; 1016 - font-size: 14px; 1017 - } 1018 - .preview-container button:hover { 1019 - background: #444; 1020 - } 1021 - .preview-container .empty { 1022 - color: #999; 1023 - font-style: italic; 1024 - } 1025 - .preview-container .empty.error { 1026 - color: #c0392b; 1027 - } 1028 - .flight-output .empty { 1029 - color: var(--text-dim); 1030 - font-style: italic; 1031 - } 1032 - /* Pulsing dots for empty/waiting states */ 1033 - .empty::after { 1034 - content: ''; 1035 - animation: none; 1036 - } 1037 - .waiting-dots::after { 1038 - content: '...'; 1039 - animation: pulse 1.5s ease-in-out infinite; 1040 - } 1041 - @keyframes pulse { 1042 - 0%, 100% { opacity: 0.3; } 1043 - 50% { opacity: 1; } 1044 - } 1045 - 1046 - /* Raw input form */ 1047 - .add-raw-btn-wrapper { 1048 - display: flex; 1049 - justify-content: center; 1050 - margin-top: 8px; 1051 - } 1052 - .add-raw-btn { 1053 - width: 24px; 1054 - height: 24px; 1055 - padding: 0; 1056 - background: var(--border); 1057 - border: 1px solid #444; 1058 - border-radius: 50%; 1059 - color: var(--text-dim); 1060 - cursor: pointer; 1061 - font-size: 14px; 1062 - line-height: 22px; 1063 - transition: all 0.15s; 1064 - } 1065 - .add-raw-btn:hover { 1066 - background: #3a3a3a; 1067 - border-color: #666; 1068 - color: var(--text); 1069 - } 1070 - .raw-input-form { 1071 - margin-top: 8px; 1072 - padding: 10px; 1073 - background: var(--surface); 1074 - border: 1px solid var(--border); 1075 - border-radius: 4px; 1076 - } 1077 - .raw-input-action { 1078 - -webkit-appearance: none; 1079 - appearance: none; 1080 - width: 100%; 1081 - padding: 5px 28px 5px 10px; 1082 - margin-bottom: 8px; 1083 - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center, linear-gradient(to bottom, #333, #2a2a2a); 1084 - border: 1px solid #555; 1085 - border-radius: 4px; 1086 - color: #fff; 1087 - font-size: 12px; 1088 - cursor: pointer; 1089 - box-shadow: 0 1px 2px rgba(0,0,0,0.2); 1090 - } 1091 - .raw-input-action:hover { 1092 - border-color: #666; 1093 - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23bbb' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center, linear-gradient(to bottom, #3a3a3a, #333); 1094 - } 1095 - .raw-input-action:focus { 1096 - outline: none; 1097 - border-color: #ffd54f; 1098 - } 1099 - .raw-input-payload { 1100 - width: 100%; 1101 - padding: 8px; 1102 - background: var(--bg); 1103 - border: 1px solid var(--border); 1104 - border-radius: 3px; 1105 - color: var(--text); 1106 - font-family: var(--font-mono); 1107 - font-size: 11px; 1108 - resize: vertical; 1109 - min-height: 100px; 1110 - } 1111 - .raw-input-payload:focus { 1112 - outline: none; 1113 - border-color: #555; 1114 - } 1115 - .raw-input-buttons { 1116 - display: flex; 1117 - gap: 8px; 1118 - margin-top: 8px; 1119 - } 1120 - .raw-input-buttons button { 1121 - padding: 5px 12px; 1122 - border-radius: 3px; 1123 - font-size: 11px; 1124 - cursor: pointer; 1125 - } 1126 - .raw-input-buttons button:first-child { 1127 - background: #ffd54f; 1128 - border: none; 1129 - color: #000; 1130 - } 1131 - .raw-input-buttons button:first-child:disabled { 1132 - background: #555; 1133 - color: #888; 1134 - cursor: not-allowed; 1135 - } 1136 - .raw-input-buttons button:last-child { 1137 - background: transparent; 1138 - border: 1px solid var(--border); 1139 - color: var(--text-dim); 1140 - } 1141 - .raw-input-buttons button:last-child:hover { 1142 - border-color: #555; 1143 - color: var(--text); 1144 - } 1145 - .log-entry-header-right { 1146 - display: flex; 1147 - align-items: center; 1148 - gap: 8px; 1149 - } 1150 - .delete-entry-btn { 1151 - background: transparent; 1152 - border: none; 1153 - color: var(--text-dim); 1154 - cursor: pointer; 1155 - font-size: 14px; 1156 - padding: 0 4px; 1157 - line-height: 1; 1158 - opacity: 0.5; 1159 - transition: opacity 0.15s, color 0.15s; 1160 - } 1161 - .delete-entry-btn:hover { 1162 - opacity: 1; 1163 - color: #e57373; 1164 - } 1165 - </style> 1166 - <!-- Privacy-friendly analytics by Plausible --> 1167 - <script async src="https://plausible.io/js/pa-CtwoQWR5DSFU93v-DPr1p.js"></script> 1168 - <script> 1169 - window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}}; 1170 - plausible.init() 1171 - </script> 1172 - </head> 1173 - <body> 1174 - <div id="app" style="display: flex; flex-direction: column; height: 100%; overflow: hidden;"></div> 1175 - <script type="module" src="/src/client/index.jsx"></script> 1176 - </body> 1086 + /* Raw input form */ 1087 + .add-raw-btn-wrapper { 1088 + display: flex; 1089 + justify-content: center; 1090 + margin-top: 8px; 1091 + } 1092 + .add-raw-btn { 1093 + width: 24px; 1094 + height: 24px; 1095 + padding: 0; 1096 + background: var(--border); 1097 + border: 1px solid #444; 1098 + border-radius: 50%; 1099 + color: var(--text-dim); 1100 + cursor: pointer; 1101 + font-size: 14px; 1102 + line-height: 22px; 1103 + transition: all 0.15s; 1104 + } 1105 + .add-raw-btn:hover { 1106 + background: #3a3a3a; 1107 + border-color: #666; 1108 + color: var(--text); 1109 + } 1110 + .raw-input-form { 1111 + margin-top: 8px; 1112 + padding: 10px; 1113 + background: var(--surface); 1114 + border: 1px solid var(--border); 1115 + border-radius: 4px; 1116 + } 1117 + .raw-input-action { 1118 + -webkit-appearance: none; 1119 + appearance: none; 1120 + width: 100%; 1121 + padding: 5px 28px 5px 10px; 1122 + margin-bottom: 8px; 1123 + background: 1124 + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") 1125 + no-repeat right 8px center, 1126 + linear-gradient(to bottom, #333, #2a2a2a); 1127 + border: 1px solid #555; 1128 + border-radius: 4px; 1129 + color: #fff; 1130 + font-size: 12px; 1131 + cursor: pointer; 1132 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 1133 + } 1134 + .raw-input-action:hover { 1135 + border-color: #666; 1136 + background: 1137 + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23bbb' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") 1138 + no-repeat right 8px center, 1139 + linear-gradient(to bottom, #3a3a3a, #333); 1140 + } 1141 + .raw-input-action:focus { 1142 + outline: none; 1143 + border-color: #ffd54f; 1144 + } 1145 + .raw-input-payload { 1146 + width: 100%; 1147 + padding: 8px; 1148 + background: var(--bg); 1149 + border: 1px solid var(--border); 1150 + border-radius: 3px; 1151 + color: var(--text); 1152 + font-family: var(--font-mono); 1153 + font-size: 11px; 1154 + resize: vertical; 1155 + min-height: 100px; 1156 + } 1157 + .raw-input-payload:focus { 1158 + outline: none; 1159 + border-color: #555; 1160 + } 1161 + .raw-input-buttons { 1162 + display: flex; 1163 + gap: 8px; 1164 + margin-top: 8px; 1165 + } 1166 + .raw-input-buttons button { 1167 + padding: 5px 12px; 1168 + border-radius: 3px; 1169 + font-size: 11px; 1170 + cursor: pointer; 1171 + } 1172 + .raw-input-buttons button:first-child { 1173 + background: #ffd54f; 1174 + border: none; 1175 + color: #000; 1176 + } 1177 + .raw-input-buttons button:first-child:disabled { 1178 + background: #555; 1179 + color: #888; 1180 + cursor: not-allowed; 1181 + } 1182 + .raw-input-buttons button:last-child { 1183 + background: transparent; 1184 + border: 1px solid var(--border); 1185 + color: var(--text-dim); 1186 + } 1187 + .raw-input-buttons button:last-child:hover { 1188 + border-color: #555; 1189 + color: var(--text); 1190 + } 1191 + .log-entry-header-right { 1192 + display: flex; 1193 + align-items: center; 1194 + gap: 8px; 1195 + } 1196 + .delete-entry-btn { 1197 + background: transparent; 1198 + border: none; 1199 + color: var(--text-dim); 1200 + cursor: pointer; 1201 + font-size: 14px; 1202 + padding: 0 4px; 1203 + line-height: 1; 1204 + opacity: 0.5; 1205 + transition: 1206 + opacity 0.15s, 1207 + color 0.15s; 1208 + } 1209 + .delete-entry-btn:hover { 1210 + opacity: 1; 1211 + color: #e57373; 1212 + } 1213 + </style> 1214 + <!-- Privacy-friendly analytics by Plausible --> 1215 + <script async src="https://plausible.io/js/pa-CtwoQWR5DSFU93v-DPr1p.js"></script> 1216 + <script> 1217 + ((window.plausible = 1218 + window.plausible || 1219 + function () { 1220 + (plausible.q = plausible.q || []).push(arguments); 1221 + }), 1222 + (plausible.init = 1223 + plausible.init || 1224 + function (i) { 1225 + plausible.o = i || {}; 1226 + })); 1227 + plausible.init(); 1228 + </script> 1229 + </head> 1230 + <body> 1231 + <div 1232 + id="app" 1233 + style="display: flex; flex-direction: column; height: 100%; overflow: hidden" 1234 + ></div> 1235 + <script type="module" src="/src/client/index.jsx"></script> 1236 + </body> 1177 1237 </html>
+1917 -7
package-lock.json
··· 26 26 "web-streams-polyfill": "^4.2.0" 27 27 }, 28 28 "devDependencies": { 29 + "@eslint/js": "^9.39.2", 29 30 "@vitejs/plugin-react": "^5.1.2", 30 31 "@vitest/browser": "^4.0.15", 31 32 "@vitest/browser-playwright": "^4.0.15", 33 + "eslint": "^9.39.2", 34 + "eslint-plugin-react-hooks": "^7.0.1", 35 + "husky": "^9.1.7", 36 + "lint-staged": "^16.2.7", 32 37 "playwright": "^1.57.0", 38 + "prettier": "^3.7.4", 33 39 "rolldown": "^1.0.0-beta.54", 40 + "typescript-eslint": "^8.50.0", 34 41 "vite": "8.0.0-beta.2", 35 42 "vitest": "^4.0.15", 36 43 "wrangler": "^4.0.0" ··· 618 625 "optional": true, 619 626 "dependencies": { 620 627 "tslib": "^2.4.0" 628 + } 629 + }, 630 + "node_modules/@eslint-community/eslint-utils": { 631 + "version": "4.9.0", 632 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", 633 + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", 634 + "dev": true, 635 + "license": "MIT", 636 + "dependencies": { 637 + "eslint-visitor-keys": "^3.4.3" 638 + }, 639 + "engines": { 640 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 641 + }, 642 + "funding": { 643 + "url": "https://opencollective.com/eslint" 644 + }, 645 + "peerDependencies": { 646 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 647 + } 648 + }, 649 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 650 + "version": "3.4.3", 651 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 652 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 653 + "dev": true, 654 + "license": "Apache-2.0", 655 + "engines": { 656 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 657 + }, 658 + "funding": { 659 + "url": "https://opencollective.com/eslint" 660 + } 661 + }, 662 + "node_modules/@eslint-community/regexpp": { 663 + "version": "4.12.2", 664 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", 665 + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", 666 + "dev": true, 667 + "license": "MIT", 668 + "engines": { 669 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 670 + } 671 + }, 672 + "node_modules/@eslint/config-array": { 673 + "version": "0.21.1", 674 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", 675 + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", 676 + "dev": true, 677 + "license": "Apache-2.0", 678 + "dependencies": { 679 + "@eslint/object-schema": "^2.1.7", 680 + "debug": "^4.3.1", 681 + "minimatch": "^3.1.2" 682 + }, 683 + "engines": { 684 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 685 + } 686 + }, 687 + "node_modules/@eslint/config-helpers": { 688 + "version": "0.4.2", 689 + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", 690 + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", 691 + "dev": true, 692 + "license": "Apache-2.0", 693 + "dependencies": { 694 + "@eslint/core": "^0.17.0" 695 + }, 696 + "engines": { 697 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 698 + } 699 + }, 700 + "node_modules/@eslint/core": { 701 + "version": "0.17.0", 702 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", 703 + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", 704 + "dev": true, 705 + "license": "Apache-2.0", 706 + "dependencies": { 707 + "@types/json-schema": "^7.0.15" 708 + }, 709 + "engines": { 710 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 711 + } 712 + }, 713 + "node_modules/@eslint/eslintrc": { 714 + "version": "3.3.3", 715 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", 716 + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", 717 + "dev": true, 718 + "license": "MIT", 719 + "dependencies": { 720 + "ajv": "^6.12.4", 721 + "debug": "^4.3.2", 722 + "espree": "^10.0.1", 723 + "globals": "^14.0.0", 724 + "ignore": "^5.2.0", 725 + "import-fresh": "^3.2.1", 726 + "js-yaml": "^4.1.1", 727 + "minimatch": "^3.1.2", 728 + "strip-json-comments": "^3.1.1" 729 + }, 730 + "engines": { 731 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 732 + }, 733 + "funding": { 734 + "url": "https://opencollective.com/eslint" 735 + } 736 + }, 737 + "node_modules/@eslint/eslintrc/node_modules/ajv": { 738 + "version": "6.12.6", 739 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 740 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 741 + "dev": true, 742 + "license": "MIT", 743 + "dependencies": { 744 + "fast-deep-equal": "^3.1.1", 745 + "fast-json-stable-stringify": "^2.0.0", 746 + "json-schema-traverse": "^0.4.1", 747 + "uri-js": "^4.2.2" 748 + }, 749 + "funding": { 750 + "type": "github", 751 + "url": "https://github.com/sponsors/epoberezkin" 752 + } 753 + }, 754 + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { 755 + "version": "0.4.1", 756 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 757 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 758 + "dev": true, 759 + "license": "MIT" 760 + }, 761 + "node_modules/@eslint/js": { 762 + "version": "9.39.2", 763 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", 764 + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", 765 + "dev": true, 766 + "license": "MIT", 767 + "engines": { 768 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 769 + }, 770 + "funding": { 771 + "url": "https://eslint.org/donate" 772 + } 773 + }, 774 + "node_modules/@eslint/object-schema": { 775 + "version": "2.1.7", 776 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", 777 + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", 778 + "dev": true, 779 + "license": "Apache-2.0", 780 + "engines": { 781 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 782 + } 783 + }, 784 + "node_modules/@eslint/plugin-kit": { 785 + "version": "0.4.1", 786 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", 787 + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", 788 + "dev": true, 789 + "license": "Apache-2.0", 790 + "dependencies": { 791 + "@eslint/core": "^0.17.0", 792 + "levn": "^0.4.1" 793 + }, 794 + "engines": { 795 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 796 + } 797 + }, 798 + "node_modules/@humanfs/core": { 799 + "version": "0.19.1", 800 + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 801 + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 802 + "dev": true, 803 + "license": "Apache-2.0", 804 + "engines": { 805 + "node": ">=18.18.0" 806 + } 807 + }, 808 + "node_modules/@humanfs/node": { 809 + "version": "0.16.7", 810 + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 811 + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 812 + "dev": true, 813 + "license": "Apache-2.0", 814 + "dependencies": { 815 + "@humanfs/core": "^0.19.1", 816 + "@humanwhocodes/retry": "^0.4.0" 817 + }, 818 + "engines": { 819 + "node": ">=18.18.0" 820 + } 821 + }, 822 + "node_modules/@humanwhocodes/module-importer": { 823 + "version": "1.0.1", 824 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 825 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 826 + "dev": true, 827 + "license": "Apache-2.0", 828 + "engines": { 829 + "node": ">=12.22" 830 + }, 831 + "funding": { 832 + "type": "github", 833 + "url": "https://github.com/sponsors/nzakas" 834 + } 835 + }, 836 + "node_modules/@humanwhocodes/retry": { 837 + "version": "0.4.3", 838 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 839 + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 840 + "dev": true, 841 + "license": "Apache-2.0", 842 + "engines": { 843 + "node": ">=18.18" 844 + }, 845 + "funding": { 846 + "type": "github", 847 + "url": "https://github.com/sponsors/nzakas" 621 848 } 622 849 }, 623 850 "node_modules/@img/sharp-darwin-arm64": { ··· 1541 1768 "version": "7.0.15", 1542 1769 "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 1543 1770 "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 1544 - "license": "MIT", 1545 - "peer": true 1771 + "license": "MIT" 1546 1772 }, 1547 1773 "node_modules/@types/node": { 1548 1774 "version": "25.0.1", ··· 1554 1780 "undici-types": "~7.16.0" 1555 1781 } 1556 1782 }, 1783 + "node_modules/@typescript-eslint/eslint-plugin": { 1784 + "version": "8.50.0", 1785 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", 1786 + "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", 1787 + "dev": true, 1788 + "license": "MIT", 1789 + "dependencies": { 1790 + "@eslint-community/regexpp": "^4.10.0", 1791 + "@typescript-eslint/scope-manager": "8.50.0", 1792 + "@typescript-eslint/type-utils": "8.50.0", 1793 + "@typescript-eslint/utils": "8.50.0", 1794 + "@typescript-eslint/visitor-keys": "8.50.0", 1795 + "ignore": "^7.0.0", 1796 + "natural-compare": "^1.4.0", 1797 + "ts-api-utils": "^2.1.0" 1798 + }, 1799 + "engines": { 1800 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1801 + }, 1802 + "funding": { 1803 + "type": "opencollective", 1804 + "url": "https://opencollective.com/typescript-eslint" 1805 + }, 1806 + "peerDependencies": { 1807 + "@typescript-eslint/parser": "^8.50.0", 1808 + "eslint": "^8.57.0 || ^9.0.0", 1809 + "typescript": ">=4.8.4 <6.0.0" 1810 + } 1811 + }, 1812 + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1813 + "version": "7.0.5", 1814 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 1815 + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 1816 + "dev": true, 1817 + "license": "MIT", 1818 + "engines": { 1819 + "node": ">= 4" 1820 + } 1821 + }, 1822 + "node_modules/@typescript-eslint/parser": { 1823 + "version": "8.50.0", 1824 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", 1825 + "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", 1826 + "dev": true, 1827 + "license": "MIT", 1828 + "dependencies": { 1829 + "@typescript-eslint/scope-manager": "8.50.0", 1830 + "@typescript-eslint/types": "8.50.0", 1831 + "@typescript-eslint/typescript-estree": "8.50.0", 1832 + "@typescript-eslint/visitor-keys": "8.50.0", 1833 + "debug": "^4.3.4" 1834 + }, 1835 + "engines": { 1836 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1837 + }, 1838 + "funding": { 1839 + "type": "opencollective", 1840 + "url": "https://opencollective.com/typescript-eslint" 1841 + }, 1842 + "peerDependencies": { 1843 + "eslint": "^8.57.0 || ^9.0.0", 1844 + "typescript": ">=4.8.4 <6.0.0" 1845 + } 1846 + }, 1847 + "node_modules/@typescript-eslint/project-service": { 1848 + "version": "8.50.0", 1849 + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", 1850 + "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", 1851 + "dev": true, 1852 + "license": "MIT", 1853 + "dependencies": { 1854 + "@typescript-eslint/tsconfig-utils": "^8.50.0", 1855 + "@typescript-eslint/types": "^8.50.0", 1856 + "debug": "^4.3.4" 1857 + }, 1858 + "engines": { 1859 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1860 + }, 1861 + "funding": { 1862 + "type": "opencollective", 1863 + "url": "https://opencollective.com/typescript-eslint" 1864 + }, 1865 + "peerDependencies": { 1866 + "typescript": ">=4.8.4 <6.0.0" 1867 + } 1868 + }, 1869 + "node_modules/@typescript-eslint/scope-manager": { 1870 + "version": "8.50.0", 1871 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", 1872 + "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", 1873 + "dev": true, 1874 + "license": "MIT", 1875 + "dependencies": { 1876 + "@typescript-eslint/types": "8.50.0", 1877 + "@typescript-eslint/visitor-keys": "8.50.0" 1878 + }, 1879 + "engines": { 1880 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1881 + }, 1882 + "funding": { 1883 + "type": "opencollective", 1884 + "url": "https://opencollective.com/typescript-eslint" 1885 + } 1886 + }, 1887 + "node_modules/@typescript-eslint/tsconfig-utils": { 1888 + "version": "8.50.0", 1889 + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", 1890 + "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", 1891 + "dev": true, 1892 + "license": "MIT", 1893 + "engines": { 1894 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1895 + }, 1896 + "funding": { 1897 + "type": "opencollective", 1898 + "url": "https://opencollective.com/typescript-eslint" 1899 + }, 1900 + "peerDependencies": { 1901 + "typescript": ">=4.8.4 <6.0.0" 1902 + } 1903 + }, 1904 + "node_modules/@typescript-eslint/type-utils": { 1905 + "version": "8.50.0", 1906 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", 1907 + "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", 1908 + "dev": true, 1909 + "license": "MIT", 1910 + "dependencies": { 1911 + "@typescript-eslint/types": "8.50.0", 1912 + "@typescript-eslint/typescript-estree": "8.50.0", 1913 + "@typescript-eslint/utils": "8.50.0", 1914 + "debug": "^4.3.4", 1915 + "ts-api-utils": "^2.1.0" 1916 + }, 1917 + "engines": { 1918 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1919 + }, 1920 + "funding": { 1921 + "type": "opencollective", 1922 + "url": "https://opencollective.com/typescript-eslint" 1923 + }, 1924 + "peerDependencies": { 1925 + "eslint": "^8.57.0 || ^9.0.0", 1926 + "typescript": ">=4.8.4 <6.0.0" 1927 + } 1928 + }, 1929 + "node_modules/@typescript-eslint/types": { 1930 + "version": "8.50.0", 1931 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", 1932 + "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", 1933 + "dev": true, 1934 + "license": "MIT", 1935 + "engines": { 1936 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1937 + }, 1938 + "funding": { 1939 + "type": "opencollective", 1940 + "url": "https://opencollective.com/typescript-eslint" 1941 + } 1942 + }, 1943 + "node_modules/@typescript-eslint/typescript-estree": { 1944 + "version": "8.50.0", 1945 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", 1946 + "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", 1947 + "dev": true, 1948 + "license": "MIT", 1949 + "dependencies": { 1950 + "@typescript-eslint/project-service": "8.50.0", 1951 + "@typescript-eslint/tsconfig-utils": "8.50.0", 1952 + "@typescript-eslint/types": "8.50.0", 1953 + "@typescript-eslint/visitor-keys": "8.50.0", 1954 + "debug": "^4.3.4", 1955 + "minimatch": "^9.0.4", 1956 + "semver": "^7.6.0", 1957 + "tinyglobby": "^0.2.15", 1958 + "ts-api-utils": "^2.1.0" 1959 + }, 1960 + "engines": { 1961 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1962 + }, 1963 + "funding": { 1964 + "type": "opencollective", 1965 + "url": "https://opencollective.com/typescript-eslint" 1966 + }, 1967 + "peerDependencies": { 1968 + "typescript": ">=4.8.4 <6.0.0" 1969 + } 1970 + }, 1971 + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 1972 + "version": "2.0.2", 1973 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1974 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1975 + "dev": true, 1976 + "license": "MIT", 1977 + "dependencies": { 1978 + "balanced-match": "^1.0.0" 1979 + } 1980 + }, 1981 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1982 + "version": "9.0.5", 1983 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1984 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1985 + "dev": true, 1986 + "license": "ISC", 1987 + "dependencies": { 1988 + "brace-expansion": "^2.0.1" 1989 + }, 1990 + "engines": { 1991 + "node": ">=16 || 14 >=14.17" 1992 + }, 1993 + "funding": { 1994 + "url": "https://github.com/sponsors/isaacs" 1995 + } 1996 + }, 1997 + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1998 + "version": "7.7.3", 1999 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 2000 + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 2001 + "dev": true, 2002 + "license": "ISC", 2003 + "bin": { 2004 + "semver": "bin/semver.js" 2005 + }, 2006 + "engines": { 2007 + "node": ">=10" 2008 + } 2009 + }, 2010 + "node_modules/@typescript-eslint/utils": { 2011 + "version": "8.50.0", 2012 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", 2013 + "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", 2014 + "dev": true, 2015 + "license": "MIT", 2016 + "dependencies": { 2017 + "@eslint-community/eslint-utils": "^4.7.0", 2018 + "@typescript-eslint/scope-manager": "8.50.0", 2019 + "@typescript-eslint/types": "8.50.0", 2020 + "@typescript-eslint/typescript-estree": "8.50.0" 2021 + }, 2022 + "engines": { 2023 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2024 + }, 2025 + "funding": { 2026 + "type": "opencollective", 2027 + "url": "https://opencollective.com/typescript-eslint" 2028 + }, 2029 + "peerDependencies": { 2030 + "eslint": "^8.57.0 || ^9.0.0", 2031 + "typescript": ">=4.8.4 <6.0.0" 2032 + } 2033 + }, 2034 + "node_modules/@typescript-eslint/visitor-keys": { 2035 + "version": "8.50.0", 2036 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", 2037 + "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", 2038 + "dev": true, 2039 + "license": "MIT", 2040 + "dependencies": { 2041 + "@typescript-eslint/types": "8.50.0", 2042 + "eslint-visitor-keys": "^4.2.1" 2043 + }, 2044 + "engines": { 2045 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2046 + }, 2047 + "funding": { 2048 + "type": "opencollective", 2049 + "url": "https://opencollective.com/typescript-eslint" 2050 + } 2051 + }, 1557 2052 "node_modules/@vitejs/plugin-react": { 1558 2053 "version": "5.1.2", 1559 2054 "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", ··· 1960 2455 "acorn": "^8.14.0" 1961 2456 } 1962 2457 }, 2458 + "node_modules/acorn-jsx": { 2459 + "version": "5.3.2", 2460 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 2461 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 2462 + "dev": true, 2463 + "license": "MIT", 2464 + "peerDependencies": { 2465 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 2466 + } 2467 + }, 1963 2468 "node_modules/acorn-loose": { 1964 2469 "version": "8.5.2", 1965 2470 "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.5.2.tgz", ··· 2030 2535 "ajv": "^8.8.2" 2031 2536 } 2032 2537 }, 2538 + "node_modules/ansi-escapes": { 2539 + "version": "7.2.0", 2540 + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", 2541 + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", 2542 + "dev": true, 2543 + "license": "MIT", 2544 + "dependencies": { 2545 + "environment": "^1.0.0" 2546 + }, 2547 + "engines": { 2548 + "node": ">=18" 2549 + }, 2550 + "funding": { 2551 + "url": "https://github.com/sponsors/sindresorhus" 2552 + } 2553 + }, 2554 + "node_modules/ansi-regex": { 2555 + "version": "6.2.2", 2556 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 2557 + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 2558 + "dev": true, 2559 + "license": "MIT", 2560 + "engines": { 2561 + "node": ">=12" 2562 + }, 2563 + "funding": { 2564 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 2565 + } 2566 + }, 2567 + "node_modules/ansi-styles": { 2568 + "version": "4.3.0", 2569 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 2570 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 2571 + "dev": true, 2572 + "license": "MIT", 2573 + "dependencies": { 2574 + "color-convert": "^2.0.1" 2575 + }, 2576 + "engines": { 2577 + "node": ">=8" 2578 + }, 2579 + "funding": { 2580 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2581 + } 2582 + }, 2583 + "node_modules/argparse": { 2584 + "version": "2.0.1", 2585 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 2586 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 2587 + "dev": true, 2588 + "license": "Python-2.0" 2589 + }, 2033 2590 "node_modules/assertion-error": { 2034 2591 "version": "2.0.1", 2035 2592 "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", ··· 2040 2597 "node": ">=12" 2041 2598 } 2042 2599 }, 2600 + "node_modules/balanced-match": { 2601 + "version": "1.0.2", 2602 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 2603 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 2604 + "dev": true, 2605 + "license": "MIT" 2606 + }, 2043 2607 "node_modules/baseline-browser-mapping": { 2044 2608 "version": "2.9.7", 2045 2609 "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz", ··· 2056 2620 "dev": true, 2057 2621 "license": "MIT" 2058 2622 }, 2623 + "node_modules/brace-expansion": { 2624 + "version": "1.1.12", 2625 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 2626 + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 2627 + "dev": true, 2628 + "license": "MIT", 2629 + "dependencies": { 2630 + "balanced-match": "^1.0.0", 2631 + "concat-map": "0.0.1" 2632 + } 2633 + }, 2634 + "node_modules/braces": { 2635 + "version": "3.0.3", 2636 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 2637 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 2638 + "dev": true, 2639 + "license": "MIT", 2640 + "dependencies": { 2641 + "fill-range": "^7.1.1" 2642 + }, 2643 + "engines": { 2644 + "node": ">=8" 2645 + } 2646 + }, 2059 2647 "node_modules/browserslist": { 2060 2648 "version": "4.28.1", 2061 2649 "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", ··· 2095 2683 "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 2096 2684 "license": "MIT", 2097 2685 "peer": true 2686 + }, 2687 + "node_modules/callsites": { 2688 + "version": "3.1.0", 2689 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 2690 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 2691 + "dev": true, 2692 + "license": "MIT", 2693 + "engines": { 2694 + "node": ">=6" 2695 + } 2098 2696 }, 2099 2697 "node_modules/caniuse-lite": { 2100 2698 "version": "1.0.30001760", ··· 2126 2724 "node": ">=18" 2127 2725 } 2128 2726 }, 2727 + "node_modules/chalk": { 2728 + "version": "4.1.2", 2729 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 2730 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 2731 + "dev": true, 2732 + "license": "MIT", 2733 + "dependencies": { 2734 + "ansi-styles": "^4.1.0", 2735 + "supports-color": "^7.1.0" 2736 + }, 2737 + "engines": { 2738 + "node": ">=10" 2739 + }, 2740 + "funding": { 2741 + "url": "https://github.com/chalk/chalk?sponsor=1" 2742 + } 2743 + }, 2744 + "node_modules/chalk/node_modules/supports-color": { 2745 + "version": "7.2.0", 2746 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2747 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2748 + "dev": true, 2749 + "license": "MIT", 2750 + "dependencies": { 2751 + "has-flag": "^4.0.0" 2752 + }, 2753 + "engines": { 2754 + "node": ">=8" 2755 + } 2756 + }, 2129 2757 "node_modules/chrome-trace-event": { 2130 2758 "version": "1.0.4", 2131 2759 "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", ··· 2136 2764 "node": ">=6.0" 2137 2765 } 2138 2766 }, 2767 + "node_modules/cli-cursor": { 2768 + "version": "5.0.0", 2769 + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", 2770 + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", 2771 + "dev": true, 2772 + "license": "MIT", 2773 + "dependencies": { 2774 + "restore-cursor": "^5.0.0" 2775 + }, 2776 + "engines": { 2777 + "node": ">=18" 2778 + }, 2779 + "funding": { 2780 + "url": "https://github.com/sponsors/sindresorhus" 2781 + } 2782 + }, 2783 + "node_modules/cli-truncate": { 2784 + "version": "5.1.1", 2785 + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", 2786 + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", 2787 + "dev": true, 2788 + "license": "MIT", 2789 + "dependencies": { 2790 + "slice-ansi": "^7.1.0", 2791 + "string-width": "^8.0.0" 2792 + }, 2793 + "engines": { 2794 + "node": ">=20" 2795 + }, 2796 + "funding": { 2797 + "url": "https://github.com/sponsors/sindresorhus" 2798 + } 2799 + }, 2139 2800 "node_modules/codemirror": { 2140 2801 "version": "6.0.2", 2141 2802 "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", ··· 2196 2857 "simple-swizzle": "^0.2.2" 2197 2858 } 2198 2859 }, 2860 + "node_modules/colorette": { 2861 + "version": "2.0.20", 2862 + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", 2863 + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", 2864 + "dev": true, 2865 + "license": "MIT" 2866 + }, 2199 2867 "node_modules/commander": { 2200 2868 "version": "2.20.3", 2201 2869 "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 2202 2870 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 2203 2871 "license": "MIT", 2204 2872 "peer": true 2873 + }, 2874 + "node_modules/concat-map": { 2875 + "version": "0.0.1", 2876 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 2877 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 2878 + "dev": true, 2879 + "license": "MIT" 2205 2880 }, 2206 2881 "node_modules/convert-source-map": { 2207 2882 "version": "2.0.0", ··· 2216 2891 "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", 2217 2892 "license": "MIT" 2218 2893 }, 2894 + "node_modules/cross-spawn": { 2895 + "version": "7.0.6", 2896 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 2897 + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 2898 + "dev": true, 2899 + "license": "MIT", 2900 + "dependencies": { 2901 + "path-key": "^3.1.0", 2902 + "shebang-command": "^2.0.0", 2903 + "which": "^2.0.1" 2904 + }, 2905 + "engines": { 2906 + "node": ">= 8" 2907 + } 2908 + }, 2219 2909 "node_modules/debug": { 2220 2910 "version": "4.4.3", 2221 2911 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", ··· 2234 2924 } 2235 2925 } 2236 2926 }, 2927 + "node_modules/deep-is": { 2928 + "version": "0.1.4", 2929 + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 2930 + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 2931 + "dev": true, 2932 + "license": "MIT" 2933 + }, 2237 2934 "node_modules/detect-libc": { 2238 2935 "version": "2.1.2", 2239 2936 "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", ··· 2250 2947 "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", 2251 2948 "license": "ISC" 2252 2949 }, 2950 + "node_modules/emoji-regex": { 2951 + "version": "10.6.0", 2952 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", 2953 + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", 2954 + "dev": true, 2955 + "license": "MIT" 2956 + }, 2253 2957 "node_modules/enhanced-resolve": { 2254 2958 "version": "5.18.4", 2255 2959 "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", ··· 2262 2966 }, 2263 2967 "engines": { 2264 2968 "node": ">=10.13.0" 2969 + } 2970 + }, 2971 + "node_modules/environment": { 2972 + "version": "1.1.0", 2973 + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", 2974 + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", 2975 + "dev": true, 2976 + "license": "MIT", 2977 + "engines": { 2978 + "node": ">=18" 2979 + }, 2980 + "funding": { 2981 + "url": "https://github.com/sponsors/sindresorhus" 2265 2982 } 2266 2983 }, 2267 2984 "node_modules/error-stack-parser-es": { ··· 2289 3006 "node": ">=6" 2290 3007 } 2291 3008 }, 3009 + "node_modules/escape-string-regexp": { 3010 + "version": "4.0.0", 3011 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 3012 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 3013 + "dev": true, 3014 + "license": "MIT", 3015 + "engines": { 3016 + "node": ">=10" 3017 + }, 3018 + "funding": { 3019 + "url": "https://github.com/sponsors/sindresorhus" 3020 + } 3021 + }, 3022 + "node_modules/eslint": { 3023 + "version": "9.39.2", 3024 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", 3025 + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", 3026 + "dev": true, 3027 + "license": "MIT", 3028 + "dependencies": { 3029 + "@eslint-community/eslint-utils": "^4.8.0", 3030 + "@eslint-community/regexpp": "^4.12.1", 3031 + "@eslint/config-array": "^0.21.1", 3032 + "@eslint/config-helpers": "^0.4.2", 3033 + "@eslint/core": "^0.17.0", 3034 + "@eslint/eslintrc": "^3.3.1", 3035 + "@eslint/js": "9.39.2", 3036 + "@eslint/plugin-kit": "^0.4.1", 3037 + "@humanfs/node": "^0.16.6", 3038 + "@humanwhocodes/module-importer": "^1.0.1", 3039 + "@humanwhocodes/retry": "^0.4.2", 3040 + "@types/estree": "^1.0.6", 3041 + "ajv": "^6.12.4", 3042 + "chalk": "^4.0.0", 3043 + "cross-spawn": "^7.0.6", 3044 + "debug": "^4.3.2", 3045 + "escape-string-regexp": "^4.0.0", 3046 + "eslint-scope": "^8.4.0", 3047 + "eslint-visitor-keys": "^4.2.1", 3048 + "espree": "^10.4.0", 3049 + "esquery": "^1.5.0", 3050 + "esutils": "^2.0.2", 3051 + "fast-deep-equal": "^3.1.3", 3052 + "file-entry-cache": "^8.0.0", 3053 + "find-up": "^5.0.0", 3054 + "glob-parent": "^6.0.2", 3055 + "ignore": "^5.2.0", 3056 + "imurmurhash": "^0.1.4", 3057 + "is-glob": "^4.0.0", 3058 + "json-stable-stringify-without-jsonify": "^1.0.1", 3059 + "lodash.merge": "^4.6.2", 3060 + "minimatch": "^3.1.2", 3061 + "natural-compare": "^1.4.0", 3062 + "optionator": "^0.9.3" 3063 + }, 3064 + "bin": { 3065 + "eslint": "bin/eslint.js" 3066 + }, 3067 + "engines": { 3068 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3069 + }, 3070 + "funding": { 3071 + "url": "https://eslint.org/donate" 3072 + }, 3073 + "peerDependencies": { 3074 + "jiti": "*" 3075 + }, 3076 + "peerDependenciesMeta": { 3077 + "jiti": { 3078 + "optional": true 3079 + } 3080 + } 3081 + }, 3082 + "node_modules/eslint-plugin-react-hooks": { 3083 + "version": "7.0.1", 3084 + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", 3085 + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", 3086 + "dev": true, 3087 + "license": "MIT", 3088 + "dependencies": { 3089 + "@babel/core": "^7.24.4", 3090 + "@babel/parser": "^7.24.4", 3091 + "hermes-parser": "^0.25.1", 3092 + "zod": "^3.25.0 || ^4.0.0", 3093 + "zod-validation-error": "^3.5.0 || ^4.0.0" 3094 + }, 3095 + "engines": { 3096 + "node": ">=18" 3097 + }, 3098 + "peerDependencies": { 3099 + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" 3100 + } 3101 + }, 3102 + "node_modules/eslint-plugin-react-hooks/node_modules/zod": { 3103 + "version": "4.2.1", 3104 + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", 3105 + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", 3106 + "dev": true, 3107 + "license": "MIT", 3108 + "funding": { 3109 + "url": "https://github.com/sponsors/colinhacks" 3110 + } 3111 + }, 3112 + "node_modules/eslint-plugin-react-hooks/node_modules/zod-validation-error": { 3113 + "version": "4.0.2", 3114 + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", 3115 + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", 3116 + "dev": true, 3117 + "license": "MIT", 3118 + "engines": { 3119 + "node": ">=18.0.0" 3120 + }, 3121 + "peerDependencies": { 3122 + "zod": "^3.25.0 || ^4.0.0" 3123 + } 3124 + }, 2292 3125 "node_modules/eslint-scope": { 2293 3126 "version": "5.1.1", 2294 3127 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", ··· 2303 3136 "node": ">=8.0.0" 2304 3137 } 2305 3138 }, 3139 + "node_modules/eslint-visitor-keys": { 3140 + "version": "4.2.1", 3141 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 3142 + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 3143 + "dev": true, 3144 + "license": "Apache-2.0", 3145 + "engines": { 3146 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3147 + }, 3148 + "funding": { 3149 + "url": "https://opencollective.com/eslint" 3150 + } 3151 + }, 3152 + "node_modules/eslint/node_modules/ajv": { 3153 + "version": "6.12.6", 3154 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 3155 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 3156 + "dev": true, 3157 + "license": "MIT", 3158 + "dependencies": { 3159 + "fast-deep-equal": "^3.1.1", 3160 + "fast-json-stable-stringify": "^2.0.0", 3161 + "json-schema-traverse": "^0.4.1", 3162 + "uri-js": "^4.2.2" 3163 + }, 3164 + "funding": { 3165 + "type": "github", 3166 + "url": "https://github.com/sponsors/epoberezkin" 3167 + } 3168 + }, 3169 + "node_modules/eslint/node_modules/eslint-scope": { 3170 + "version": "8.4.0", 3171 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 3172 + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 3173 + "dev": true, 3174 + "license": "BSD-2-Clause", 3175 + "dependencies": { 3176 + "esrecurse": "^4.3.0", 3177 + "estraverse": "^5.2.0" 3178 + }, 3179 + "engines": { 3180 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3181 + }, 3182 + "funding": { 3183 + "url": "https://opencollective.com/eslint" 3184 + } 3185 + }, 3186 + "node_modules/eslint/node_modules/estraverse": { 3187 + "version": "5.3.0", 3188 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 3189 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 3190 + "dev": true, 3191 + "license": "BSD-2-Clause", 3192 + "engines": { 3193 + "node": ">=4.0" 3194 + } 3195 + }, 3196 + "node_modules/eslint/node_modules/json-schema-traverse": { 3197 + "version": "0.4.1", 3198 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 3199 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 3200 + "dev": true, 3201 + "license": "MIT" 3202 + }, 3203 + "node_modules/espree": { 3204 + "version": "10.4.0", 3205 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 3206 + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 3207 + "dev": true, 3208 + "license": "BSD-2-Clause", 3209 + "dependencies": { 3210 + "acorn": "^8.15.0", 3211 + "acorn-jsx": "^5.3.2", 3212 + "eslint-visitor-keys": "^4.2.1" 3213 + }, 3214 + "engines": { 3215 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3216 + }, 3217 + "funding": { 3218 + "url": "https://opencollective.com/eslint" 3219 + } 3220 + }, 3221 + "node_modules/esquery": { 3222 + "version": "1.6.0", 3223 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 3224 + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 3225 + "dev": true, 3226 + "license": "BSD-3-Clause", 3227 + "dependencies": { 3228 + "estraverse": "^5.1.0" 3229 + }, 3230 + "engines": { 3231 + "node": ">=0.10" 3232 + } 3233 + }, 3234 + "node_modules/esquery/node_modules/estraverse": { 3235 + "version": "5.3.0", 3236 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 3237 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 3238 + "dev": true, 3239 + "license": "BSD-2-Clause", 3240 + "engines": { 3241 + "node": ">=4.0" 3242 + } 3243 + }, 2306 3244 "node_modules/esrecurse": { 2307 3245 "version": "4.3.0", 2308 3246 "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 2309 3247 "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 2310 3248 "license": "BSD-2-Clause", 2311 - "peer": true, 2312 3249 "dependencies": { 2313 3250 "estraverse": "^5.2.0" 2314 3251 }, ··· 2321 3258 "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 2322 3259 "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 2323 3260 "license": "BSD-2-Clause", 2324 - "peer": true, 2325 3261 "engines": { 2326 3262 "node": ">=4.0" 2327 3263 } ··· 2346 3282 "@types/estree": "^1.0.0" 2347 3283 } 2348 3284 }, 3285 + "node_modules/esutils": { 3286 + "version": "2.0.3", 3287 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 3288 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 3289 + "dev": true, 3290 + "license": "BSD-2-Clause", 3291 + "engines": { 3292 + "node": ">=0.10.0" 3293 + } 3294 + }, 3295 + "node_modules/eventemitter3": { 3296 + "version": "5.0.1", 3297 + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", 3298 + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", 3299 + "dev": true, 3300 + "license": "MIT" 3301 + }, 2349 3302 "node_modules/events": { 2350 3303 "version": "3.3.0", 2351 3304 "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", ··· 2383 3336 "version": "3.1.3", 2384 3337 "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 2385 3338 "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 2386 - "license": "MIT", 2387 - "peer": true 3339 + "license": "MIT" 3340 + }, 3341 + "node_modules/fast-json-stable-stringify": { 3342 + "version": "2.1.0", 3343 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 3344 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 3345 + "dev": true, 3346 + "license": "MIT" 3347 + }, 3348 + "node_modules/fast-levenshtein": { 3349 + "version": "2.0.6", 3350 + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 3351 + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 3352 + "dev": true, 3353 + "license": "MIT" 2388 3354 }, 2389 3355 "node_modules/fast-uri": { 2390 3356 "version": "3.1.0", ··· 2421 3387 } 2422 3388 } 2423 3389 }, 3390 + "node_modules/file-entry-cache": { 3391 + "version": "8.0.0", 3392 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 3393 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 3394 + "dev": true, 3395 + "license": "MIT", 3396 + "dependencies": { 3397 + "flat-cache": "^4.0.0" 3398 + }, 3399 + "engines": { 3400 + "node": ">=16.0.0" 3401 + } 3402 + }, 3403 + "node_modules/fill-range": { 3404 + "version": "7.1.1", 3405 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 3406 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 3407 + "dev": true, 3408 + "license": "MIT", 3409 + "dependencies": { 3410 + "to-regex-range": "^5.0.1" 3411 + }, 3412 + "engines": { 3413 + "node": ">=8" 3414 + } 3415 + }, 3416 + "node_modules/find-up": { 3417 + "version": "5.0.0", 3418 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 3419 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 3420 + "dev": true, 3421 + "license": "MIT", 3422 + "dependencies": { 3423 + "locate-path": "^6.0.0", 3424 + "path-exists": "^4.0.0" 3425 + }, 3426 + "engines": { 3427 + "node": ">=10" 3428 + }, 3429 + "funding": { 3430 + "url": "https://github.com/sponsors/sindresorhus" 3431 + } 3432 + }, 3433 + "node_modules/flat-cache": { 3434 + "version": "4.0.1", 3435 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 3436 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 3437 + "dev": true, 3438 + "license": "MIT", 3439 + "dependencies": { 3440 + "flatted": "^3.2.9", 3441 + "keyv": "^4.5.4" 3442 + }, 3443 + "engines": { 3444 + "node": ">=16" 3445 + } 3446 + }, 3447 + "node_modules/flatted": { 3448 + "version": "3.3.3", 3449 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 3450 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 3451 + "dev": true, 3452 + "license": "ISC" 3453 + }, 2424 3454 "node_modules/fsevents": { 2425 3455 "version": "2.3.2", 2426 3456 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", ··· 2446 3476 "node": ">=6.9.0" 2447 3477 } 2448 3478 }, 3479 + "node_modules/get-east-asian-width": { 3480 + "version": "1.4.0", 3481 + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", 3482 + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", 3483 + "dev": true, 3484 + "license": "MIT", 3485 + "engines": { 3486 + "node": ">=18" 3487 + }, 3488 + "funding": { 3489 + "url": "https://github.com/sponsors/sindresorhus" 3490 + } 3491 + }, 3492 + "node_modules/glob-parent": { 3493 + "version": "6.0.2", 3494 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 3495 + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 3496 + "dev": true, 3497 + "license": "ISC", 3498 + "dependencies": { 3499 + "is-glob": "^4.0.3" 3500 + }, 3501 + "engines": { 3502 + "node": ">=10.13.0" 3503 + } 3504 + }, 2449 3505 "node_modules/glob-to-regexp": { 2450 3506 "version": "0.4.1", 2451 3507 "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", 2452 3508 "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", 2453 3509 "license": "BSD-2-Clause" 2454 3510 }, 3511 + "node_modules/globals": { 3512 + "version": "14.0.0", 3513 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 3514 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 3515 + "dev": true, 3516 + "license": "MIT", 3517 + "engines": { 3518 + "node": ">=18" 3519 + }, 3520 + "funding": { 3521 + "url": "https://github.com/sponsors/sindresorhus" 3522 + } 3523 + }, 2455 3524 "node_modules/graceful-fs": { 2456 3525 "version": "4.2.11", 2457 3526 "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", ··· 2464 3533 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 2465 3534 "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 2466 3535 "license": "MIT", 2467 - "peer": true, 2468 3536 "engines": { 2469 3537 "node": ">=8" 2470 3538 } 2471 3539 }, 3540 + "node_modules/hermes-estree": { 3541 + "version": "0.25.1", 3542 + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", 3543 + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", 3544 + "dev": true, 3545 + "license": "MIT" 3546 + }, 3547 + "node_modules/hermes-parser": { 3548 + "version": "0.25.1", 3549 + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", 3550 + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", 3551 + "dev": true, 3552 + "license": "MIT", 3553 + "dependencies": { 3554 + "hermes-estree": "0.25.1" 3555 + } 3556 + }, 3557 + "node_modules/husky": { 3558 + "version": "9.1.7", 3559 + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", 3560 + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", 3561 + "dev": true, 3562 + "license": "MIT", 3563 + "bin": { 3564 + "husky": "bin.js" 3565 + }, 3566 + "engines": { 3567 + "node": ">=18" 3568 + }, 3569 + "funding": { 3570 + "url": "https://github.com/sponsors/typicode" 3571 + } 3572 + }, 3573 + "node_modules/ignore": { 3574 + "version": "5.3.2", 3575 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 3576 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 3577 + "dev": true, 3578 + "license": "MIT", 3579 + "engines": { 3580 + "node": ">= 4" 3581 + } 3582 + }, 3583 + "node_modules/import-fresh": { 3584 + "version": "3.3.1", 3585 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 3586 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 3587 + "dev": true, 3588 + "license": "MIT", 3589 + "dependencies": { 3590 + "parent-module": "^1.0.0", 3591 + "resolve-from": "^4.0.0" 3592 + }, 3593 + "engines": { 3594 + "node": ">=6" 3595 + }, 3596 + "funding": { 3597 + "url": "https://github.com/sponsors/sindresorhus" 3598 + } 3599 + }, 3600 + "node_modules/imurmurhash": { 3601 + "version": "0.1.4", 3602 + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 3603 + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 3604 + "dev": true, 3605 + "license": "MIT", 3606 + "engines": { 3607 + "node": ">=0.8.19" 3608 + } 3609 + }, 2472 3610 "node_modules/is-arrayish": { 2473 3611 "version": "0.3.4", 2474 3612 "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", ··· 2476 3614 "dev": true, 2477 3615 "license": "MIT" 2478 3616 }, 3617 + "node_modules/is-extglob": { 3618 + "version": "2.1.1", 3619 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 3620 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 3621 + "dev": true, 3622 + "license": "MIT", 3623 + "engines": { 3624 + "node": ">=0.10.0" 3625 + } 3626 + }, 3627 + "node_modules/is-fullwidth-code-point": { 3628 + "version": "5.1.0", 3629 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", 3630 + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", 3631 + "dev": true, 3632 + "license": "MIT", 3633 + "dependencies": { 3634 + "get-east-asian-width": "^1.3.1" 3635 + }, 3636 + "engines": { 3637 + "node": ">=18" 3638 + }, 3639 + "funding": { 3640 + "url": "https://github.com/sponsors/sindresorhus" 3641 + } 3642 + }, 3643 + "node_modules/is-glob": { 3644 + "version": "4.0.3", 3645 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 3646 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 3647 + "dev": true, 3648 + "license": "MIT", 3649 + "dependencies": { 3650 + "is-extglob": "^2.1.1" 3651 + }, 3652 + "engines": { 3653 + "node": ">=0.10.0" 3654 + } 3655 + }, 3656 + "node_modules/is-number": { 3657 + "version": "7.0.0", 3658 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 3659 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 3660 + "dev": true, 3661 + "license": "MIT", 3662 + "engines": { 3663 + "node": ">=0.12.0" 3664 + } 3665 + }, 3666 + "node_modules/isexe": { 3667 + "version": "2.0.0", 3668 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 3669 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 3670 + "dev": true, 3671 + "license": "ISC" 3672 + }, 2479 3673 "node_modules/jest-worker": { 2480 3674 "version": "27.5.1", 2481 3675 "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", ··· 2498 3692 "dev": true, 2499 3693 "license": "MIT" 2500 3694 }, 3695 + "node_modules/js-yaml": { 3696 + "version": "4.1.1", 3697 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", 3698 + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", 3699 + "dev": true, 3700 + "license": "MIT", 3701 + "dependencies": { 3702 + "argparse": "^2.0.1" 3703 + }, 3704 + "bin": { 3705 + "js-yaml": "bin/js-yaml.js" 3706 + } 3707 + }, 2501 3708 "node_modules/jsesc": { 2502 3709 "version": "3.1.0", 2503 3710 "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", ··· 2511 3718 "node": ">=6" 2512 3719 } 2513 3720 }, 3721 + "node_modules/json-buffer": { 3722 + "version": "3.0.1", 3723 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 3724 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 3725 + "dev": true, 3726 + "license": "MIT" 3727 + }, 2514 3728 "node_modules/json-parse-even-better-errors": { 2515 3729 "version": "2.3.1", 2516 3730 "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", ··· 2525 3739 "license": "MIT", 2526 3740 "peer": true 2527 3741 }, 3742 + "node_modules/json-stable-stringify-without-jsonify": { 3743 + "version": "1.0.1", 3744 + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 3745 + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 3746 + "dev": true, 3747 + "license": "MIT" 3748 + }, 2528 3749 "node_modules/json5": { 2529 3750 "version": "2.2.3", 2530 3751 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", ··· 2536 3757 }, 2537 3758 "engines": { 2538 3759 "node": ">=6" 3760 + } 3761 + }, 3762 + "node_modules/keyv": { 3763 + "version": "4.5.4", 3764 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 3765 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 3766 + "dev": true, 3767 + "license": "MIT", 3768 + "dependencies": { 3769 + "json-buffer": "3.0.1" 2539 3770 } 2540 3771 }, 2541 3772 "node_modules/kleur": { ··· 2546 3777 "license": "MIT", 2547 3778 "engines": { 2548 3779 "node": ">=6" 3780 + } 3781 + }, 3782 + "node_modules/levn": { 3783 + "version": "0.4.1", 3784 + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 3785 + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 3786 + "dev": true, 3787 + "license": "MIT", 3788 + "dependencies": { 3789 + "prelude-ls": "^1.2.1", 3790 + "type-check": "~0.4.0" 3791 + }, 3792 + "engines": { 3793 + "node": ">= 0.8.0" 2549 3794 } 2550 3795 }, 2551 3796 "node_modules/lightningcss": { ··· 2809 4054 "url": "https://opencollective.com/parcel" 2810 4055 } 2811 4056 }, 4057 + "node_modules/lint-staged": { 4058 + "version": "16.2.7", 4059 + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", 4060 + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", 4061 + "dev": true, 4062 + "license": "MIT", 4063 + "dependencies": { 4064 + "commander": "^14.0.2", 4065 + "listr2": "^9.0.5", 4066 + "micromatch": "^4.0.8", 4067 + "nano-spawn": "^2.0.0", 4068 + "pidtree": "^0.6.0", 4069 + "string-argv": "^0.3.2", 4070 + "yaml": "^2.8.1" 4071 + }, 4072 + "bin": { 4073 + "lint-staged": "bin/lint-staged.js" 4074 + }, 4075 + "engines": { 4076 + "node": ">=20.17" 4077 + }, 4078 + "funding": { 4079 + "url": "https://opencollective.com/lint-staged" 4080 + } 4081 + }, 4082 + "node_modules/lint-staged/node_modules/commander": { 4083 + "version": "14.0.2", 4084 + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", 4085 + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", 4086 + "dev": true, 4087 + "license": "MIT", 4088 + "engines": { 4089 + "node": ">=20" 4090 + } 4091 + }, 4092 + "node_modules/listr2": { 4093 + "version": "9.0.5", 4094 + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", 4095 + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", 4096 + "dev": true, 4097 + "license": "MIT", 4098 + "dependencies": { 4099 + "cli-truncate": "^5.0.0", 4100 + "colorette": "^2.0.20", 4101 + "eventemitter3": "^5.0.1", 4102 + "log-update": "^6.1.0", 4103 + "rfdc": "^1.4.1", 4104 + "wrap-ansi": "^9.0.0" 4105 + }, 4106 + "engines": { 4107 + "node": ">=20.0.0" 4108 + } 4109 + }, 2812 4110 "node_modules/loader-runner": { 2813 4111 "version": "4.3.1", 2814 4112 "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", ··· 2823 4121 "url": "https://opencollective.com/webpack" 2824 4122 } 2825 4123 }, 4124 + "node_modules/locate-path": { 4125 + "version": "6.0.0", 4126 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 4127 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 4128 + "dev": true, 4129 + "license": "MIT", 4130 + "dependencies": { 4131 + "p-locate": "^5.0.0" 4132 + }, 4133 + "engines": { 4134 + "node": ">=10" 4135 + }, 4136 + "funding": { 4137 + "url": "https://github.com/sponsors/sindresorhus" 4138 + } 4139 + }, 4140 + "node_modules/lodash.merge": { 4141 + "version": "4.6.2", 4142 + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 4143 + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 4144 + "dev": true, 4145 + "license": "MIT" 4146 + }, 4147 + "node_modules/log-update": { 4148 + "version": "6.1.0", 4149 + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", 4150 + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", 4151 + "dev": true, 4152 + "license": "MIT", 4153 + "dependencies": { 4154 + "ansi-escapes": "^7.0.0", 4155 + "cli-cursor": "^5.0.0", 4156 + "slice-ansi": "^7.1.0", 4157 + "strip-ansi": "^7.1.0", 4158 + "wrap-ansi": "^9.0.0" 4159 + }, 4160 + "engines": { 4161 + "node": ">=18" 4162 + }, 4163 + "funding": { 4164 + "url": "https://github.com/sponsors/sindresorhus" 4165 + } 4166 + }, 2826 4167 "node_modules/lru-cache": { 2827 4168 "version": "5.1.1", 2828 4169 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", ··· 2850 4191 "license": "MIT", 2851 4192 "peer": true 2852 4193 }, 4194 + "node_modules/micromatch": { 4195 + "version": "4.0.8", 4196 + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 4197 + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 4198 + "dev": true, 4199 + "license": "MIT", 4200 + "dependencies": { 4201 + "braces": "^3.0.3", 4202 + "picomatch": "^2.3.1" 4203 + }, 4204 + "engines": { 4205 + "node": ">=8.6" 4206 + } 4207 + }, 4208 + "node_modules/micromatch/node_modules/picomatch": { 4209 + "version": "2.3.1", 4210 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 4211 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 4212 + "dev": true, 4213 + "license": "MIT", 4214 + "engines": { 4215 + "node": ">=8.6" 4216 + }, 4217 + "funding": { 4218 + "url": "https://github.com/sponsors/jonschlinkert" 4219 + } 4220 + }, 2853 4221 "node_modules/mime-db": { 2854 4222 "version": "1.52.0", 2855 4223 "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", ··· 2873 4241 "node": ">= 0.6" 2874 4242 } 2875 4243 }, 4244 + "node_modules/mimic-function": { 4245 + "version": "5.0.1", 4246 + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", 4247 + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", 4248 + "dev": true, 4249 + "license": "MIT", 4250 + "engines": { 4251 + "node": ">=18" 4252 + }, 4253 + "funding": { 4254 + "url": "https://github.com/sponsors/sindresorhus" 4255 + } 4256 + }, 2876 4257 "node_modules/miniflare": { 2877 4258 "version": "4.20251210.0", 2878 4259 "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251210.0.tgz", ··· 2935 4316 } 2936 4317 } 2937 4318 }, 4319 + "node_modules/minimatch": { 4320 + "version": "3.1.2", 4321 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 4322 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 4323 + "dev": true, 4324 + "license": "ISC", 4325 + "dependencies": { 4326 + "brace-expansion": "^1.1.7" 4327 + }, 4328 + "engines": { 4329 + "node": "*" 4330 + } 4331 + }, 2938 4332 "node_modules/mrmime": { 2939 4333 "version": "2.0.1", 2940 4334 "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", ··· 2952 4346 "dev": true, 2953 4347 "license": "MIT" 2954 4348 }, 4349 + "node_modules/nano-spawn": { 4350 + "version": "2.0.0", 4351 + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", 4352 + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", 4353 + "dev": true, 4354 + "license": "MIT", 4355 + "engines": { 4356 + "node": ">=20.17" 4357 + }, 4358 + "funding": { 4359 + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" 4360 + } 4361 + }, 2955 4362 "node_modules/nanoid": { 2956 4363 "version": "3.3.11", 2957 4364 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", ··· 2970 4377 "engines": { 2971 4378 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2972 4379 } 4380 + }, 4381 + "node_modules/natural-compare": { 4382 + "version": "1.4.0", 4383 + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 4384 + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 4385 + "dev": true, 4386 + "license": "MIT" 2973 4387 }, 2974 4388 "node_modules/neo-async": { 2975 4389 "version": "2.6.2", ··· 2994 4408 ], 2995 4409 "license": "MIT" 2996 4410 }, 4411 + "node_modules/onetime": { 4412 + "version": "7.0.0", 4413 + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", 4414 + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", 4415 + "dev": true, 4416 + "license": "MIT", 4417 + "dependencies": { 4418 + "mimic-function": "^5.0.0" 4419 + }, 4420 + "engines": { 4421 + "node": ">=18" 4422 + }, 4423 + "funding": { 4424 + "url": "https://github.com/sponsors/sindresorhus" 4425 + } 4426 + }, 4427 + "node_modules/optionator": { 4428 + "version": "0.9.4", 4429 + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 4430 + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 4431 + "dev": true, 4432 + "license": "MIT", 4433 + "dependencies": { 4434 + "deep-is": "^0.1.3", 4435 + "fast-levenshtein": "^2.0.6", 4436 + "levn": "^0.4.1", 4437 + "prelude-ls": "^1.2.1", 4438 + "type-check": "^0.4.0", 4439 + "word-wrap": "^1.2.5" 4440 + }, 4441 + "engines": { 4442 + "node": ">= 0.8.0" 4443 + } 4444 + }, 4445 + "node_modules/p-limit": { 4446 + "version": "3.1.0", 4447 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 4448 + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 4449 + "dev": true, 4450 + "license": "MIT", 4451 + "dependencies": { 4452 + "yocto-queue": "^0.1.0" 4453 + }, 4454 + "engines": { 4455 + "node": ">=10" 4456 + }, 4457 + "funding": { 4458 + "url": "https://github.com/sponsors/sindresorhus" 4459 + } 4460 + }, 4461 + "node_modules/p-locate": { 4462 + "version": "5.0.0", 4463 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 4464 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 4465 + "dev": true, 4466 + "license": "MIT", 4467 + "dependencies": { 4468 + "p-limit": "^3.0.2" 4469 + }, 4470 + "engines": { 4471 + "node": ">=10" 4472 + }, 4473 + "funding": { 4474 + "url": "https://github.com/sponsors/sindresorhus" 4475 + } 4476 + }, 4477 + "node_modules/parent-module": { 4478 + "version": "1.0.1", 4479 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 4480 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 4481 + "dev": true, 4482 + "license": "MIT", 4483 + "dependencies": { 4484 + "callsites": "^3.0.0" 4485 + }, 4486 + "engines": { 4487 + "node": ">=6" 4488 + } 4489 + }, 4490 + "node_modules/path-exists": { 4491 + "version": "4.0.0", 4492 + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 4493 + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 4494 + "dev": true, 4495 + "license": "MIT", 4496 + "engines": { 4497 + "node": ">=8" 4498 + } 4499 + }, 4500 + "node_modules/path-key": { 4501 + "version": "3.1.1", 4502 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 4503 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 4504 + "dev": true, 4505 + "license": "MIT", 4506 + "engines": { 4507 + "node": ">=8" 4508 + } 4509 + }, 2997 4510 "node_modules/pathe": { 2998 4511 "version": "2.0.3", 2999 4512 "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", ··· 3018 4531 }, 3019 4532 "funding": { 3020 4533 "url": "https://github.com/sponsors/jonschlinkert" 4534 + } 4535 + }, 4536 + "node_modules/pidtree": { 4537 + "version": "0.6.0", 4538 + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", 4539 + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", 4540 + "dev": true, 4541 + "license": "MIT", 4542 + "bin": { 4543 + "pidtree": "bin/pidtree.js" 4544 + }, 4545 + "engines": { 4546 + "node": ">=0.10" 3021 4547 } 3022 4548 }, 3023 4549 "node_modules/pixelmatch": { ··· 3104 4630 "node": "^10 || ^12 || >=14" 3105 4631 } 3106 4632 }, 4633 + "node_modules/prelude-ls": { 4634 + "version": "1.2.1", 4635 + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 4636 + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 4637 + "dev": true, 4638 + "license": "MIT", 4639 + "engines": { 4640 + "node": ">= 0.8.0" 4641 + } 4642 + }, 4643 + "node_modules/prettier": { 4644 + "version": "3.7.4", 4645 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", 4646 + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", 4647 + "dev": true, 4648 + "license": "MIT", 4649 + "bin": { 4650 + "prettier": "bin/prettier.cjs" 4651 + }, 4652 + "engines": { 4653 + "node": ">=14" 4654 + }, 4655 + "funding": { 4656 + "url": "https://github.com/prettier/prettier?sponsor=1" 4657 + } 4658 + }, 4659 + "node_modules/punycode": { 4660 + "version": "2.3.1", 4661 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 4662 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 4663 + "dev": true, 4664 + "license": "MIT", 4665 + "engines": { 4666 + "node": ">=6" 4667 + } 4668 + }, 3107 4669 "node_modules/randombytes": { 3108 4670 "version": "2.1.0", 3109 4671 "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", ··· 3173 4735 "engines": { 3174 4736 "node": ">=0.10.0" 3175 4737 } 4738 + }, 4739 + "node_modules/resolve-from": { 4740 + "version": "4.0.0", 4741 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 4742 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 4743 + "dev": true, 4744 + "license": "MIT", 4745 + "engines": { 4746 + "node": ">=4" 4747 + } 4748 + }, 4749 + "node_modules/restore-cursor": { 4750 + "version": "5.1.0", 4751 + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", 4752 + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", 4753 + "dev": true, 4754 + "license": "MIT", 4755 + "dependencies": { 4756 + "onetime": "^7.0.0", 4757 + "signal-exit": "^4.1.0" 4758 + }, 4759 + "engines": { 4760 + "node": ">=18" 4761 + }, 4762 + "funding": { 4763 + "url": "https://github.com/sponsors/sindresorhus" 4764 + } 4765 + }, 4766 + "node_modules/rfdc": { 4767 + "version": "1.4.1", 4768 + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", 4769 + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", 4770 + "dev": true, 4771 + "license": "MIT" 3176 4772 }, 3177 4773 "node_modules/rolldown": { 3178 4774 "version": "1.0.0-beta.54", ··· 3333 4929 "node": ">=10" 3334 4930 } 3335 4931 }, 4932 + "node_modules/shebang-command": { 4933 + "version": "2.0.0", 4934 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 4935 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 4936 + "dev": true, 4937 + "license": "MIT", 4938 + "dependencies": { 4939 + "shebang-regex": "^3.0.0" 4940 + }, 4941 + "engines": { 4942 + "node": ">=8" 4943 + } 4944 + }, 4945 + "node_modules/shebang-regex": { 4946 + "version": "3.0.0", 4947 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 4948 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 4949 + "dev": true, 4950 + "license": "MIT", 4951 + "engines": { 4952 + "node": ">=8" 4953 + } 4954 + }, 3336 4955 "node_modules/siginfo": { 3337 4956 "version": "2.0.0", 3338 4957 "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", ··· 3340 4959 "dev": true, 3341 4960 "license": "ISC" 3342 4961 }, 4962 + "node_modules/signal-exit": { 4963 + "version": "4.1.0", 4964 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 4965 + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 4966 + "dev": true, 4967 + "license": "ISC", 4968 + "engines": { 4969 + "node": ">=14" 4970 + }, 4971 + "funding": { 4972 + "url": "https://github.com/sponsors/isaacs" 4973 + } 4974 + }, 3343 4975 "node_modules/simple-swizzle": { 3344 4976 "version": "0.2.4", 3345 4977 "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", ··· 3365 4997 "node": ">=18" 3366 4998 } 3367 4999 }, 5000 + "node_modules/slice-ansi": { 5001 + "version": "7.1.2", 5002 + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", 5003 + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", 5004 + "dev": true, 5005 + "license": "MIT", 5006 + "dependencies": { 5007 + "ansi-styles": "^6.2.1", 5008 + "is-fullwidth-code-point": "^5.0.0" 5009 + }, 5010 + "engines": { 5011 + "node": ">=18" 5012 + }, 5013 + "funding": { 5014 + "url": "https://github.com/chalk/slice-ansi?sponsor=1" 5015 + } 5016 + }, 5017 + "node_modules/slice-ansi/node_modules/ansi-styles": { 5018 + "version": "6.2.3", 5019 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", 5020 + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", 5021 + "dev": true, 5022 + "license": "MIT", 5023 + "engines": { 5024 + "node": ">=12" 5025 + }, 5026 + "funding": { 5027 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 5028 + } 5029 + }, 3368 5030 "node_modules/source-map": { 3369 5031 "version": "0.6.1", 3370 5032 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", ··· 3421 5083 "npm": ">=6" 3422 5084 } 3423 5085 }, 5086 + "node_modules/string-argv": { 5087 + "version": "0.3.2", 5088 + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", 5089 + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", 5090 + "dev": true, 5091 + "license": "MIT", 5092 + "engines": { 5093 + "node": ">=0.6.19" 5094 + } 5095 + }, 5096 + "node_modules/string-width": { 5097 + "version": "8.1.0", 5098 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", 5099 + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", 5100 + "dev": true, 5101 + "license": "MIT", 5102 + "dependencies": { 5103 + "get-east-asian-width": "^1.3.0", 5104 + "strip-ansi": "^7.1.0" 5105 + }, 5106 + "engines": { 5107 + "node": ">=20" 5108 + }, 5109 + "funding": { 5110 + "url": "https://github.com/sponsors/sindresorhus" 5111 + } 5112 + }, 5113 + "node_modules/strip-ansi": { 5114 + "version": "7.1.2", 5115 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 5116 + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 5117 + "dev": true, 5118 + "license": "MIT", 5119 + "dependencies": { 5120 + "ansi-regex": "^6.0.1" 5121 + }, 5122 + "engines": { 5123 + "node": ">=12" 5124 + }, 5125 + "funding": { 5126 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 5127 + } 5128 + }, 5129 + "node_modules/strip-json-comments": { 5130 + "version": "3.1.1", 5131 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 5132 + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 5133 + "dev": true, 5134 + "license": "MIT", 5135 + "engines": { 5136 + "node": ">=8" 5137 + }, 5138 + "funding": { 5139 + "url": "https://github.com/sponsors/sindresorhus" 5140 + } 5141 + }, 3424 5142 "node_modules/style-mod": { 3425 5143 "version": "4.1.3", 3426 5144 "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", ··· 3562 5280 "node": ">=14.0.0" 3563 5281 } 3564 5282 }, 5283 + "node_modules/to-regex-range": { 5284 + "version": "5.0.1", 5285 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 5286 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 5287 + "dev": true, 5288 + "license": "MIT", 5289 + "dependencies": { 5290 + "is-number": "^7.0.0" 5291 + }, 5292 + "engines": { 5293 + "node": ">=8.0" 5294 + } 5295 + }, 3565 5296 "node_modules/totalist": { 3566 5297 "version": "3.0.1", 3567 5298 "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", ··· 3572 5303 "node": ">=6" 3573 5304 } 3574 5305 }, 5306 + "node_modules/ts-api-utils": { 5307 + "version": "2.1.0", 5308 + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", 5309 + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", 5310 + "dev": true, 5311 + "license": "MIT", 5312 + "engines": { 5313 + "node": ">=18.12" 5314 + }, 5315 + "peerDependencies": { 5316 + "typescript": ">=4.8.4" 5317 + } 5318 + }, 3575 5319 "node_modules/tslib": { 3576 5320 "version": "2.8.1", 3577 5321 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", ··· 3580 5324 "license": "0BSD", 3581 5325 "optional": true 3582 5326 }, 5327 + "node_modules/type-check": { 5328 + "version": "0.4.0", 5329 + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 5330 + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 5331 + "dev": true, 5332 + "license": "MIT", 5333 + "dependencies": { 5334 + "prelude-ls": "^1.2.1" 5335 + }, 5336 + "engines": { 5337 + "node": ">= 0.8.0" 5338 + } 5339 + }, 5340 + "node_modules/typescript": { 5341 + "version": "5.9.3", 5342 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 5343 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 5344 + "dev": true, 5345 + "license": "Apache-2.0", 5346 + "peer": true, 5347 + "bin": { 5348 + "tsc": "bin/tsc", 5349 + "tsserver": "bin/tsserver" 5350 + }, 5351 + "engines": { 5352 + "node": ">=14.17" 5353 + } 5354 + }, 5355 + "node_modules/typescript-eslint": { 5356 + "version": "8.50.0", 5357 + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz", 5358 + "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==", 5359 + "dev": true, 5360 + "license": "MIT", 5361 + "dependencies": { 5362 + "@typescript-eslint/eslint-plugin": "8.50.0", 5363 + "@typescript-eslint/parser": "8.50.0", 5364 + "@typescript-eslint/typescript-estree": "8.50.0", 5365 + "@typescript-eslint/utils": "8.50.0" 5366 + }, 5367 + "engines": { 5368 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 5369 + }, 5370 + "funding": { 5371 + "type": "opencollective", 5372 + "url": "https://opencollective.com/typescript-eslint" 5373 + }, 5374 + "peerDependencies": { 5375 + "eslint": "^8.57.0 || ^9.0.0", 5376 + "typescript": ">=4.8.4 <6.0.0" 5377 + } 5378 + }, 3583 5379 "node_modules/undici": { 3584 5380 "version": "7.14.0", 3585 5381 "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", ··· 3635 5431 }, 3636 5432 "peerDependencies": { 3637 5433 "browserslist": ">= 4.21.0" 5434 + } 5435 + }, 5436 + "node_modules/uri-js": { 5437 + "version": "4.4.1", 5438 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 5439 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 5440 + "dev": true, 5441 + "license": "BSD-2-Clause", 5442 + "dependencies": { 5443 + "punycode": "^2.1.0" 3638 5444 } 3639 5445 }, 3640 5446 "node_modules/vite": { ··· 3925 5731 "node": ">=10.13.0" 3926 5732 } 3927 5733 }, 5734 + "node_modules/which": { 5735 + "version": "2.0.2", 5736 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 5737 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 5738 + "dev": true, 5739 + "license": "ISC", 5740 + "dependencies": { 5741 + "isexe": "^2.0.0" 5742 + }, 5743 + "bin": { 5744 + "node-which": "bin/node-which" 5745 + }, 5746 + "engines": { 5747 + "node": ">= 8" 5748 + } 5749 + }, 3928 5750 "node_modules/why-is-node-running": { 3929 5751 "version": "2.3.0", 3930 5752 "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", ··· 3940 5762 }, 3941 5763 "engines": { 3942 5764 "node": ">=8" 5765 + } 5766 + }, 5767 + "node_modules/word-wrap": { 5768 + "version": "1.2.5", 5769 + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 5770 + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 5771 + "dev": true, 5772 + "license": "MIT", 5773 + "engines": { 5774 + "node": ">=0.10.0" 3943 5775 } 3944 5776 }, 3945 5777 "node_modules/workerd": { ··· 4489 6321 "dev": true, 4490 6322 "license": "MIT" 4491 6323 }, 6324 + "node_modules/wrap-ansi": { 6325 + "version": "9.0.2", 6326 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", 6327 + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", 6328 + "dev": true, 6329 + "license": "MIT", 6330 + "dependencies": { 6331 + "ansi-styles": "^6.2.1", 6332 + "string-width": "^7.0.0", 6333 + "strip-ansi": "^7.1.0" 6334 + }, 6335 + "engines": { 6336 + "node": ">=18" 6337 + }, 6338 + "funding": { 6339 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 6340 + } 6341 + }, 6342 + "node_modules/wrap-ansi/node_modules/ansi-styles": { 6343 + "version": "6.2.3", 6344 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", 6345 + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", 6346 + "dev": true, 6347 + "license": "MIT", 6348 + "engines": { 6349 + "node": ">=12" 6350 + }, 6351 + "funding": { 6352 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 6353 + } 6354 + }, 6355 + "node_modules/wrap-ansi/node_modules/string-width": { 6356 + "version": "7.2.0", 6357 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 6358 + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 6359 + "dev": true, 6360 + "license": "MIT", 6361 + "dependencies": { 6362 + "emoji-regex": "^10.3.0", 6363 + "get-east-asian-width": "^1.0.0", 6364 + "strip-ansi": "^7.1.0" 6365 + }, 6366 + "engines": { 6367 + "node": ">=18" 6368 + }, 6369 + "funding": { 6370 + "url": "https://github.com/sponsors/sindresorhus" 6371 + } 6372 + }, 4492 6373 "node_modules/ws": { 4493 6374 "version": "8.18.3", 4494 6375 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", ··· 4517 6398 "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 4518 6399 "dev": true, 4519 6400 "license": "ISC" 6401 + }, 6402 + "node_modules/yaml": { 6403 + "version": "2.8.2", 6404 + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", 6405 + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", 6406 + "dev": true, 6407 + "license": "ISC", 6408 + "bin": { 6409 + "yaml": "bin.mjs" 6410 + }, 6411 + "engines": { 6412 + "node": ">= 14.6" 6413 + }, 6414 + "funding": { 6415 + "url": "https://github.com/sponsors/eemeli" 6416 + } 6417 + }, 6418 + "node_modules/yocto-queue": { 6419 + "version": "0.1.0", 6420 + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 6421 + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 6422 + "dev": true, 6423 + "license": "MIT", 6424 + "engines": { 6425 + "node": ">=10" 6426 + }, 6427 + "funding": { 6428 + "url": "https://github.com/sponsors/sindresorhus" 6429 + } 4520 6430 }, 4521 6431 "node_modules/youch": { 4522 6432 "version": "4.1.0-beta.10",
+15 -1
package.json
··· 11 11 "dev": "vite", 12 12 "preview": "vite preview", 13 13 "deploy": "npm install && npm test && npm run build:versions && wrangler pages deploy dist --project-name=rscexplorer", 14 - "test": "vitest run --reporter=verbose" 14 + "test": "vitest run --reporter=verbose", 15 + "lint": "eslint .", 16 + "format": "prettier --write .", 17 + "format:check": "prettier --check .", 18 + "prepare": "husky" 15 19 }, 16 20 "keywords": [], 17 21 "author": "Dan Abramov <dan.abramov@gmail.com>", ··· 37 41 "vite": "8.0.0-beta.2" 38 42 }, 39 43 "devDependencies": { 44 + "@eslint/js": "^9.39.2", 40 45 "@vitejs/plugin-react": "^5.1.2", 41 46 "@vitest/browser": "^4.0.15", 42 47 "@vitest/browser-playwright": "^4.0.15", 48 + "eslint": "^9.39.2", 49 + "eslint-plugin-react-hooks": "^7.0.1", 50 + "husky": "^9.1.7", 51 + "lint-staged": "^16.2.7", 43 52 "playwright": "^1.57.0", 53 + "prettier": "^3.7.4", 44 54 "rolldown": "^1.0.0-beta.54", 55 + "typescript-eslint": "^8.50.0", 45 56 "vite": "8.0.0-beta.2", 46 57 "vitest": "^4.0.15", 47 58 "wrangler": "^4.0.0" 59 + }, 60 + "lint-staged": { 61 + "*": "prettier --write --ignore-unknown" 48 62 } 49 63 }
+9
prettier.config.js
··· 1 + /** @type {import("prettier").Config} */ 2 + const config = { 3 + printWidth: 100, 4 + semi: true, 5 + singleQuote: false, 6 + trailingComma: "all", 7 + }; 8 + 9 + export default config;
+1 -3
scripts/build-version.js
··· 24 24 25 25 function buildForVersion(version, outDir) { 26 26 console.log(`\n========================================`); 27 - console.log( 28 - `Building React ${version} (dev + prod) → ${outDir || `dist/${version}`}`, 29 - ); 27 + console.log(`Building React ${version} (dev + prod) → ${outDir || `dist/${version}`}`); 30 28 console.log(`========================================`); 31 29 32 30 installReactVersion(version);
+2 -2
src/client/byte-stream-polyfill.js
··· 5 5 import { 6 6 ReadableStream as PolyfillReadableStream, 7 7 ReadableByteStreamController as PolyfillReadableByteStreamController, 8 - } from 'web-streams-polyfill'; 8 + } from "web-streams-polyfill"; 9 9 10 - if (typeof globalThis.ReadableByteStreamController === 'undefined') { 10 + if (typeof globalThis.ReadableByteStreamController === "undefined") { 11 11 // Safari doesn't have byte stream support - use the polyfill's ReadableStream 12 12 // which includes full byte stream support 13 13 globalThis.ReadableStream = PolyfillReadableStream;
+30 -20
src/client/embed.jsx
··· 1 1 // Must be first - shims webpack globals for react-server-dom-webpack 2 - import './webpack-shim.js'; 2 + import "./webpack-shim.js"; 3 3 4 - import './byte-stream-polyfill.js'; 5 - import 'web-streams-polyfill/polyfill'; 6 - import 'text-encoding'; 4 + import "./byte-stream-polyfill.js"; 5 + import "web-streams-polyfill/polyfill"; 6 + import "text-encoding"; 7 7 8 - import React, { useState, useEffect } from 'react'; 9 - import { createRoot } from 'react-dom/client'; 10 - import { Workspace } from './ui/Workspace.jsx'; 11 - import './styles/workspace.css'; 8 + import React, { useState, useEffect } from "react"; 9 + import { createRoot } from "react-dom/client"; 10 + import { Workspace } from "./ui/Workspace.jsx"; 11 + import "./styles/workspace.css"; 12 12 13 13 // Default code shown when no code is provided 14 14 const DEFAULT_SERVER = `export default function App() { ··· 28 28 useEffect(() => { 29 29 const handleMessage = (event) => { 30 30 const { data } = event; 31 - if (data?.type === 'rsc-embed:init') { 31 + if (data?.type === "rsc-embed:init") { 32 32 setCode({ 33 33 server: (data.code?.server || DEFAULT_SERVER).trim(), 34 34 client: (data.code?.client || DEFAULT_CLIENT).trim(), ··· 39 39 } 40 40 }; 41 41 42 - window.addEventListener('message', handleMessage); 42 + window.addEventListener("message", handleMessage); 43 43 44 44 // Signal to parent that we're ready to receive code 45 45 if (window.parent !== window) { 46 - window.parent.postMessage({ type: 'rsc-embed:ready' }, '*'); 46 + window.parent.postMessage({ type: "rsc-embed:ready" }, "*"); 47 47 } 48 48 49 - return () => window.removeEventListener('message', handleMessage); 49 + return () => window.removeEventListener("message", handleMessage); 50 50 }, []); 51 51 52 52 // Report code changes back to parent 53 53 const handleCodeChange = (server, client) => { 54 54 if (window.parent !== window) { 55 - window.parent.postMessage({ 56 - type: 'rsc-embed:code-changed', 57 - code: { server, client } 58 - }, '*'); 55 + window.parent.postMessage( 56 + { 57 + type: "rsc-embed:code-changed", 58 + code: { server, client }, 59 + }, 60 + "*", 61 + ); 59 62 } 60 63 }; 61 64 62 65 // Generate fullscreen URL 63 66 const getFullscreenUrl = () => { 64 - if (!code) return '#'; 67 + if (!code) return "#"; 65 68 const json = JSON.stringify({ server: code.server, client: code.client }); 66 69 const encoded = encodeURIComponent(btoa(unescape(encodeURIComponent(json)))); 67 70 return `https://rscexplorer.dev/?c=${encoded}`; ··· 84 87 className="embed-fullscreen-link" 85 88 title="Open in RSC Explorer" 86 89 > 87 - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> 90 + <svg 91 + width="14" 92 + height="14" 93 + viewBox="0 0 24 24" 94 + fill="none" 95 + stroke="currentColor" 96 + strokeWidth="2" 97 + > 88 98 <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3" /> 89 99 </svg> 90 100 </a> ··· 100 110 ); 101 111 } 102 112 103 - document.addEventListener('DOMContentLoaded', () => { 104 - const root = createRoot(document.getElementById('embed-root')); 113 + document.addEventListener("DOMContentLoaded", () => { 114 + const root = createRoot(document.getElementById("embed-root")); 105 115 root.render(<EmbedApp />); 106 116 });
+9 -9
src/client/index.jsx
··· 1 1 // Must be first - shims webpack globals for react-server-dom-webpack 2 - import './webpack-shim.js'; 2 + import "./webpack-shim.js"; 3 3 4 - import './byte-stream-polyfill.js'; 5 - import 'web-streams-polyfill/polyfill'; 6 - import 'text-encoding'; 4 + import "./byte-stream-polyfill.js"; 5 + import "web-streams-polyfill/polyfill"; 6 + import "text-encoding"; 7 7 8 - import React from 'react'; 9 - import { createRoot } from 'react-dom/client'; 10 - import { App } from './ui/App.jsx'; 8 + import React from "react"; 9 + import { createRoot } from "react-dom/client"; 10 + import { App } from "./ui/App.jsx"; 11 11 12 12 // Mount the app 13 - document.addEventListener('DOMContentLoaded', () => { 14 - const root = createRoot(document.getElementById('app')); 13 + document.addEventListener("DOMContentLoaded", () => { 14 + const root = createRoot(document.getElementById("app")); 15 15 root.render(<App />); 16 16 });
+3 -3
src/client/runtime/index.js
··· 1 - export { registerClientModule, evaluateClientModule } from './module-registry.js'; 2 - export { SteppableStream } from './steppable-stream.js'; 3 - export { Timeline } from './timeline.js'; 1 + export { registerClientModule, evaluateClientModule } from "./module-registry.js"; 2 + export { SteppableStream } from "./steppable-stream.js"; 3 + export { Timeline } from "./timeline.js";
+4 -4
src/client/runtime/module-registry.js
··· 1 - import React from 'react'; 1 + import React from "react"; 2 2 3 3 export function registerClientModule(moduleId, moduleExports) { 4 - if (typeof __webpack_module_cache__ !== 'undefined') { 4 + if (typeof __webpack_module_cache__ !== "undefined") { 5 5 __webpack_module_cache__[moduleId] = { exports: moduleExports }; 6 6 } 7 7 } ··· 9 9 export function evaluateClientModule(compiledCode) { 10 10 const module = { exports: {} }; 11 11 const require = (id) => { 12 - if (id === 'react') return React; 12 + if (id === "react") return React; 13 13 throw new Error(`Module "${id}" not found in client context`); 14 14 }; 15 - const fn = new Function('module', 'exports', 'require', 'React', compiledCode); 15 + const fn = new Function("module", "exports", "require", "React", compiledCode); 16 16 fn(module, module.exports, require, React); 17 17 return module.exports; 18 18 }
+7 -5
src/client/runtime/steppable-stream.js
··· 1 - import { createFromReadableStream } from 'react-server-dom-webpack/client'; 1 + import { createFromReadableStream } from "react-server-dom-webpack/client"; 2 2 3 3 /** 4 4 * SteppableStream - makes a Flight stream steppable for debugging. ··· 16 16 const encoder = new TextEncoder(); 17 17 let controller; 18 18 const output = new ReadableStream({ 19 - start: (c) => { controller = c; } 19 + start: (c) => { 20 + controller = c; 21 + }, 20 22 }); 21 23 22 24 this.release = (count) => { 23 25 while (this.releasedCount < count && this.releasedCount < this.rows.length) { 24 - controller.enqueue(encoder.encode(this.rows[this.releasedCount] + '\n')); 26 + controller.enqueue(encoder.encode(this.rows[this.releasedCount] + "\n")); 25 27 this.releasedCount++; 26 28 } 27 29 if (this.releasedCount >= this.rows.length && this.buffered && !this.closed) { ··· 37 39 async buffer(stream) { 38 40 const reader = stream.getReader(); 39 41 const decoder = new TextDecoder(); 40 - let partial = ''; 42 + let partial = ""; 41 43 42 44 try { 43 45 while (true) { ··· 45 47 if (done) break; 46 48 47 49 partial += decoder.decode(value, { stream: true }); 48 - const lines = partial.split('\n'); 50 + const lines = partial.split("\n"); 49 51 partial = lines.pop(); 50 52 51 53 for (const line of lines) {
+5 -4
src/client/runtime/timeline.js
··· 23 23 24 24 notify() { 25 25 this.snapshot = null; // Invalidate cache 26 - this.listeners.forEach(fn => fn()); 26 + this.listeners.forEach((fn) => fn()); 27 27 } 28 28 29 29 getChunkCount(entry) { ··· 75 75 }; 76 76 77 77 setRender(stream) { 78 - this.entries = [{ type: 'render', stream }]; 78 + this.entries = [{ type: "render", stream }]; 79 79 this.cursor = 0; 80 80 this.notify(); 81 81 } 82 82 83 83 addAction(name, args, stream) { 84 - this.entries = [...this.entries, { type: 'action', name, args, stream }]; 84 + this.entries = [...this.entries, { type: "action", name, args, stream }]; 85 85 this.notify(); 86 86 } 87 87 ··· 110 110 const pos = this.getPosition(this.cursor); 111 111 if (!pos) return; 112 112 113 - const entryEnd = this.getEntryStart(pos.entryIndex) + this.getChunkCount(this.entries[pos.entryIndex]); 113 + const entryEnd = 114 + this.getEntryStart(pos.entryIndex) + this.getChunkCount(this.entries[pos.entryIndex]); 114 115 while (this.cursor < entryEnd) { 115 116 this.stepForward(); 116 117 }
+21 -21
src/client/samples.js
··· 1 1 export const SAMPLES = { 2 2 hello: { 3 - name: 'Hello World', 3 + name: "Hello World", 4 4 server: `export default function App() { 5 5 return <h1>Hello World</h1> 6 6 }`, 7 - client: `'use client'` 7 + client: `'use client'`, 8 8 }, 9 9 async: { 10 - name: 'Async Component', 10 + name: "Async Component", 11 11 server: `import { Suspense } from 'react' 12 12 13 13 export default function App() { ··· 25 25 await new Promise(r => setTimeout(r, 500)) 26 26 return <p>Data loaded!</p> 27 27 }`, 28 - client: `'use client'` 28 + client: `'use client'`, 29 29 }, 30 30 counter: { 31 - name: 'Counter', 31 + name: "Counter", 32 32 server: `import { Counter } from './client' 33 33 34 34 export default function App() { ··· 55 55 </div> 56 56 </div> 57 57 ) 58 - }` 58 + }`, 59 59 }, 60 60 form: { 61 - name: 'Form Action', 61 + name: "Form Action", 62 62 server: `import { Form } from './client' 63 63 64 64 export default function App() { ··· 103 103 {state.message && <p style={{ color: 'green', marginTop: 8 }}>{state.message}</p>} 104 104 </form> 105 105 ) 106 - }` 106 + }`, 107 107 }, 108 108 pagination: { 109 - name: 'Pagination', 109 + name: "Pagination", 110 110 server: `import { Suspense } from 'react' 111 111 import { Paginator } from './client' 112 112 ··· 198 198 } 199 199 200 200 return { items, hasMore, formAction, isPending } 201 - }` 201 + }`, 202 202 }, 203 203 refresh: { 204 - name: 'Router Refresh', 204 + name: "Router Refresh", 205 205 server: `import { Suspense } from 'react' 206 206 import { Timer, Router } from './client' 207 207 ··· 272 272 </button> 273 273 </div> 274 274 ) 275 - }` 275 + }`, 276 276 }, 277 277 errors: { 278 - name: 'Error Handling', 278 + name: "Error Handling", 279 279 server: `import { Suspense } from 'react' 280 280 import { ErrorBoundary } from './client' 281 281 ··· 331 331 } 332 332 return this.props.children 333 333 } 334 - }` 334 + }`, 335 335 }, 336 336 clientref: { 337 - name: 'Client Reference', 337 + name: "Client Reference", 338 338 server: `// Server can pass client module exports as props 339 339 import { darkTheme, lightTheme, ThemedBox } from './client' 340 340 ··· 362 362 {label} theme 363 363 </div> 364 364 ) 365 - }` 365 + }`, 366 366 }, 367 367 bound: { 368 - name: 'Bound Actions', 368 + name: "Bound Actions", 369 369 server: `// action.bind() pre-binds arguments on the server 370 370 import { Greeter } from './client' 371 371 ··· 407 407 {result && <span style={{ marginLeft: 8 }}>{result}</span>} 408 408 </form> 409 409 ) 410 - }` 410 + }`, 411 411 }, 412 412 kitchensink: { 413 - name: 'Kitchen Sink', 413 + name: "Kitchen Sink", 414 414 server: `// Kitchen Sink - All RSC Protocol Types 415 415 import { Suspense } from 'react' 416 416 import { DataDisplay } from './client' ··· 568 568 if (Array.isArray(v)) return '[' + v.length + ' items]' 569 569 if (typeof v === 'object') return '{...}' 570 570 return String(v) 571 - }` 571 + }`, 572 572 }, 573 573 cve: { 574 - name: 'CVE-2025-55182', 574 + name: "CVE-2025-55182", 575 575 server: `import { Instructions } from './client' 576 576 577 577 async function poc() {
+31 -27
src/client/server-worker.js
··· 1 - import workerUrl from '../server/worker.js?rolldown-worker'; 1 + import workerUrl from "../server/worker.js?rolldown-worker"; 2 2 3 - const randomUUID = crypto.randomUUID?.bind(crypto) ?? function() { 4 - const bytes = crypto.getRandomValues(new Uint8Array(16)); 5 - bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 6 - bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1 7 - const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')); 8 - return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10).join('')}`; 9 - }; 3 + const randomUUID = 4 + crypto.randomUUID?.bind(crypto) ?? 5 + function () { 6 + const bytes = crypto.getRandomValues(new Uint8Array(16)); 7 + bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 8 + bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1 9 + const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")); 10 + return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`; 11 + }; 10 12 11 13 function serializeForTransfer(encoded) { 12 14 if (encoded instanceof FormData) { 13 - return { type: 'formdata', data: new URLSearchParams(encoded).toString() }; 15 + return { type: "formdata", data: new URLSearchParams(encoded).toString() }; 14 16 } 15 - return { type: 'string', data: encoded }; 17 + return { type: "string", data: encoded }; 16 18 } 17 19 18 20 export class ServerWorker { ··· 20 22 this.worker = new Worker(workerUrl); 21 23 this.pending = new Map(); 22 24 this.streams = new Map(); 23 - this.readyPromise = new Promise(resolve => { 25 + this.readyPromise = new Promise((resolve) => { 24 26 this.readyResolve = resolve; 25 27 }); 26 28 ··· 31 33 handleMessage(event) { 32 34 const { type, requestId, error, chunk } = event.data; 33 35 34 - if (type === 'ready') { 36 + if (type === "ready") { 35 37 this.readyResolve(); 36 38 return; 37 39 } 38 40 39 - if (type === 'stream-start') { 41 + if (type === "stream-start") { 40 42 const pending = this.pending.get(requestId); 41 43 if (!pending) return; 42 44 this.pending.delete(requestId); 43 45 44 46 let controller; 45 47 const stream = new ReadableStream({ 46 - start: (c) => { controller = c; } 48 + start: (c) => { 49 + controller = c; 50 + }, 47 51 }); 48 52 this.streams.set(requestId, controller); 49 53 pending.resolve(stream); 50 54 return; 51 55 } 52 56 53 - if (type === 'stream-chunk') { 57 + if (type === "stream-chunk") { 54 58 const controller = this.streams.get(requestId); 55 59 if (controller) controller.enqueue(chunk); 56 60 return; 57 61 } 58 62 59 - if (type === 'stream-end') { 63 + if (type === "stream-end") { 60 64 const controller = this.streams.get(requestId); 61 65 if (controller) { 62 66 controller.close(); ··· 65 69 return; 66 70 } 67 71 68 - if (type === 'stream-error') { 72 + if (type === "stream-error") { 69 73 const controller = this.streams.get(requestId); 70 74 if (controller) { 71 75 controller.error(new Error(error.message)); ··· 82 86 83 87 this.pending.delete(requestId); 84 88 85 - if (type === 'error') { 89 + if (type === "error") { 86 90 const err = new Error(error.message); 87 91 err.stack = error.stack; 88 92 pending.reject(err); 89 - } else if (type === 'deployed') { 93 + } else if (type === "deployed") { 90 94 pending.resolve(); 91 95 } 92 96 } 93 97 94 98 handleError(event) { 95 - const errorMsg = event.message || event.error?.message || 'Unknown worker error'; 99 + const errorMsg = event.message || event.error?.message || "Unknown worker error"; 96 100 console.error(`Worker error: ${errorMsg}`); 97 101 98 102 for (const [, pending] of this.pending) { ··· 108 112 return new Promise((resolve, reject) => { 109 113 this.pending.set(requestId, { resolve, reject }); 110 114 this.worker.postMessage({ 111 - type: 'deploy', 115 + type: "deploy", 112 116 requestId, 113 117 compiledCode, 114 118 manifest, ··· 123 127 124 128 return new Promise((resolve, reject) => { 125 129 this.pending.set(requestId, { resolve, reject }); 126 - this.worker.postMessage({ type: 'render', requestId }); 130 + this.worker.postMessage({ type: "render", requestId }); 127 131 }); 128 132 } 129 133 ··· 134 138 return new Promise((resolve, reject) => { 135 139 this.pending.set(requestId, { resolve, reject }); 136 140 this.worker.postMessage({ 137 - type: 'action', 141 + type: "action", 138 142 requestId, 139 143 actionId, 140 144 encodedArgs: serializeForTransfer(encodedArgs), ··· 149 153 return new Promise((resolve, reject) => { 150 154 this.pending.set(requestId, { resolve, reject }); 151 155 this.worker.postMessage({ 152 - type: 'action', 156 + type: "action", 153 157 requestId, 154 158 actionId, 155 - encodedArgs: { type: 'formdata', data: rawPayload }, 159 + encodedArgs: { type: "formdata", data: rawPayload }, 156 160 }); 157 161 }); 158 162 } ··· 160 164 terminate() { 161 165 this.worker.terminate(); 162 166 for (const [, pending] of this.pending) { 163 - pending.reject(new Error('Worker terminated')); 167 + pending.reject(new Error("Worker terminated")); 164 168 } 165 169 this.pending.clear(); 166 170 for (const [, controller] of this.streams) { 167 - controller.error(new Error('Worker terminated')); 171 + controller.error(new Error("Worker terminated")); 168 172 } 169 173 this.streams.clear(); 170 174 }
+57 -19
src/client/styles/workspace.css
··· 370 370 border-radius: 3px; 371 371 margin: -8px; 372 372 } 373 - .value-string { color: #22863a; } 374 - .value-number { color: #005cc5; } 375 - .value-boolean { color: #d73a49; } 376 - .value-null, .value-undefined { color: #6a737d; font-style: italic; } 377 - .value-key { color: #005cc5; } 373 + .value-string { 374 + color: #22863a; 375 + } 376 + .value-number { 377 + color: #005cc5; 378 + } 379 + .value-boolean { 380 + color: #d73a49; 381 + } 382 + .value-null, 383 + .value-undefined { 384 + color: #6a737d; 385 + font-style: italic; 386 + } 387 + .value-key { 388 + color: #005cc5; 389 + } 378 390 .value-indent { 379 391 display: block; 380 392 padding-left: 16px; 381 393 } 382 - .value-array-item, .value-object-entry { 394 + .value-array-item, 395 + .value-object-entry { 383 396 display: block; 384 397 } 385 398 .value-react-element { ··· 449 462 animation: pending-pulse 2s ease-in-out infinite; 450 463 } 451 464 @keyframes pending-pulse { 452 - 0%, 100% { opacity: 0.7; } 453 - 50% { opacity: 1; } 465 + 0%, 466 + 100% { 467 + opacity: 0.7; 468 + } 469 + 50% { 470 + opacity: 1; 471 + } 454 472 } 455 473 .tree-string { 456 474 color: #98c379; ··· 577 595 animation: none; 578 596 } 579 597 @keyframes pulse-step { 580 - 0%, 100% { opacity: 1; } 581 - 50% { opacity: 0.7; } 598 + 0%, 599 + 100% { 600 + opacity: 1; 601 + } 602 + 50% { 603 + opacity: 0.7; 604 + } 582 605 } 583 606 .step-slider { 584 607 flex: 1; ··· 671 694 } 672 695 /* Pulsing dots for empty/waiting states */ 673 696 .empty::after { 674 - content: ''; 697 + content: ""; 675 698 animation: none; 676 699 } 677 700 .waiting-dots::after { 678 - content: '...'; 701 + content: "..."; 679 702 animation: pulse 1.5s ease-in-out infinite; 680 703 } 681 704 @keyframes pulse { 682 - 0%, 100% { opacity: 0.3; } 683 - 50% { opacity: 1; } 705 + 0%, 706 + 100% { 707 + opacity: 0.3; 708 + } 709 + 50% { 710 + opacity: 1; 711 + } 684 712 } 685 713 686 714 /* Raw input form */ ··· 720 748 width: 100%; 721 749 padding: 5px 28px 5px 10px; 722 750 margin-bottom: 8px; 723 - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center, linear-gradient(to bottom, #333, #2a2a2a); 751 + background: 752 + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") 753 + no-repeat right 8px center, 754 + linear-gradient(to bottom, #333, #2a2a2a); 724 755 border: 1px solid #555; 725 756 border-radius: 4px; 726 757 color: #fff; 727 758 font-size: 12px; 728 759 cursor: pointer; 729 - box-shadow: 0 1px 2px rgba(0,0,0,0.2); 760 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 730 761 } 731 762 .raw-input-action:hover { 732 763 border-color: #666; 733 - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23bbb' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center, linear-gradient(to bottom, #3a3a3a, #333); 764 + background: 765 + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23bbb' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") 766 + no-repeat right 8px center, 767 + linear-gradient(to bottom, #3a3a3a, #333); 734 768 } 735 769 .raw-input-action:focus { 736 770 outline: none; ··· 796 830 padding: 0 4px; 797 831 line-height: 1; 798 832 opacity: 0.5; 799 - transition: opacity 0.15s, color 0.15s; 833 + transition: 834 + opacity 0.15s, 835 + color 0.15s; 800 836 } 801 837 .delete-entry-btn:hover { 802 838 opacity: 1; ··· 807 843 @media (max-width: 768px) { 808 844 /* Keep 4-grid layout on mobile */ 809 845 /* Prevent iOS zoom on all form elements */ 810 - input, textarea, select { 846 + input, 847 + textarea, 848 + select { 811 849 font-size: 16px !important; 812 850 } 813 851 .raw-input-payload {
+8 -30
src/client/ui/App.jsx
··· 11 11 const newVersion = e.target.value; 12 12 if (newVersion !== version) { 13 13 const modePath = isDev ? "/dev" : ""; 14 - window.location.href = 15 - `/${newVersion}${modePath}/` + window.location.search; 14 + window.location.href = `/${newVersion}${modePath}/` + window.location.search; 16 15 } 17 16 }; 18 17 ··· 27 26 return ( 28 27 <div className="build-switcher"> 29 28 <label>React</label> 30 - <select 31 - value={version} 32 - onChange={handleVersionChange} 33 - disabled={isDisabled} 34 - > 29 + <select value={version} onChange={handleVersionChange} disabled={isDisabled}> 35 30 {REACT_VERSIONS.map((v) => ( 36 31 <option key={v} value={v}> 37 32 {v} ··· 100 95 const [copied, setCopied] = useState(false); 101 96 102 97 const embedCode = useMemo(() => { 103 - const base = 104 - window.location.origin + window.location.pathname.replace(/\/$/, ""); 98 + const base = window.location.origin + window.location.pathname.replace(/\/$/, ""); 105 99 const id = Math.random().toString(36).slice(2, 6); 106 100 return `<div id="rsc-${id}" style="height: 500px;"></div> 107 101 <script type="module"> ··· 206 200 const isDirty = currentSample 207 201 ? liveCode.server !== SAMPLES[currentSample].server || 208 202 liveCode.client !== SAMPLES[currentSample].client 209 - : liveCode.server !== initialCode.server || 210 - liveCode.client !== initialCode.client; 203 + : liveCode.server !== initialCode.server || liveCode.client !== initialCode.client; 211 204 212 205 const handleSampleChange = (e) => { 213 206 const key = e.target.value; ··· 239 232 </option> 240 233 ))} 241 234 </select> 242 - <button 243 - className="save-btn" 244 - onClick={handleSave} 245 - disabled={!isDirty} 246 - title="Save to URL" 247 - > 235 + <button className="save-btn" onClick={handleSave} disabled={!isDirty} title="Save to URL"> 248 236 <svg 249 237 width="16" 250 238 height="16" ··· 258 246 <polyline points="7 3 7 8 15 8" /> 259 247 </svg> 260 248 </button> 261 - <button 262 - className="embed-btn" 263 - onClick={() => setShowEmbedModal(true)} 264 - title="Embed" 265 - > 249 + <button className="embed-btn" onClick={() => setShowEmbedModal(true)} title="Embed"> 266 250 <svg 267 251 width="16" 268 252 height="16" ··· 303 287 </div> 304 288 <BuildSwitcher /> 305 289 </header> 306 - <iframe 307 - ref={iframeRef} 308 - src="embed.html" 309 - style={{ flex: 1, border: "none", width: "100%" }} 310 - /> 311 - {showEmbedModal && ( 312 - <EmbedModal code={liveCode} onClose={() => setShowEmbedModal(false)} /> 313 - )} 290 + <iframe ref={iframeRef} src="embed.html" style={{ flex: 1, border: "none", width: "100%" }} /> 291 + {showEmbedModal && <EmbedModal code={liveCode} onClose={() => setShowEmbedModal(false)} />} 314 292 </> 315 293 ); 316 294 }
+88 -52
src/client/ui/FlightLog.jsx
··· 1 - import React, { useState, useRef, useEffect } from 'react'; 2 - import { FlightTreeView } from './TreeView.jsx'; 1 + import React, { useState, useRef, useEffect } from "react"; 2 + import { FlightTreeView } from "./TreeView.jsx"; 3 3 4 4 function escapeHtml(str) { 5 - return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); 5 + return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 6 6 } 7 7 8 8 function RenderLogView({ lines, chunkStart, cursor, flightPromise }) { 9 9 const activeRef = useRef(null); 10 - const nextLineIndex = cursor >= chunkStart && cursor < chunkStart + lines.length 11 - ? cursor - chunkStart 12 - : -1; 10 + const nextLineIndex = 11 + cursor >= chunkStart && cursor < chunkStart + lines.length ? cursor - chunkStart : -1; 13 12 14 13 useEffect(() => { 15 14 if (activeRef.current) { 16 - activeRef.current.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); 15 + activeRef.current.scrollIntoView({ block: "nearest", behavior: "smooth" }); 17 16 } 18 17 }, [nextLineIndex]); 19 18 ··· 21 20 22 21 const getLineClass = (i) => { 23 22 const globalChunk = chunkStart + i; 24 - if (globalChunk < cursor) return 'line-done'; 25 - if (globalChunk === cursor) return 'line-next'; 26 - return 'line-pending'; 23 + if (globalChunk < cursor) return "line-done"; 24 + if (globalChunk === cursor) return "line-next"; 25 + return "line-pending"; 27 26 }; 28 27 29 28 const showTree = cursor >= chunkStart; ··· 52 51 ); 53 52 } 54 53 55 - function FlightLogEntry({ entry, entryIndex, chunkStart, cursor, canDelete, onDelete, getChunkCount }) { 54 + function FlightLogEntry({ 55 + entry, 56 + entryIndex, 57 + chunkStart, 58 + cursor, 59 + canDelete, 60 + onDelete, 61 + getChunkCount, 62 + }) { 56 63 const chunkCount = getChunkCount(entry); 57 64 const entryEnd = chunkStart + chunkCount; 58 65 const isEntryActive = cursor >= chunkStart && cursor < entryEnd; 59 66 const isEntryDone = cursor >= entryEnd; 60 67 61 - const entryClass = isEntryActive ? 'active' : isEntryDone ? 'done-entry' : 'pending-entry'; 68 + const entryClass = isEntryActive ? "active" : isEntryDone ? "done-entry" : "pending-entry"; 62 69 63 - if (entry.type === 'render') { 70 + if (entry.type === "render") { 64 71 const lines = entry.stream?.rows || []; 65 72 return ( 66 73 <div className={`log-entry ${entryClass}`}> ··· 68 75 <span className="log-entry-label">Render</span> 69 76 <span className="log-entry-header-right"> 70 77 {canDelete && ( 71 - <button className="delete-entry-btn" onClick={() => onDelete(entryIndex)} title="Delete">×</button> 78 + <button 79 + className="delete-entry-btn" 80 + onClick={() => onDelete(entryIndex)} 81 + title="Delete" 82 + > 83 + × 84 + </button> 72 85 )} 73 86 </span> 74 87 </div> ··· 82 95 ); 83 96 } 84 97 85 - if (entry.type === 'action') { 98 + if (entry.type === "action") { 86 99 const responseLines = entry.stream?.rows || []; 87 100 88 101 return ( ··· 91 104 <span className="log-entry-label">Action: {entry.name}</span> 92 105 <span className="log-entry-header-right"> 93 106 {canDelete && ( 94 - <button className="delete-entry-btn" onClick={() => onDelete(entryIndex)} title="Delete">×</button> 107 + <button 108 + className="delete-entry-btn" 109 + onClick={() => onDelete(entryIndex)} 110 + title="Delete" 111 + > 112 + × 113 + </button> 95 114 )} 96 115 </span> 97 116 </div> ··· 113 132 return null; 114 133 } 115 134 116 - export function FlightLog({ timeline, entries, cursor, error, availableActions, onAddRawAction, onDeleteEntry }) { 135 + export function FlightLog({ 136 + timeline, 137 + entries, 138 + cursor, 139 + error, 140 + availableActions, 141 + onAddRawAction, 142 + onDeleteEntry, 143 + }) { 117 144 const logRef = useRef(null); 118 145 const [showRawInput, setShowRawInput] = useState(false); 119 - const [selectedAction, setSelectedAction] = useState(''); 120 - const [rawPayload, setRawPayload] = useState(''); 146 + const [selectedAction, setSelectedAction] = useState(""); 147 + const [rawPayload, setRawPayload] = useState(""); 121 148 122 149 const handleAddRaw = () => { 123 150 if (rawPayload.trim()) { 124 151 onAddRawAction(selectedAction, rawPayload); 125 - setSelectedAction(availableActions[0] || ''); 126 - setRawPayload(''); 152 + setSelectedAction(availableActions[0] || ""); 153 + setRawPayload(""); 127 154 setShowRawInput(false); 128 155 } 129 156 }; 130 157 131 158 const handleShowRawInput = () => { 132 - setSelectedAction(availableActions[0] || ''); 159 + setSelectedAction(availableActions[0] || ""); 133 160 setShowRawInput(true); 134 161 }; 135 162 ··· 138 165 } 139 166 140 167 if (entries.length === 0) { 141 - return <div className="flight-output"><span className="empty waiting-dots">Compiling</span></div>; 168 + return ( 169 + <div className="flight-output"> 170 + <span className="empty waiting-dots">Compiling</span> 171 + </div> 172 + ); 142 173 } 143 174 144 175 let chunkOffset = 0; ··· 163 194 /> 164 195 ); 165 196 })} 166 - {availableActions.length > 0 && (showRawInput ? ( 167 - <div className="raw-input-form"> 168 - <select 169 - value={selectedAction} 170 - onChange={(e) => setSelectedAction(e.target.value)} 171 - className="raw-input-action" 172 - > 173 - {availableActions.map(action => ( 174 - <option key={action} value={action}>{action}</option> 175 - ))} 176 - </select> 177 - <textarea 178 - placeholder="Paste a request payload from a real action" 179 - value={rawPayload} 180 - onChange={(e) => setRawPayload(e.target.value)} 181 - className="raw-input-payload" 182 - rows={6} 183 - /> 184 - <div className="raw-input-buttons"> 185 - <button onClick={handleAddRaw} disabled={!rawPayload.trim()}>Add</button> 186 - <button onClick={() => setShowRawInput(false)}>Cancel</button> 197 + {availableActions.length > 0 && 198 + (showRawInput ? ( 199 + <div className="raw-input-form"> 200 + <select 201 + value={selectedAction} 202 + onChange={(e) => setSelectedAction(e.target.value)} 203 + className="raw-input-action" 204 + > 205 + {availableActions.map((action) => ( 206 + <option key={action} value={action}> 207 + {action} 208 + </option> 209 + ))} 210 + </select> 211 + <textarea 212 + placeholder="Paste a request payload from a real action" 213 + value={rawPayload} 214 + onChange={(e) => setRawPayload(e.target.value)} 215 + className="raw-input-payload" 216 + rows={6} 217 + /> 218 + <div className="raw-input-buttons"> 219 + <button onClick={handleAddRaw} disabled={!rawPayload.trim()}> 220 + Add 221 + </button> 222 + <button onClick={() => setShowRawInput(false)}>Cancel</button> 223 + </div> 187 224 </div> 188 - </div> 189 - ) : ( 190 - <div className="add-raw-btn-wrapper"> 191 - <button className="add-raw-btn" onClick={handleShowRawInput} title="Add action"> 192 - + 193 - </button> 194 - </div> 195 - ))} 225 + ) : ( 226 + <div className="add-raw-btn-wrapper"> 227 + <button className="add-raw-btn" onClick={handleShowRawInput} title="Add action"> 228 + + 229 + </button> 230 + </div> 231 + ))} 196 232 </div> 197 233 ); 198 234 }
+35 -26
src/client/ui/LivePreview.jsx
··· 1 - import React, { Suspense, Component, useState, useEffect, useSyncExternalStore } from 'react'; 1 + import React, { Suspense, Component, useState, useEffect, useSyncExternalStore } from "react"; 2 2 3 3 class PreviewErrorBoundary extends Component { 4 4 constructor(props) { ··· 35 35 isAtEnd, 36 36 onStep, 37 37 onSkip, 38 - onReset 38 + onReset, 39 39 }) { 40 40 const snapshot = useSyncExternalStore(timeline.subscribe, timeline.getSnapshot); 41 41 const { entries } = snapshot; ··· 60 60 const showPlaceholder = !clientModuleReady || cursor === 0; 61 61 62 62 const handlePlayPause = () => setIsPlaying(!isPlaying); 63 - const handleStep = () => { setIsPlaying(false); onStep(); }; 64 - const handleSkip = () => { setIsPlaying(false); onSkip(); }; 65 - const handleReset = () => { setIsPlaying(false); onReset(); }; 63 + const handleStep = () => { 64 + setIsPlaying(false); 65 + onStep(); 66 + }; 67 + const handleSkip = () => { 68 + setIsPlaying(false); 69 + onSkip(); 70 + }; 71 + const handleReset = () => { 72 + setIsPlaying(false); 73 + onReset(); 74 + }; 66 75 67 - let statusText = ''; 76 + let statusText = ""; 68 77 if (isAtStart) { 69 - statusText = 'Ready'; 78 + statusText = "Ready"; 70 79 } else if (isAtEnd) { 71 - statusText = 'Done'; 80 + statusText = "Done"; 72 81 } else { 73 82 statusText = `${cursor} / ${totalChunks}`; 74 83 } ··· 78 87 <div className="pane-header">preview</div> 79 88 <div className="playback-container"> 80 89 <div className="playback-controls"> 81 - <button 82 - className="control-btn" 83 - onClick={handleReset} 84 - disabled={isAtStart} 85 - title="Reset" 86 - > 90 + <button className="control-btn" onClick={handleReset} disabled={isAtStart} title="Reset"> 87 91 <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> 88 - <path d="M8 1a7 7 0 1 1-7 7h1.5a5.5 5.5 0 1 0 1.6-3.9L6 6H1V1l1.6 1.6A7 7 0 0 1 8 1z"/> 92 + <path d="M8 1a7 7 0 1 1-7 7h1.5a5.5 5.5 0 1 0 1.6-3.9L6 6H1V1l1.6 1.6A7 7 0 0 1 8 1z" /> 89 93 </svg> 90 94 </button> 91 95 <button 92 - className={`control-btn play-btn${isPlaying ? ' playing' : ''}`} 96 + className={`control-btn play-btn${isPlaying ? " playing" : ""}`} 93 97 onClick={handlePlayPause} 94 98 disabled={isAtEnd} 95 - title={isPlaying ? 'Pause' : 'Play'} 99 + title={isPlaying ? "Pause" : "Play"} 96 100 > 97 101 {isPlaying ? ( 98 102 <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> 99 - <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/> 103 + <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /> 100 104 </svg> 101 105 ) : ( 102 106 <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> 103 - <path d="M8 5v14l11-7L8 5z"/> 107 + <path d="M8 5v14l11-7L8 5z" /> 104 108 </svg> 105 109 )} 106 110 </button> 107 111 <button 108 - className={`control-btn ${!isAtEnd ? 'step-btn' : ''}`} 112 + className={`control-btn ${!isAtEnd ? "step-btn" : ""}`} 109 113 onClick={handleStep} 110 114 disabled={isAtEnd} 111 115 title="Step forward" 112 116 > 113 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"> 114 - <path d="M9 6l6 6-6 6"/> 117 + <svg 118 + width="16" 119 + height="16" 120 + viewBox="0 0 24 24" 121 + fill="none" 122 + stroke="currentColor" 123 + strokeWidth="2.5" 124 + > 125 + <path d="M9 6l6 6-6 6" /> 115 126 </svg> 116 127 </button> 117 128 <button ··· 121 132 title="Skip to end" 122 133 > 123 134 <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> 124 - <path d="M5.5 18V6l9 6-9 6zm9-12h2v12h-2V6z"/> 135 + <path d="M5.5 18V6l9 6-9 6zm9-12h2v12h-2V6z" /> 125 136 </svg> 126 137 </button> 127 138 </div> ··· 138 149 </div> 139 150 <div className="preview-container"> 140 151 {showPlaceholder ? ( 141 - <span className="empty"> 142 - {isAtStart ? 'Step to begin...' : 'Loading...'} 143 - </span> 152 + <span className="empty">{isAtStart ? "Step to begin..." : "Loading..."}</span> 144 153 ) : flightPromise ? ( 145 154 <PreviewErrorBoundary> 146 155 <Suspense fallback={<span className="empty">Loading...</span>}>
+203 -128
src/client/ui/TreeView.jsx
··· 1 - import React, { Suspense, Component } from 'react'; 1 + import React, { Suspense, Component } from "react"; 2 2 3 3 function isReactElement(value) { 4 - if (!value || typeof value !== 'object') return false; 4 + if (!value || typeof value !== "object") return false; 5 5 const typeofSymbol = value.$$typeof; 6 - return typeofSymbol === Symbol.for('react.transitional.element'); 6 + return typeofSymbol === Symbol.for("react.transitional.element"); 7 7 } 8 8 9 9 function escapeHtml(str) { 10 - return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); 10 + return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 11 11 } 12 12 13 13 function PendingFallback() { ··· 58 58 if (value === null) return <span className="tree-null">null</span>; 59 59 if (value === undefined) return <span className="tree-undefined">undefined</span>; 60 60 61 - if (typeof value === 'string') { 62 - const display = value.length > 50 ? value.slice(0, 50) + '...' : value; 61 + if (typeof value === "string") { 62 + const display = value.length > 50 ? value.slice(0, 50) + "..." : value; 63 63 return <span className="tree-string">"{escapeHtml(display)}"</span>; 64 64 } 65 - if (typeof value === 'number') { 66 - const display = Object.is(value, -0) ? '-0' : String(value); 65 + if (typeof value === "number") { 66 + const display = Object.is(value, -0) ? "-0" : String(value); 67 67 return <span className="tree-number">{display}</span>; 68 68 } 69 - if (typeof value === 'bigint') { 69 + if (typeof value === "bigint") { 70 70 return <span className="tree-number">{String(value)}n</span>; 71 71 } 72 - if (typeof value === 'boolean') return <span className="tree-boolean">{String(value)}</span>; 73 - if (typeof value === 'symbol') { 72 + if (typeof value === "boolean") return <span className="tree-boolean">{String(value)}</span>; 73 + if (typeof value === "symbol") { 74 74 return <span className="tree-symbol">{value.toString()}</span>; 75 75 } 76 - if (typeof value === 'function') { 77 - return <span className="tree-function">[Function: {value.name || 'anonymous'}]</span>; 76 + if (typeof value === "function") { 77 + return <span className="tree-function">[Function: {value.name || "anonymous"}]</span>; 78 78 } 79 79 80 - if (typeof value === 'object' && value !== null) { 80 + if (typeof value === "object" && value !== null) { 81 81 if (ancestors.includes(value)) { 82 82 return <span className="tree-circular">[Circular]</span>; 83 83 } 84 84 } 85 85 86 - const nextAncestors = typeof value === 'object' && value !== null 87 - ? [...ancestors, value] 88 - : ancestors; 86 + const nextAncestors = 87 + typeof value === "object" && value !== null ? [...ancestors, value] : ancestors; 89 88 90 89 if (value instanceof Date) { 91 90 return <span className="tree-date">Date({value.toISOString()})</span>; 92 91 } 93 92 94 93 if (value instanceof Map) { 95 - if (value.size === 0) return <span className="tree-collection">Map(0) {'{}'}</span>; 96 - const pad = ' '.repeat(indent + 1); 97 - const closePad = ' '.repeat(indent); 94 + if (value.size === 0) return <span className="tree-collection">Map(0) {"{}"}</span>; 95 + const pad = " ".repeat(indent + 1); 96 + const closePad = " ".repeat(indent); 98 97 return ( 99 98 <> 100 - <span className="tree-collection">Map({value.size}) {'{\n'}</span> 99 + <span className="tree-collection"> 100 + Map({value.size}) {"{\n"} 101 + </span> 101 102 {Array.from(value.entries()).map(([k, v], i) => ( 102 103 <React.Fragment key={i}> 103 - {pad}<JSXValue value={k} indent={indent + 1} ancestors={nextAncestors} /> =&gt; <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 104 - {i < value.size - 1 ? ',' : ''}{'\n'} 104 + {pad} 105 + <JSXValue value={k} indent={indent + 1} ancestors={nextAncestors} /> =&gt;{" "} 106 + <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 107 + {i < value.size - 1 ? "," : ""} 108 + {"\n"} 105 109 </React.Fragment> 106 110 ))} 107 - {closePad}{'}'} 111 + {closePad} 112 + {"}"} 108 113 </> 109 114 ); 110 115 } 111 116 112 117 if (value instanceof Set) { 113 - if (value.size === 0) return <span className="tree-collection">Set(0) {'{}'}</span>; 114 - const pad = ' '.repeat(indent + 1); 115 - const closePad = ' '.repeat(indent); 118 + if (value.size === 0) return <span className="tree-collection">Set(0) {"{}"}</span>; 119 + const pad = " ".repeat(indent + 1); 120 + const closePad = " ".repeat(indent); 116 121 return ( 117 122 <> 118 - <span className="tree-collection">Set({value.size}) {'{\n'}</span> 123 + <span className="tree-collection"> 124 + Set({value.size}) {"{\n"} 125 + </span> 119 126 {Array.from(value).map((v, i) => ( 120 127 <React.Fragment key={i}> 121 - {pad}<JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 122 - {i < value.size - 1 ? ',' : ''}{'\n'} 128 + {pad} 129 + <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 130 + {i < value.size - 1 ? "," : ""} 131 + {"\n"} 123 132 </React.Fragment> 124 133 ))} 125 - {closePad}{'}'} 134 + {closePad} 135 + {"}"} 126 136 </> 127 137 ); 128 138 } 129 139 130 140 if (value instanceof FormData) { 131 141 const entries = Array.from(value.entries()); 132 - if (entries.length === 0) return <span className="tree-collection">FormData {'{}'}</span>; 133 - const pad = ' '.repeat(indent + 1); 134 - const closePad = ' '.repeat(indent); 142 + if (entries.length === 0) return <span className="tree-collection">FormData {"{}"}</span>; 143 + const pad = " ".repeat(indent + 1); 144 + const closePad = " ".repeat(indent); 135 145 return ( 136 146 <> 137 - <span className="tree-collection">FormData {'{\n'}</span> 147 + <span className="tree-collection">FormData {"{\n"}</span> 138 148 {entries.map(([k, v], i) => ( 139 149 <React.Fragment key={i}> 140 - {pad}<span className="tree-key">{k}</span>: <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 141 - {i < entries.length - 1 ? ',' : ''}{'\n'} 150 + {pad} 151 + <span className="tree-key">{k}</span>:{" "} 152 + <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 153 + {i < entries.length - 1 ? "," : ""} 154 + {"\n"} 142 155 </React.Fragment> 143 156 ))} 144 - {closePad}{'}'} 157 + {closePad} 158 + {"}"} 145 159 </> 146 160 ); 147 161 } 148 162 149 163 if (value instanceof Blob) { 150 - return <span className="tree-collection">Blob({value.size} bytes, "{value.type || 'application/octet-stream'}")</span>; 164 + return ( 165 + <span className="tree-collection"> 166 + Blob({value.size} bytes, "{value.type || "application/octet-stream"}") 167 + </span> 168 + ); 151 169 } 152 170 153 171 if (ArrayBuffer.isView(value)) { 154 172 const name = value.constructor.name; 155 - const preview = Array.from(value.slice(0, 5)).join(', '); 156 - const suffix = value.length > 5 ? ', ...' : ''; 157 - return <span className="tree-collection">{name}({value.length}) [{preview}{suffix}]</span>; 173 + const preview = Array.from(value.slice(0, 5)).join(", "); 174 + const suffix = value.length > 5 ? ", ..." : ""; 175 + return ( 176 + <span className="tree-collection"> 177 + {name}({value.length}) [{preview} 178 + {suffix}] 179 + </span> 180 + ); 158 181 } 159 182 if (value instanceof ArrayBuffer) { 160 183 return <span className="tree-collection">ArrayBuffer({value.byteLength} bytes)</span>; ··· 172 195 }; 173 196 174 197 if (hasElements || value.length > 3) { 175 - const pad = ' '.repeat(indent + 1); 176 - const closePad = ' '.repeat(indent); 198 + const pad = " ".repeat(indent + 1); 199 + const closePad = " ".repeat(indent); 177 200 return ( 178 201 <> 179 - {'[\n'} 202 + {"[\n"} 180 203 {Array.from({ length: value.length }, (_, i) => ( 181 204 <React.Fragment key={i}> 182 - {pad}{renderItem(i)} 183 - {i < value.length - 1 ? ',' : ''}{'\n'} 205 + {pad} 206 + {renderItem(i)} 207 + {i < value.length - 1 ? "," : ""} 208 + {"\n"} 184 209 </React.Fragment> 185 210 ))} 186 211 {closePad}] ··· 193 218 {Array.from({ length: value.length }, (_, i) => ( 194 219 <React.Fragment key={i}> 195 220 {renderItem(i)} 196 - {i < value.length - 1 ? ', ' : ''} 221 + {i < value.length - 1 ? ", " : ""} 197 222 </React.Fragment> 198 223 ))} 199 224 ] ··· 205 230 return <JSXElement element={value} indent={indent} ancestors={nextAncestors} />; 206 231 } 207 232 208 - if (typeof value === 'object') { 209 - if (typeof value.next === 'function' && typeof value[Symbol.iterator] === 'function') { 210 - return <span className="tree-iterator">Iterator {'{}'}</span>; 233 + if (typeof value === "object") { 234 + if (typeof value.next === "function" && typeof value[Symbol.iterator] === "function") { 235 + return <span className="tree-iterator">Iterator {"{}"}</span>; 211 236 } 212 237 213 - if (typeof value[Symbol.asyncIterator] === 'function') { 214 - return <span className="tree-iterator">AsyncIterator {'{}'}</span>; 238 + if (typeof value[Symbol.asyncIterator] === "function") { 239 + return <span className="tree-iterator">AsyncIterator {"{}"}</span>; 215 240 } 216 241 217 242 if (value instanceof ReadableStream) { 218 - return <span className="tree-stream">ReadableStream {'{}'}</span>; 243 + return <span className="tree-stream">ReadableStream {"{}"}</span>; 219 244 } 220 245 221 - if (typeof value.then === 'function') { 246 + if (typeof value.then === "function") { 222 247 return ( 223 248 <ErrorBoundary> 224 249 <Suspense fallback={<PendingFallback />}> 225 250 <Await promise={value}> 226 - {(resolved) => <JSXValue value={resolved} indent={indent} ancestors={nextAncestors} />} 251 + {(resolved) => ( 252 + <JSXValue value={resolved} indent={indent} ancestors={nextAncestors} /> 253 + )} 227 254 </Await> 228 255 </Suspense> 229 256 </ErrorBoundary> 230 257 ); 231 258 } 232 259 233 - if (value.$$typeof === Symbol.for('react.lazy')) { 260 + if (value.$$typeof === Symbol.for("react.lazy")) { 234 261 return ( 235 262 <ErrorBoundary> 236 263 <Suspense fallback={<PendingFallback />}> ··· 240 267 ); 241 268 } 242 269 243 - const entries = Object.entries(value) 244 - if (entries.length === 0) return <>{'{}'}</>; 245 - if (entries.length <= 2 && entries.every(([, v]) => typeof v !== 'object' || v === null)) { 270 + const entries = Object.entries(value); 271 + if (entries.length === 0) return <>{"{}"}</>; 272 + if (entries.length <= 2 && entries.every(([, v]) => typeof v !== "object" || v === null)) { 246 273 return ( 247 274 <> 248 - {'{ '} 275 + {"{ "} 249 276 {entries.map(([k, v], i) => ( 250 277 <React.Fragment key={k}> 251 - <span className="tree-key">{k}</span>: <JSXValue value={v} indent={indent} ancestors={nextAncestors} /> 252 - {i < entries.length - 1 ? ', ' : ''} 278 + <span className="tree-key">{k}</span>:{" "} 279 + <JSXValue value={v} indent={indent} ancestors={nextAncestors} /> 280 + {i < entries.length - 1 ? ", " : ""} 253 281 </React.Fragment> 254 282 ))} 255 - {' }'} 283 + {" }"} 256 284 </> 257 285 ); 258 286 } 259 - const pad = ' '.repeat(indent + 1); 260 - const closePad = ' '.repeat(indent); 287 + const pad = " ".repeat(indent + 1); 288 + const closePad = " ".repeat(indent); 261 289 return ( 262 290 <> 263 - {'{\n'} 291 + {"{\n"} 264 292 {entries.map(([k, v], i) => ( 265 293 <React.Fragment key={k}> 266 - {pad}<span className="tree-key">{k}</span>: <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 267 - {i < entries.length - 1 ? ',' : ''}{'\n'} 294 + {pad} 295 + <span className="tree-key">{k}</span>:{" "} 296 + <JSXValue value={v} indent={indent + 1} ancestors={nextAncestors} /> 297 + {i < entries.length - 1 ? "," : ""} 298 + {"\n"} 268 299 </React.Fragment> 269 300 ))} 270 - {closePad}{'}'} 301 + {closePad} 302 + {"}"} 271 303 </> 272 304 ); 273 305 } ··· 277 309 278 310 function JSXElement({ element, indent, ancestors = [] }) { 279 311 const { type, props, key } = element; 280 - const pad = ' '.repeat(indent); 281 - const padInner = ' '.repeat(indent + 1); 312 + const pad = " ".repeat(indent); 313 + const padInner = " ".repeat(indent + 1); 282 314 283 - let tagName, tagClass = 'tree-tag'; 284 - if (typeof type === 'string') { 315 + let tagName, 316 + tagClass = "tree-tag"; 317 + if (typeof type === "string") { 285 318 tagName = type; 286 - } else if (typeof type === 'function') { 287 - tagName = type.displayName || type.name || 'Component'; 288 - tagClass = 'tree-client-tag'; 289 - } else if (typeof type === 'symbol') { 319 + } else if (typeof type === "function") { 320 + tagName = type.displayName || type.name || "Component"; 321 + tagClass = "tree-client-tag"; 322 + } else if (typeof type === "symbol") { 290 323 switch (type) { 291 - case Symbol.for('react.fragment'): 292 - tagName = 'Fragment'; 324 + case Symbol.for("react.fragment"): 325 + tagName = "Fragment"; 293 326 break; 294 - case Symbol.for('react.profiler'): 295 - tagName = 'Profiler'; 327 + case Symbol.for("react.profiler"): 328 + tagName = "Profiler"; 296 329 break; 297 - case Symbol.for('react.strict_mode'): 298 - tagName = 'StrictMode'; 330 + case Symbol.for("react.strict_mode"): 331 + tagName = "StrictMode"; 299 332 break; 300 - case Symbol.for('react.suspense'): 301 - tagName = 'Suspense'; 333 + case Symbol.for("react.suspense"): 334 + tagName = "Suspense"; 302 335 break; 303 - case Symbol.for('react.suspense_list'): 304 - tagName = 'SuspenseList'; 336 + case Symbol.for("react.suspense_list"): 337 + tagName = "SuspenseList"; 305 338 break; 306 - case Symbol.for('react.activity'): 307 - tagName = 'Activity'; 339 + case Symbol.for("react.activity"): 340 + tagName = "Activity"; 308 341 break; 309 - case Symbol.for('react.view_transition'): 310 - tagName = 'ViewTransition'; 342 + case Symbol.for("react.view_transition"): 343 + tagName = "ViewTransition"; 311 344 break; 312 345 default: 313 - tagName = 'Unknown'; 346 + tagName = "Unknown"; 314 347 } 315 - tagClass = 'tree-react-tag'; 316 - } else if (type && typeof type === 'object' && type.$$typeof) { 317 - if (type.$$typeof === Symbol.for('react.lazy')) { 348 + tagClass = "tree-react-tag"; 349 + } else if (type && typeof type === "object" && type.$$typeof) { 350 + if (type.$$typeof === Symbol.for("react.lazy")) { 318 351 return ( 319 352 <ErrorBoundary> 320 353 <Suspense fallback={<PendingFallback />}> ··· 323 356 </ErrorBoundary> 324 357 ); 325 358 } 326 - tagName = 'Component'; 327 - tagClass = 'tree-client-tag'; 359 + tagName = "Component"; 360 + tagClass = "tree-client-tag"; 328 361 } else { 329 - tagName = 'Unknown'; 362 + tagName = "Unknown"; 330 363 } 331 364 332 365 const { children, ...otherProps } = props || {}; 333 - const propEntries = Object.entries(otherProps).filter(([k]) => 334 - !['key', 'ref', '__self', '__source'].includes(k) 366 + const propEntries = Object.entries(otherProps).filter( 367 + ([k]) => !["key", "ref", "__self", "__source"].includes(k), 335 368 ); 336 369 337 - if (children === undefined || children === null || (Array.isArray(children) && children.length === 0)) { 370 + if ( 371 + children === undefined || 372 + children === null || 373 + (Array.isArray(children) && children.length === 0) 374 + ) { 338 375 return ( 339 376 <> 340 377 <span className={tagClass}>&lt;{tagName}</span> 341 - {key != null && <> <span className="tree-prop-name">key</span>=<span className="tree-string">"{key}"</span></>} 378 + {key != null && ( 379 + <> 380 + {" "} 381 + <span className="tree-prop-name">key</span>=<span className="tree-string">"{key}"</span> 382 + </> 383 + )} 342 384 {propEntries.map(([k, v]) => ( 343 385 <JSXProp key={k} name={k} value={v} indent={indent + 1} ancestors={ancestors} /> 344 386 ))} ··· 347 389 ); 348 390 } 349 391 350 - const hasComplexChildren = typeof children !== 'string' && typeof children !== 'number'; 392 + const hasComplexChildren = typeof children !== "string" && typeof children !== "number"; 351 393 return ( 352 394 <> 353 395 <span className={tagClass}>&lt;{tagName}</span> 354 - {key != null && <> <span className="tree-prop-name">key</span>=<span className="tree-string">"{key}"</span></>} 396 + {key != null && ( 397 + <> 398 + {" "} 399 + <span className="tree-prop-name">key</span>=<span className="tree-string">"{key}"</span> 400 + </> 401 + )} 355 402 {propEntries.map(([k, v]) => ( 356 403 <JSXProp key={k} name={k} value={v} indent={indent + 1} ancestors={ancestors} /> 357 404 ))} 358 405 <span className={tagClass}>&gt;</span> 359 406 {hasComplexChildren ? ( 360 407 <> 361 - {'\n'}{padInner}<JSXChildren value={children} indent={indent + 1} ancestors={ancestors} />{'\n'}{pad} 408 + {"\n"} 409 + {padInner} 410 + <JSXChildren value={children} indent={indent + 1} ancestors={ancestors} /> 411 + {"\n"} 412 + {pad} 362 413 </> 363 414 ) : ( 364 415 <JSXChildren value={children} indent={indent + 1} ancestors={ancestors} /> ··· 369 420 } 370 421 371 422 function JSXProp({ name, value, indent, ancestors = [] }) { 372 - if (typeof value === 'string') { 373 - return <> <span className="tree-prop-name">{name}</span>=<span className="tree-string">"{escapeHtml(value)}"</span></>; 423 + if (typeof value === "string") { 424 + return ( 425 + <> 426 + {" "} 427 + <span className="tree-prop-name">{name}</span>= 428 + <span className="tree-string">"{escapeHtml(value)}"</span> 429 + </> 430 + ); 374 431 } 375 432 if (isReactElement(value)) { 376 - const pad = ' '.repeat(indent); 377 - const closePad = ' '.repeat(indent - 1); 433 + const pad = " ".repeat(indent); 434 + const closePad = " ".repeat(indent - 1); 378 435 return ( 379 436 <> 380 - {' '}<span className="tree-prop-name">{name}</span>={'{'} 381 - {'\n'}{pad}<JSXValue value={value} indent={indent} ancestors={ancestors} />{'\n'}{closePad} 382 - {'}'} 437 + {" "} 438 + <span className="tree-prop-name">{name}</span>={"{"} 439 + {"\n"} 440 + {pad} 441 + <JSXValue value={value} indent={indent} ancestors={ancestors} /> 442 + {"\n"} 443 + {closePad} 444 + {"}"} 383 445 </> 384 446 ); 385 447 } 386 - if (Array.isArray(value) && value.some(v => isReactElement(v))) { 387 - const pad = ' '.repeat(indent); 388 - const closePad = ' '.repeat(indent - 1); 448 + if (Array.isArray(value) && value.some((v) => isReactElement(v))) { 449 + const pad = " ".repeat(indent); 450 + const closePad = " ".repeat(indent - 1); 389 451 return ( 390 452 <> 391 - {' '}<span className="tree-prop-name">{name}</span>={'{['} 392 - {'\n'} 453 + {" "} 454 + <span className="tree-prop-name">{name}</span>={"{["} 455 + {"\n"} 393 456 {value.map((v, i) => ( 394 457 <React.Fragment key={i}> 395 - {pad}<JSXValue value={v} indent={indent} ancestors={ancestors} /> 396 - {i < value.length - 1 ? ',' : ''}{'\n'} 458 + {pad} 459 + <JSXValue value={v} indent={indent} ancestors={ancestors} /> 460 + {i < value.length - 1 ? "," : ""} 461 + {"\n"} 397 462 </React.Fragment> 398 463 ))} 399 - {closePad}{']}'} 464 + {closePad} 465 + {"]}"} 400 466 </> 401 467 ); 402 468 } 403 - return <> <span className="tree-prop-name">{name}</span>={'{'}<JSXValue value={value} indent={indent} ancestors={ancestors} />{'}'}</>; 469 + return ( 470 + <> 471 + {" "} 472 + <span className="tree-prop-name">{name}</span>={"{"} 473 + <JSXValue value={value} indent={indent} ancestors={ancestors} /> 474 + {"}"} 475 + </> 476 + ); 404 477 } 405 478 406 479 function JSXChildren({ value, indent, ancestors = [] }) { 407 - if (typeof value === 'string') return <>{escapeHtml(value)}</>; 408 - if (typeof value === 'number') return <>{'{' + value + '}'}</>; 480 + if (typeof value === "string") return <>{escapeHtml(value)}</>; 481 + if (typeof value === "number") return <>{"{" + value + "}"}</>; 409 482 if (Array.isArray(value)) { 410 - const pad = ' '.repeat(indent); 411 - const hasComplex = value.some(v => isReactElement(v)); 483 + const pad = " ".repeat(indent); 484 + const hasComplex = value.some((v) => isReactElement(v)); 412 485 if (hasComplex) { 413 486 return ( 414 487 <> 415 488 {value.map((child, i) => ( 416 489 <React.Fragment key={i}> 417 490 <JSXChildren value={child} indent={indent} ancestors={ancestors} /> 418 - {i < value.length - 1 ? '\n' + pad : ''} 491 + {i < value.length - 1 ? "\n" + pad : ""} 419 492 </React.Fragment> 420 493 ))} 421 494 </> ··· 438 511 if (!flightPromise) { 439 512 return ( 440 513 <div className="flight-tree"> 441 - <pre className="jsx-output"><PendingFallback /></pre> 514 + <pre className="jsx-output"> 515 + <PendingFallback /> 516 + </pre> 442 517 </div> 443 518 ); 444 519 }
+80 -62
src/client/ui/Workspace.jsx
··· 1 - import React, { useState, useEffect, useRef, useCallback, useSyncExternalStore } from 'react'; 2 - import { encodeReply } from 'react-server-dom-webpack/client'; 3 - import { Timeline, SteppableStream, registerClientModule, evaluateClientModule } from '../runtime/index.js'; 4 - import { ServerWorker } from '../server-worker.js'; 5 - import { parseClientModule, parseServerActions, compileToCommonJS, buildManifest } from '../../shared/compiler.js'; 6 - import { CodeEditor } from './CodeEditor.jsx'; 7 - import { FlightLog } from './FlightLog.jsx'; 8 - import { LivePreview } from './LivePreview.jsx'; 1 + import React, { useState, useEffect, useRef, useCallback, useSyncExternalStore } from "react"; 2 + import { encodeReply } from "react-server-dom-webpack/client"; 3 + import { 4 + Timeline, 5 + SteppableStream, 6 + registerClientModule, 7 + evaluateClientModule, 8 + } from "../runtime/index.js"; 9 + import { ServerWorker } from "../server-worker.js"; 10 + import { 11 + parseClientModule, 12 + parseServerActions, 13 + compileToCommonJS, 14 + buildManifest, 15 + } from "../../shared/compiler.js"; 16 + import { CodeEditor } from "./CodeEditor.jsx"; 17 + import { FlightLog } from "./FlightLog.jsx"; 18 + import { LivePreview } from "./LivePreview.jsx"; 9 19 10 20 export function Workspace({ initialServerCode, initialClientCode, onCodeChange }) { 11 21 const [serverCode, setServerCode] = useState(initialServerCode); ··· 44 54 timeline.skipToEntryEnd(); 45 55 }, [timeline]); 46 56 47 - const handleAddRawAction = useCallback(async (actionName, rawPayload) => { 48 - try { 49 - const responseRaw = await serverWorker.callActionRaw(actionName, rawPayload); 50 - const stream = new SteppableStream(responseRaw, { callServer: callServerRef.current }); 51 - await stream.waitForBuffer(); 52 - timeline.addAction(actionName, rawPayload, stream); 53 - } catch (err) { 54 - console.error('[raw action] Failed:', err); 55 - } 56 - }, [serverWorker, timeline, callServerRef]); 57 + const handleAddRawAction = useCallback( 58 + async (actionName, rawPayload) => { 59 + try { 60 + const responseRaw = await serverWorker.callActionRaw(actionName, rawPayload); 61 + const stream = new SteppableStream(responseRaw, { callServer: callServerRef.current }); 62 + await stream.waitForBuffer(); 63 + timeline.addAction(actionName, rawPayload, stream); 64 + } catch (err) { 65 + console.error("[raw action] Failed:", err); 66 + } 67 + }, 68 + [serverWorker, timeline, callServerRef], 69 + ); 57 70 58 - const compile = useCallback(async (sCode, cCode) => { 59 - try { 60 - setError(null); 61 - timeline.clear(); 71 + const compile = useCallback( 72 + async (sCode, cCode) => { 73 + try { 74 + setError(null); 75 + timeline.clear(); 62 76 63 - const clientExports = parseClientModule(cCode); 64 - const manifest = buildManifest('client', clientExports); 65 - const compiledClient = compileToCommonJS(cCode); 66 - const clientModule = evaluateClientModule(compiledClient); 67 - registerClientModule('client', clientModule); 77 + const clientExports = parseClientModule(cCode); 78 + const manifest = buildManifest("client", clientExports); 79 + const compiledClient = compileToCommonJS(cCode); 80 + const clientModule = evaluateClientModule(compiledClient); 81 + registerClientModule("client", clientModule); 68 82 69 - const actionNames = parseServerActions(sCode); 70 - const compiledServer = compileToCommonJS(sCode); 71 - setAvailableActions(actionNames); 83 + const actionNames = parseServerActions(sCode); 84 + const compiledServer = compileToCommonJS(sCode); 85 + setAvailableActions(actionNames); 72 86 73 - await serverWorker.deploy({ 74 - compiledCode: compiledServer, 75 - manifest, 76 - actionNames, 77 - }); 87 + await serverWorker.deploy({ 88 + compiledCode: compiledServer, 89 + manifest, 90 + actionNames, 91 + }); 78 92 79 - const callServer = actionNames.length > 0 80 - ? async (actionId, args) => { 81 - const actionName = actionId.split('#')[0]; 82 - const encodedArgs = await encodeReply(args); 83 - const argsDisplay = typeof encodedArgs === 'string' 84 - ? `0=${encodedArgs}` 85 - : new URLSearchParams(encodedArgs).toString(); 93 + const callServer = 94 + actionNames.length > 0 95 + ? async (actionId, args) => { 96 + const actionName = actionId.split("#")[0]; 97 + const encodedArgs = await encodeReply(args); 98 + const argsDisplay = 99 + typeof encodedArgs === "string" 100 + ? `0=${encodedArgs}` 101 + : new URLSearchParams(encodedArgs).toString(); 86 102 87 - const responseRaw = await serverWorker.callAction(actionName, encodedArgs); 88 - const stream = new SteppableStream(responseRaw, { callServer }); 89 - await stream.waitForBuffer(); 90 - timeline.addAction(actionName, argsDisplay, stream); 91 - return stream.flightPromise; 92 - } 93 - : null; 103 + const responseRaw = await serverWorker.callAction(actionName, encodedArgs); 104 + const stream = new SteppableStream(responseRaw, { callServer }); 105 + await stream.waitForBuffer(); 106 + timeline.addAction(actionName, argsDisplay, stream); 107 + return stream.flightPromise; 108 + } 109 + : null; 94 110 95 - callServerRef.current = callServer; 111 + callServerRef.current = callServer; 96 112 97 - const renderRaw = await serverWorker.render(); 98 - const renderStream = new SteppableStream(renderRaw, { callServer }); 99 - await renderStream.waitForBuffer(); 113 + const renderRaw = await serverWorker.render(); 114 + const renderStream = new SteppableStream(renderRaw, { callServer }); 115 + await renderStream.waitForBuffer(); 100 116 101 - timeline.setRender(renderStream); 102 - setClientModuleReady(true); 103 - } catch (err) { 104 - console.error('[compile] Error:', err); 105 - setError(err.message || String(err)); 106 - timeline.clear(); 107 - setClientModuleReady(false); 108 - } 109 - }, [timeline, serverWorker, callServerRef]); 117 + timeline.setRender(renderStream); 118 + setClientModuleReady(true); 119 + } catch (err) { 120 + console.error("[compile] Error:", err); 121 + setError(err.message || String(err)); 122 + timeline.clear(); 123 + setClientModuleReady(false); 124 + } 125 + }, 126 + [timeline, serverWorker, callServerRef], 127 + ); 110 128 111 129 const handleReset = useCallback(() => { 112 130 compile(serverCode, clientCode);
+14 -11
src/client/webpack-shim.js
··· 4 4 window.__webpack_module_cache__ = moduleCache; 5 5 window.__webpack_modules__ = moduleFactories; 6 6 7 - window.__webpack_require__ = function(moduleId) { 7 + window.__webpack_require__ = function (moduleId) { 8 8 if (moduleCache[moduleId]) { 9 9 return moduleCache[moduleId].exports || moduleCache[moduleId]; 10 10 } ··· 19 19 20 20 window.__webpack_require__.m = moduleFactories; 21 21 window.__webpack_require__.c = moduleCache; 22 - window.__webpack_require__.d = function(exports, definition) { 22 + window.__webpack_require__.d = function (exports, definition) { 23 23 for (const key in definition) { 24 - if (Object.prototype.hasOwnProperty.call(definition, key) && !Object.prototype.hasOwnProperty.call(exports, key)) { 24 + if ( 25 + Object.prototype.hasOwnProperty.call(definition, key) && 26 + !Object.prototype.hasOwnProperty.call(exports, key) 27 + ) { 25 28 Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 26 29 } 27 30 } 28 31 }; 29 - window.__webpack_require__.r = function(exports) { 30 - if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { 31 - Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 32 + window.__webpack_require__.r = function (exports) { 33 + if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 34 + Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); 32 35 } 33 - Object.defineProperty(exports, '__esModule', { value: true }); 36 + Object.defineProperty(exports, "__esModule", { value: true }); 34 37 }; 35 - window.__webpack_require__.o = function(obj, prop) { 38 + window.__webpack_require__.o = function (obj, prop) { 36 39 return Object.prototype.hasOwnProperty.call(obj, prop); 37 40 }; 38 41 39 - window.__webpack_chunk_load__ = function(chunkId) { 42 + window.__webpack_chunk_load__ = function (chunkId) { 40 43 return Promise.resolve(); 41 44 }; 42 45 43 - window.__webpack_require__.e = function(chunkId) { 46 + window.__webpack_require__.e = function (chunkId) { 44 47 return Promise.resolve(); 45 48 }; 46 49 47 - window.__webpack_require__.p = '/'; 50 + window.__webpack_require__.p = "/";
+2 -5
src/embed.js
··· 26 26 27 27 // Get the embed URL relative to this script's location 28 28 const getEmbedUrl = () => { 29 - return new URL('embed.html', import.meta.url).href; 29 + return new URL("embed.html", import.meta.url).href; 30 30 }; 31 31 32 32 /** ··· 38 38 * @returns {Object} - Control object with methods to interact with the embed 39 39 */ 40 40 export function mount(container, { server, client }) { 41 - const el = 42 - typeof container === "string" 43 - ? document.querySelector(container) 44 - : container; 41 + const el = typeof container === "string" ? document.querySelector(container) : container; 45 42 46 43 if (!el) { 47 44 throw new Error(`RSC Explorer: Container not found: ${container}`);
+14 -11
src/server/webpack-shim.js
··· 3 3 4 4 const moduleCache = {}; 5 5 6 - self.__webpack_require__ = function(moduleId) { 6 + self.__webpack_require__ = function (moduleId) { 7 7 if (moduleCache[moduleId]) { 8 8 return moduleCache[moduleId]; 9 9 } ··· 12 12 13 13 self.__webpack_require__.m = {}; 14 14 self.__webpack_require__.c = moduleCache; 15 - self.__webpack_require__.d = function(exports, definition) { 15 + self.__webpack_require__.d = function (exports, definition) { 16 16 for (const key in definition) { 17 - if (Object.prototype.hasOwnProperty.call(definition, key) && !Object.prototype.hasOwnProperty.call(exports, key)) { 17 + if ( 18 + Object.prototype.hasOwnProperty.call(definition, key) && 19 + !Object.prototype.hasOwnProperty.call(exports, key) 20 + ) { 18 21 Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 19 22 } 20 23 } 21 24 }; 22 - self.__webpack_require__.r = function(exports) { 23 - if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { 24 - Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 25 + self.__webpack_require__.r = function (exports) { 26 + if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 27 + Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); 25 28 } 26 - Object.defineProperty(exports, '__esModule', { value: true }); 29 + Object.defineProperty(exports, "__esModule", { value: true }); 27 30 }; 28 - self.__webpack_require__.o = function(obj, prop) { 31 + self.__webpack_require__.o = function (obj, prop) { 29 32 return Object.prototype.hasOwnProperty.call(obj, prop); 30 33 }; 31 34 32 - self.__webpack_chunk_load__ = function(chunkId) { 35 + self.__webpack_chunk_load__ = function (chunkId) { 33 36 return Promise.resolve(); 34 37 }; 35 38 36 - self.__webpack_require__.e = function(chunkId) { 39 + self.__webpack_require__.e = function (chunkId) { 37 40 return Promise.resolve(); 38 41 }; 39 42 40 - self.__webpack_require__.p = '/'; 43 + self.__webpack_require__.p = "/";
+36 -27
src/server/worker.js
··· 4 4 // - `deploy`: Store compiled code, manifest, etc. (like deploying to production) 5 5 // - `render`/`action`: Execute against deployed code 6 6 7 - import './webpack-shim.js'; 8 - import '../client/byte-stream-polyfill.js'; 9 - import 'text-encoding'; 7 + import "./webpack-shim.js"; 8 + import "../client/byte-stream-polyfill.js"; 9 + import "text-encoding"; 10 10 11 11 import { 12 12 renderToReadableStream, 13 13 registerServerReference, 14 14 createClientModuleProxy, 15 15 decodeReply, 16 - } from 'react-server-dom-webpack/server'; 17 - import React from 'react'; 16 + } from "react-server-dom-webpack/server"; 17 + import React from "react"; 18 18 19 19 let deployed = null; 20 20 ··· 25 25 while (true) { 26 26 const { done, value } = await reader.read(); 27 27 if (done) { 28 - self.postMessage({ type: 'stream-end', requestId }); 28 + self.postMessage({ type: "stream-end", requestId }); 29 29 break; 30 30 } 31 - self.postMessage({ type: 'stream-chunk', requestId, chunk: value }); 31 + self.postMessage({ type: "stream-chunk", requestId, chunk: value }); 32 32 } 33 33 } catch (err) { 34 - self.postMessage({ type: 'stream-error', requestId, error: { message: err.message } }); 34 + self.postMessage({ type: "stream-error", requestId, error: { message: err.message } }); 35 35 } 36 36 } 37 37 ··· 40 40 41 41 try { 42 42 switch (type) { 43 - case 'deploy': 43 + case "deploy": 44 44 handleDeploy(event.data); 45 45 break; 46 - case 'render': 46 + case "render": 47 47 await handleRender(event.data); 48 48 break; 49 - case 'action': 49 + case "action": 50 50 await handleAction(event.data); 51 51 break; 52 52 default: ··· 54 54 } 55 55 } catch (error) { 56 56 self.postMessage({ 57 - type: 'error', 57 + type: "error", 58 58 requestId, 59 59 error: { message: error.message, stack: error.stack }, 60 60 }); ··· 62 62 }; 63 63 64 64 function handleDeploy({ compiledCode, manifest, actionNames, requestId }) { 65 - const clientModule = createClientModuleProxy('client'); 66 - const modules = { 'react': React, './client': clientModule }; 65 + const clientModule = createClientModuleProxy("client"); 66 + const modules = { react: React, "./client": clientModule }; 67 67 const serverModule = evalModule(compiledCode, modules, actionNames); 68 68 69 69 deployed = { manifest, serverModule, actionNames }; 70 70 71 - self.postMessage({ type: 'deployed', requestId }); 71 + self.postMessage({ type: "deployed", requestId }); 72 72 } 73 73 74 74 function requireDeployed() { 75 - if (!deployed) throw new Error('No code deployed'); 75 + if (!deployed) throw new Error("No code deployed"); 76 76 return deployed; 77 77 } 78 78 ··· 80 80 const { manifest, serverModule } = requireDeployed(); 81 81 82 82 const App = serverModule.default || serverModule; 83 - const element = typeof App === 'function' ? React.createElement(App) : App; 83 + const element = typeof App === "function" ? React.createElement(App) : App; 84 84 85 85 const flightStream = renderToReadableStream(element, manifest, { 86 86 onError: (error) => error.message || String(error), 87 87 }); 88 88 89 - self.postMessage({ type: 'stream-start', requestId }); 89 + self.postMessage({ type: "stream-start", requestId }); 90 90 streamToMain(flightStream, requestId); 91 91 } 92 92 ··· 94 94 const { manifest, serverModule } = requireDeployed(); 95 95 96 96 const actionFn = serverModule[actionId]; 97 - if (typeof actionFn !== 'function') { 97 + if (typeof actionFn !== "function") { 98 98 throw new Error(`Action "${actionId}" not found`); 99 99 } 100 100 ··· 106 106 onError: (error) => error.message || String(error), 107 107 }); 108 108 109 - self.postMessage({ type: 'stream-start', requestId }); 109 + self.postMessage({ type: "stream-start", requestId }); 110 110 streamToMain(flightStream, requestId); 111 111 } 112 112 113 113 function reconstructEncodedArgs(encodedArgs) { 114 - if (encodedArgs.type === 'formdata') { 114 + if (encodedArgs.type === "formdata") { 115 115 const formData = new FormData(); 116 116 for (const [key, value] of new URLSearchParams(encodedArgs.data)) { 117 117 formData.append(key, value); ··· 124 124 function evalModule(code, modules, actionNames) { 125 125 let finalCode = code; 126 126 if (actionNames?.length > 0) { 127 - finalCode += '\n' + actionNames 128 - .map(name => `__registerServerReference(${name}, "${name}", "${name}"); exports.${name} = ${name};`) 129 - .join('\n'); 127 + finalCode += 128 + "\n" + 129 + actionNames 130 + .map( 131 + (name) => 132 + `__registerServerReference(${name}, "${name}", "${name}"); exports.${name} = ${name};`, 133 + ) 134 + .join("\n"); 130 135 } 131 136 132 137 const module = { exports: {} }; ··· 135 140 return modules[id]; 136 141 }; 137 142 138 - new Function('module', 'exports', 'require', 'React', '__registerServerReference', finalCode)( 139 - module, module.exports, require, React, registerServerReference 143 + new Function("module", "exports", "require", "React", "__registerServerReference", finalCode)( 144 + module, 145 + module.exports, 146 + require, 147 + React, 148 + registerServerReference, 140 149 ); 141 150 142 151 return module.exports; 143 152 } 144 153 145 - self.postMessage({ type: 'ready' }); 154 + self.postMessage({ type: "ready" });
+65 -70
src/shared/compiler.js
··· 1 - import { transform } from '@babel/standalone'; 1 + import { transform } from "@babel/standalone"; 2 2 3 3 export function parseExports(code) { 4 4 const exports = []; 5 5 const { ast } = transform(code, { 6 - presets: ['react'], 6 + presets: ["react"], 7 7 ast: true, 8 8 }); 9 9 10 10 for (const node of ast.program.body) { 11 - if (node.type === 'ExportNamedDeclaration') { 11 + if (node.type === "ExportNamedDeclaration") { 12 12 if (node.declaration) { 13 - if (node.declaration.type === 'FunctionDeclaration') { 13 + if (node.declaration.type === "FunctionDeclaration") { 14 14 exports.push(node.declaration.id.name); 15 - } else if (node.declaration.type === 'VariableDeclaration') { 15 + } else if (node.declaration.type === "VariableDeclaration") { 16 16 for (const decl of node.declaration.declarations) { 17 - if (decl.id.type === 'Identifier') { 17 + if (decl.id.type === "Identifier") { 18 18 exports.push(decl.id.name); 19 19 } 20 20 } 21 21 } 22 22 } 23 - } else if (node.type === 'ExportDefaultDeclaration') { 24 - exports.push('default'); 23 + } else if (node.type === "ExportDefaultDeclaration") { 24 + exports.push("default"); 25 25 } 26 26 } 27 27 return exports; ··· 30 30 function hasDirective(block, directive) { 31 31 if (!block) return false; 32 32 if (block.directives?.length > 0) { 33 - if (block.directives.some(d => d.value?.value === directive)) { 33 + if (block.directives.some((d) => d.value?.value === directive)) { 34 34 return true; 35 35 } 36 36 } 37 37 // Fallback for some parsers 38 38 if (block.body) { 39 39 for (const node of block.body) { 40 - if (node.type !== 'ExpressionStatement') break; 40 + if (node.type !== "ExpressionStatement") break; 41 41 if (node.directive === directive) return true; 42 - if (node.expression?.type === 'StringLiteral' && node.expression.value === directive) return true; 42 + if (node.expression?.type === "StringLiteral" && node.expression.value === directive) 43 + return true; 43 44 } 44 45 } 45 46 return false; 46 47 } 47 48 48 49 function hasUseServerDirective(body) { 49 - if (!body || body.type !== 'BlockStatement') return false; 50 - return hasDirective(body, 'use server'); 50 + if (!body || body.type !== "BlockStatement") return false; 51 + return hasDirective(body, "use server"); 51 52 } 52 53 53 54 export function parseServerActions(code) { 54 55 const { ast } = transform(code, { 55 - presets: ['react'], 56 + presets: ["react"], 56 57 ast: true, 57 58 }); 58 59 59 60 // 'use client' is not supported - this REPL only handles server code 60 - if (hasDirective(ast.program, 'use client')) { 61 - throw new Error( 62 - '"use client" is not supported. This environment only handles server code.' 63 - ); 61 + if (hasDirective(ast.program, "use client")) { 62 + throw new Error('"use client" is not supported. This environment only handles server code.'); 64 63 } 65 64 66 - const hasModuleUseServer = hasDirective(ast.program, 'use server'); 65 + const hasModuleUseServer = hasDirective(ast.program, "use server"); 67 66 68 67 const actions = []; 69 68 70 69 function checkNoUseClient(body, name) { 71 - if (hasDirective(body, 'use client')) { 70 + if (hasDirective(body, "use client")) { 72 71 throw new Error( 73 - `"use client" is not supported${name ? ` (found in "${name}")` : ''}. This environment only handles server code.` 72 + `"use client" is not supported${name ? ` (found in "${name}")` : ""}. This environment only handles server code.`, 74 73 ); 75 74 } 76 75 } ··· 87 86 } 88 87 89 88 for (const node of ast.program.body) { 90 - if (node.type === 'FunctionDeclaration' && node.id) { 89 + if (node.type === "FunctionDeclaration" && node.id) { 91 90 checkNoUseClient(node.body, node.id.name); 92 91 if (!hasModuleUseServer && hasUseServerDirective(node.body)) { 93 92 actions.push(node.id.name); 94 93 } 95 - } 96 - else if (node.type === 'ExportNamedDeclaration' && node.declaration) { 97 - if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) { 94 + } else if (node.type === "ExportNamedDeclaration" && node.declaration) { 95 + if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) { 98 96 collectExportedFunction(node.declaration, node.declaration.id.name); 99 - } 100 - else if (node.declaration.type === 'VariableDeclaration') { 97 + } else if (node.declaration.type === "VariableDeclaration") { 101 98 for (const decl of node.declaration.declarations) { 102 - if (decl.id.type === 'Identifier' && decl.init) { 103 - if (decl.init.type === 'ArrowFunctionExpression' || 104 - decl.init.type === 'FunctionExpression') { 99 + if (decl.id.type === "Identifier" && decl.init) { 100 + if ( 101 + decl.init.type === "ArrowFunctionExpression" || 102 + decl.init.type === "FunctionExpression" 103 + ) { 105 104 collectExportedFunction(decl.init, decl.id.name); 106 105 } 107 106 } 108 107 } 109 108 } 110 - } 111 - else if (node.type === 'VariableDeclaration') { 109 + } else if (node.type === "VariableDeclaration") { 112 110 for (const decl of node.declarations) { 113 - if (decl.id.type === 'Identifier' && decl.init) { 114 - if (decl.init.type === 'ArrowFunctionExpression' || 115 - decl.init.type === 'FunctionExpression') { 111 + if (decl.id.type === "Identifier" && decl.init) { 112 + if ( 113 + decl.init.type === "ArrowFunctionExpression" || 114 + decl.init.type === "FunctionExpression" 115 + ) { 116 116 checkNoUseClient(decl.init.body, decl.id.name); 117 117 if (!hasModuleUseServer && hasUseServerDirective(decl.init.body)) { 118 118 actions.push(decl.id.name); ··· 128 128 129 129 export function parseClientModule(code) { 130 130 const { ast } = transform(code, { 131 - presets: ['react'], 131 + presets: ["react"], 132 132 ast: true, 133 133 }); 134 134 135 135 // Require 'use client' at module level 136 - if (!hasDirective(ast.program, 'use client')) { 137 - throw new Error( 138 - 'Client code must start with "use client" directive.' 139 - ); 136 + if (!hasDirective(ast.program, "use client")) { 137 + throw new Error('Client code must start with "use client" directive.'); 140 138 } 141 139 142 - if (hasDirective(ast.program, 'use server')) { 143 - throw new Error( 144 - '"use server" is not supported in client code.' 145 - ); 140 + if (hasDirective(ast.program, "use server")) { 141 + throw new Error('"use server" is not supported in client code.'); 146 142 } 147 143 148 144 function checkFunction(body, name) { 149 - if (!body || body.type !== 'BlockStatement') return; 150 - if (hasDirective(body, 'use client')) { 145 + if (!body || body.type !== "BlockStatement") return; 146 + if (hasDirective(body, "use client")) { 151 147 throw new Error( 152 - `"use client" must be at module level, not inside functions${name ? ` (found in "${name}")` : ''}.` 148 + `"use client" must be at module level, not inside functions${name ? ` (found in "${name}")` : ""}.`, 153 149 ); 154 150 } 155 - if (hasDirective(body, 'use server')) { 151 + if (hasDirective(body, "use server")) { 156 152 throw new Error( 157 - `"use server" is not supported in client code${name ? ` (found in "${name}")` : ''}.` 153 + `"use server" is not supported in client code${name ? ` (found in "${name}")` : ""}.`, 158 154 ); 159 155 } 160 156 } ··· 162 158 const exports = []; 163 159 164 160 for (const node of ast.program.body) { 165 - if (node.type === 'FunctionDeclaration' && node.id) { 161 + if (node.type === "FunctionDeclaration" && node.id) { 166 162 checkFunction(node.body, node.id.name); 167 - } 168 - else if (node.type === 'ExportNamedDeclaration') { 163 + } else if (node.type === "ExportNamedDeclaration") { 169 164 if (node.declaration) { 170 - if (node.declaration.type === 'FunctionDeclaration') { 165 + if (node.declaration.type === "FunctionDeclaration") { 171 166 checkFunction(node.declaration.body, node.declaration.id?.name); 172 167 if (node.declaration.id) { 173 168 exports.push(node.declaration.id.name); 174 169 } 175 - } else if (node.declaration.type === 'VariableDeclaration') { 170 + } else if (node.declaration.type === "VariableDeclaration") { 176 171 for (const decl of node.declaration.declarations) { 177 - if (decl.init?.type === 'ArrowFunctionExpression' || 178 - decl.init?.type === 'FunctionExpression') { 172 + if ( 173 + decl.init?.type === "ArrowFunctionExpression" || 174 + decl.init?.type === "FunctionExpression" 175 + ) { 179 176 checkFunction(decl.init.body, decl.id?.name); 180 177 } 181 - if (decl.id.type === 'Identifier') { 178 + if (decl.id.type === "Identifier") { 182 179 exports.push(decl.id.name); 183 180 } 184 181 } 185 182 } 186 183 } 187 - } 188 - else if (node.type === 'ExportDefaultDeclaration') { 189 - exports.push('default'); 190 - } 191 - else if (node.type === 'VariableDeclaration') { 184 + } else if (node.type === "ExportDefaultDeclaration") { 185 + exports.push("default"); 186 + } else if (node.type === "VariableDeclaration") { 192 187 for (const decl of node.declarations) { 193 - if (decl.init?.type === 'ArrowFunctionExpression' || 194 - decl.init?.type === 'FunctionExpression') { 188 + if ( 189 + decl.init?.type === "ArrowFunctionExpression" || 190 + decl.init?.type === "FunctionExpression" 191 + ) { 195 192 checkFunction(decl.init.body, decl.id?.name); 196 193 } 197 194 } ··· 203 200 204 201 export function compileToCommonJS(code) { 205 202 const { code: compiled } = transform(code, { 206 - presets: ['react'], 207 - sourceType: 'module', 208 - plugins: [ 209 - ['transform-modules-commonjs', { loose: true }] 210 - ], 203 + presets: ["react"], 204 + sourceType: "module", 205 + plugins: [["transform-modules-commonjs", { loose: true }]], 211 206 }); 212 207 return compiled; 213 208 } ··· 217 212 [moduleId]: { 218 213 id: moduleId, 219 214 chunks: [], 220 - name: '*', 215 + name: "*", 221 216 }, 222 217 }; 223 218 for (const name of exportNames) {
+129 -134
test-embed.html
··· 1 1 <!doctype html> 2 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>RSC Explorer Embeds</title> 7 - <style> 8 - body { 9 - font-family: system-ui, sans-serif; 10 - max-width: 900px; 11 - margin: 0 auto; 12 - padding: 20px; 13 - line-height: 1.6; 14 - } 15 - h2 { 16 - margin-top: 40px; 17 - border-bottom: 1px solid #eee; 18 - padding-bottom: 8px; 19 - } 20 - .embed-container { 21 - height: 500px; 22 - margin-bottom: 40px; 23 - } 24 - </style> 25 - </head> 26 - <body> 27 - <h1>RSC Explorer Embeds</h1> 28 - <p> 29 - Multiple embedded RSC explorers demonstrating different React Server 30 - Components patterns. 31 - </p> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>RSC Explorer Embeds</title> 7 + <style> 8 + body { 9 + font-family: system-ui, sans-serif; 10 + max-width: 900px; 11 + margin: 0 auto; 12 + padding: 20px; 13 + line-height: 1.6; 14 + } 15 + h2 { 16 + margin-top: 40px; 17 + border-bottom: 1px solid #eee; 18 + padding-bottom: 8px; 19 + } 20 + .embed-container { 21 + height: 500px; 22 + margin-bottom: 40px; 23 + } 24 + </style> 25 + </head> 26 + <body> 27 + <h1>RSC Explorer Embeds</h1> 28 + <p>Multiple embedded RSC explorers demonstrating different React Server Components patterns.</p> 32 29 33 - <h2>Hello World</h2> 34 - <p>The simplest possible server component.</p> 35 - <div id="hello" class="embed-container"></div> 30 + <h2>Hello World</h2> 31 + <p>The simplest possible server component.</p> 32 + <div id="hello" class="embed-container"></div> 36 33 37 - <h2>Counter</h2> 38 - <p>A client component with state, rendered from a server component.</p> 39 - <div id="counter" class="embed-container"></div> 34 + <h2>Counter</h2> 35 + <p>A client component with state, rendered from a server component.</p> 36 + <div id="counter" class="embed-container"></div> 40 37 41 - <h2>Async Component</h2> 42 - <p> 43 - Server components can be async and use Suspense for loading states. 44 - </p> 45 - <div id="async" class="embed-container"></div> 38 + <h2>Async Component</h2> 39 + <p>Server components can be async and use Suspense for loading states.</p> 40 + <div id="async" class="embed-container"></div> 46 41 47 - <h2>Form Action</h2> 48 - <p>Server actions handle form submissions with useActionState.</p> 49 - <div id="form" class="embed-container"></div> 50 - </body> 42 + <h2>Form Action</h2> 43 + <p>Server actions handle form submissions with useActionState.</p> 44 + <div id="form" class="embed-container"></div> 45 + </body> 51 46 52 - <script type="module"> 53 - import { mount } from "http://localhost:3333/embed.js"; 47 + <script type="module"> 48 + import { mount } from "http://localhost:3333/embed.js"; 54 49 55 - mount("#hello", { 56 - server: `export default function App() { 50 + mount("#hello", { 51 + server: `export default function App() { 57 52 return <h1>Hello World</h1> 58 53 }`, 59 - client: `'use client'`, 60 - }); 54 + client: `'use client'`, 55 + }); 61 56 62 - mount("#counter", { 63 - server: `import { Counter } from './client' 57 + mount("#counter", { 58 + server: `import { Counter } from './client' 64 59 65 60 export default function App() { 66 61 return ( ··· 70 65 </div> 71 66 ) 72 67 }`, 73 - client: `'use client' 68 + client: `'use client' 74 69 75 70 import { useState } from 'react' 76 71 ··· 87 82 </div> 88 83 ) 89 84 }`, 90 - }); 85 + }); 91 86 92 - mount("#async", { 93 - server: `import { Suspense } from 'react' 87 + mount("#async", { 88 + server: `import { Suspense } from 'react' 94 89 95 90 export default function App() { 96 91 return ( ··· 107 102 await new Promise(r => setTimeout(r, 500)) 108 103 return <p>Data loaded!</p> 109 104 }`, 110 - client: `'use client'`, 111 - }); 105 + client: `'use client'`, 106 + }); 112 107 113 - mount("#form", { 114 - server: `import { Form } from './client' 108 + mount("#form", { 109 + server: `import { Form } from './client' 115 110 116 111 export default function App() { 117 112 return ( ··· 129 124 if (!name) return { message: null, error: 'Please enter a name' } 130 125 return { message: \`Hello, \${name}!\`, error: null } 131 126 }`, 132 - client: `'use client' 127 + client: `'use client' 133 128 134 129 import { useActionState } from 'react' 135 130 ··· 156 151 </form> 157 152 ) 158 153 }`, 159 - }); 160 - </script> 154 + }); 155 + </script> 161 156 162 - <div id="rsc-explorer" style="height: 500px"></div> 163 - <script type="module"> 164 - import { mount } from 'https://rscexplorer.dev/embed.js'; 157 + <div id="rsc-explorer" style="height: 500px"></div> 158 + <script type="module"> 159 + import { mount } from 'https://rscexplorer.dev/embed.js'; 165 160 166 - mount('#rsc-explorer', { 167 - server: ` 168 - import { Suspense } from 'react' 169 - import { Timer, Router } from './client' 161 + mount('#rsc-explorer', { 162 + server: ` 163 + import { Suspense } from 'react' 164 + import { Timer, Router } from './client' 170 165 171 - export default function App() { 172 - return ( 173 - <div> 174 - <h1>Router Refresh!!!</h1> 175 - <p style={{ marginBottom: 12, color: '#666' }}> 176 - Client state persists across server navigations 177 - </p> 178 - <Suspense fallback={<p>Loading...</p>}> 179 - <Router initial={renderPage()} refreshAction={renderPage} /> 180 - </Suspense> 181 - </div> 182 - ) 183 - } 166 + export default function App() { 167 + return ( 168 + <div> 169 + <h1>Router Refresh!!!</h1> 170 + <p style={{ marginBottom: 12, color: '#666' }}> 171 + Client state persists across server navigations 172 + </p> 173 + <Suspense fallback={<p>Loading...</p>}> 174 + <Router initial={renderPage()} refreshAction={renderPage} /> 175 + </Suspense> 176 + </div> 177 + ) 178 + } 184 179 185 - async function renderPage() { 186 - 'use server' 187 - return <ColorTimer /> 188 - } 180 + async function renderPage() { 181 + 'use server' 182 + return <ColorTimer /> 183 + } 189 184 190 - async function ColorTimer() { 191 - await new Promise(r => setTimeout(r, 300)) 192 - const hue = Math.floor(Math.random() * 360) 193 - return <Timer color={`hsl(${hue}, 70%, 85%)`} /> 194 - } 195 - `, 196 - client: ` 197 - 'use client' 185 + async function ColorTimer() { 186 + await new Promise(r => setTimeout(r, 300)) 187 + const hue = Math.floor(Math.random() * 360) 188 + return <Timer color={`hsl(${hue}, 70%, 85%)`} /> 189 + } 190 + `, 191 + client: ` 192 + 'use client' 198 193 199 - import { useState, useEffect, useTransition, use } from 'react' 194 + import { useState, useEffect, useTransition, use } from 'react' 200 195 201 - export function Timer({ color }) { 202 - const [seconds, setSeconds] = useState(0) 196 + export function Timer({ color }) { 197 + const [seconds, setSeconds] = useState(0) 203 198 204 - useEffect(() => { 205 - const id = setInterval(() => setSeconds(s => s + 1), 1000) 206 - return () => clearInterval(id) 207 - }, []) 199 + useEffect(() => { 200 + const id = setInterval(() => setSeconds(s => s + 1), 1000) 201 + return () => clearInterval(id) 202 + }, []) 208 203 209 - return ( 210 - <div style={{ 211 - background: color, 212 - padding: 24, 213 - borderRadius: 8, 214 - textAlign: 'center' 215 - }}> 216 - <p style={{ fontFamily: 'monospace', fontSize: 32, margin: 0 }}>{seconds}s</p> 217 - </div> 218 - ) 219 - } 204 + return ( 205 + <div style={{ 206 + background: color, 207 + padding: 24, 208 + borderRadius: 8, 209 + textAlign: 'center' 210 + }}> 211 + <p style={{ fontFamily: 'monospace', fontSize: 32, margin: 0 }}>{seconds}s</p> 212 + </div> 213 + ) 214 + } 220 215 221 - export function Router({ initial, refreshAction }) { 222 - const [contentPromise, setContentPromise] = useState(initial) 223 - const [isPending, startTransition] = useTransition() 224 - const content = use(contentPromise) 216 + export function Router({ initial, refreshAction }) { 217 + const [contentPromise, setContentPromise] = useState(initial) 218 + const [isPending, startTransition] = useTransition() 219 + const content = use(contentPromise) 225 220 226 - const refresh = () => { 227 - startTransition(() => { 228 - setContentPromise(refreshAction()) 229 - }) 230 - } 221 + const refresh = () => { 222 + startTransition(() => { 223 + setContentPromise(refreshAction()) 224 + }) 225 + } 231 226 232 - return ( 233 - <div style={{ opacity: isPending ? 0.6 : 1, transition: 'opacity 0.2s' }}> 234 - {content} 235 - <button onClick={refresh} disabled={isPending} style={{ marginTop: 12 }}> 236 - {isPending ? 'Refreshing...' : 'Refresh'} 237 - </button> 238 - </div> 239 - ) 240 - } 241 - ` 242 - }); 243 - </script> 227 + return ( 228 + <div style={{ opacity: isPending ? 0.6 : 1, transition: 'opacity 0.2s' }}> 229 + {content} 230 + <button onClick={refresh} disabled={isPending} style={{ marginTop: 12 }}> 231 + {isPending ? 'Refreshing...' : 'Refresh'} 232 + </button> 233 + </div> 234 + ) 235 + } 236 + ` 237 + }); 238 + </script> 244 239 </html>
+5 -5
tests/async.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('async sample', async () => { 22 - await h.load('async'); 21 + test("async sample", async () => { 22 + await h.load("async"); 23 23 24 24 // First tree state - Suspense with Pending 25 25 expect(await h.stepAll()).toMatchInlineSnapshot(`
+30 -18
tests/bound.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('bound sample - renders bound actions with different greetings', async () => { 22 - await h.load('bound'); 21 + test("bound sample - renders bound actions with different greetings", async () => { 22 + await h.load("bound"); 23 23 24 24 // Step to end - should show 3 Greeter forms 25 25 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 31 31 <Greeter action={[Function: bound greet]} /> 32 32 </div>" 33 33 `); 34 - expect(await h.preview()).toMatchInlineSnapshot(`"Bound Actions Same action, different bound greetings: Greet Greet Greet"`); 34 + expect(await h.preview()).toMatchInlineSnapshot( 35 + `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 36 + ); 35 37 }); 36 38 37 - test('bound sample - three concurrent actions', async () => { 38 - await h.load('bound'); 39 + test("bound sample - three concurrent actions", async () => { 40 + await h.load("bound"); 39 41 40 42 // Step to end to render UI 41 43 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 47 49 <Greeter action={[Function: bound greet]} /> 48 50 </div>" 49 51 `); 50 - expect(await h.preview()).toMatchInlineSnapshot(`"Bound Actions Same action, different bound greetings: Greet Greet Greet"`); 52 + expect(await h.preview()).toMatchInlineSnapshot( 53 + `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 54 + ); 51 55 52 56 // Fill all three inputs and submit all three forms 53 - const inputs = h.frame().locator('.preview-container input'); 54 - const buttons = h.frame().locator('.preview-container button'); 57 + const inputs = h.frame().locator(".preview-container input"); 58 + const buttons = h.frame().locator(".preview-container button"); 55 59 56 - await inputs.nth(0).fill('Alice'); 57 - await inputs.nth(1).fill('Bob'); 58 - await inputs.nth(2).fill('Charlie'); 60 + await inputs.nth(0).fill("Alice"); 61 + await inputs.nth(1).fill("Bob"); 62 + await inputs.nth(2).fill("Charlie"); 59 63 60 64 await buttons.nth(0).click(); 61 65 await buttons.nth(1).click(); ··· 63 67 64 68 // First action pending 65 69 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 66 - expect(await h.preview()).toMatchInlineSnapshot(`"Bound Actions Same action, different bound greetings: Greet Greet Greet"`); 70 + expect(await h.preview()).toMatchInlineSnapshot( 71 + `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 72 + ); 67 73 68 74 // First action resolves - Hello greeting (second still pending) 69 75 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 70 - expect(await h.preview()).toMatchInlineSnapshot(`"Bound Actions Same action, different bound greetings: GreetHello, Alice! Greet Greet"`); 76 + expect(await h.preview()).toMatchInlineSnapshot( 77 + `"Bound Actions Same action, different bound greetings: GreetHello, Alice! Greet Greet"`, 78 + ); 71 79 72 80 // Second action resolves - Howdy greeting (third still pending) 73 81 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 74 - expect(await h.preview()).toMatchInlineSnapshot(`"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! Greet"`); 82 + expect(await h.preview()).toMatchInlineSnapshot( 83 + `"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! Greet"`, 84 + ); 75 85 76 86 // Third action resolves - Hey greeting 77 87 expect(await h.stepAll()).toMatchInlineSnapshot(`""Hey, Charlie!""`); 78 - expect(await h.preview()).toMatchInlineSnapshot(`"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! GreetHey, Charlie!"`); 88 + expect(await h.preview()).toMatchInlineSnapshot( 89 + `"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! GreetHey, Charlie!"`, 90 + ); 79 91 });
+8 -6
tests/clientref.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('clientref sample - renders client module exports passed as props', async () => { 22 - await h.load('clientref'); 21 + test("clientref sample - renders client module exports passed as props", async () => { 22 + await h.load("clientref"); 23 23 24 24 // Check flight rows include client references for themes 25 25 const rows = await h.getRows(); 26 - expect(rows.some(r => r.text.includes('darkTheme') || r.text.includes('lightTheme'))).toBe(true); 26 + expect(rows.some((r) => r.text.includes("darkTheme") || r.text.includes("lightTheme"))).toBe( 27 + true, 28 + ); 27 29 28 30 // Step to end - should show both themed boxes 29 31 expect(await h.stepAll()).toMatchInlineSnapshot(`
+7 -7
tests/counter.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('counter sample', async () => { 22 - await h.load('counter'); 21 + test("counter sample", async () => { 22 + await h.load("counter"); 23 23 24 24 expect(await h.stepAll()).toMatchInlineSnapshot(` 25 25 "<div> ··· 30 30 expect(await h.preview()).toMatchInlineSnapshot(`"Counter Count: 0 − +"`); 31 31 32 32 // Client interactivity works 33 - await h.frame().locator('.preview-container button').last().click(); 34 - expect(await h.preview()).toContain('Count: 1'); 33 + await h.frame().locator(".preview-container button").last().click(); 34 + expect(await h.preview()).toContain("Count: 1"); 35 35 });
+8 -8
tests/errors.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('errors sample - error boundary catches thrown error', async () => { 22 - await h.load('errors'); 21 + test("errors sample - error boundary catches thrown error", async () => { 22 + await h.load("errors"); 23 23 24 24 // Step to end - should show error fallback (fetchUser throws) 25 25 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 44 44 </ErrorBoundary> 45 45 </div>" 46 46 `); 47 - expect(await h.preview()).toContain('Error Handling'); 47 + expect(await h.preview()).toContain("Error Handling"); 48 48 49 49 // After async resolves with error, error boundary catches it 50 50 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 69 69 </ErrorBoundary> 70 70 </div>" 71 71 `); 72 - expect(await h.preview()).toContain('Failed to load user'); 73 - expect(await h.preview()).toContain('Please try again later'); 72 + expect(await h.preview()).toContain("Failed to load user"); 73 + expect(await h.preview()).toContain("Please try again later"); 74 74 });
+7 -7
tests/form.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('form sample', async () => { 22 - await h.load('form'); 21 + test("form sample", async () => { 22 + await h.load("form"); 23 23 24 24 expect(await h.stepAll()).toMatchInlineSnapshot(` 25 25 "<div> ··· 30 30 expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Greet"`); 31 31 32 32 // Submit form 33 - await h.frame().locator('.preview-container input[name="name"]').fill('World'); 34 - await h.frame().locator('.preview-container button').click(); 33 + await h.frame().locator('.preview-container input[name="name"]').fill("World"); 34 + await h.frame().locator(".preview-container button").click(); 35 35 expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Sending..."`); 36 36 37 37 // Action response
+10 -10
tests/globalSetup.js
··· 1 - import { spawn, execSync } from 'child_process'; 1 + import { spawn, execSync } from "child_process"; 2 2 3 3 let devServer; 4 4 5 5 export async function setup() { 6 6 await new Promise((resolve, reject) => { 7 - devServer = spawn('npm', ['run', 'dev', '--', '--port', '5599'], { 8 - stdio: ['ignore', 'pipe', 'pipe'], 7 + devServer = spawn("npm", ["run", "dev", "--", "--port", "5599"], { 8 + stdio: ["ignore", "pipe", "pipe"], 9 9 detached: false, 10 10 }); 11 11 12 - devServer.stdout.on('data', (data) => { 13 - if (data.toString().includes('Local:')) { 12 + devServer.stdout.on("data", (data) => { 13 + if (data.toString().includes("Local:")) { 14 14 resolve(); 15 15 } 16 16 }); 17 17 18 - devServer.stderr.on('data', (data) => { 18 + devServer.stderr.on("data", (data) => { 19 19 console.error(data.toString()); 20 20 }); 21 21 22 - devServer.on('error', reject); 22 + devServer.on("error", reject); 23 23 24 24 // Timeout after 30s 25 - setTimeout(() => reject(new Error('Dev server failed to start')), 30000); 25 + setTimeout(() => reject(new Error("Dev server failed to start")), 30000); 26 26 }); 27 27 } 28 28 29 29 export async function teardown() { 30 30 if (devServer) { 31 - devServer.kill('SIGTERM'); 31 + devServer.kill("SIGTERM"); 32 32 // Also kill any process on the port 33 33 try { 34 - execSync('lsof -ti:5599 | xargs kill -9 2>/dev/null || true'); 34 + execSync("lsof -ti:5599 | xargs kill -9 2>/dev/null || true"); 35 35 } catch {} 36 36 } 37 37 }
+5 -5
tests/hello.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('hello sample', async () => { 22 - await h.load('hello'); 21 + test("hello sample", async () => { 22 + await h.load("hello"); 23 23 24 24 expect(await h.stepAll()).toMatchInlineSnapshot(`"<h1>Hello World</h1>"`); 25 25 expect(await h.preview()).toMatchInlineSnapshot(`"Hello World"`);
+72 -43
tests/helpers.js
··· 1 - import { expect } from 'vitest'; 1 + import { expect } from "vitest"; 2 2 3 3 let prevRowTexts = []; 4 4 let prevStatuses = []; 5 - let prevPreview = ''; 5 + let prevPreview = ""; 6 6 let previewAsserted = true; 7 7 let pageRef = null; 8 8 let frameRef = null; ··· 13 13 async function load(sample) { 14 14 await page.goto(`http://localhost:5599/?s=${sample}`); 15 15 // Wait for iframe to load and get frame reference 16 - const iframe = page.frameLocator('iframe'); 16 + const iframe = page.frameLocator("iframe"); 17 17 frameRef = iframe; 18 18 // Wait for content inside iframe 19 - await iframe.locator('.log-entry').first().waitFor({ timeout: 10000 }); 19 + await iframe.locator(".log-entry").first().waitFor({ timeout: 10000 }); 20 20 await page.waitForTimeout(100); 21 21 prevRowTexts = []; 22 22 prevStatuses = []; ··· 25 25 } 26 26 27 27 async function getPreviewText() { 28 - return (await frameRef.locator('.preview-container').innerText()).trim().replace(/\s+/g, ' '); 28 + return (await frameRef.locator(".preview-container").innerText()).trim().replace(/\s+/g, " "); 29 29 } 30 30 31 31 async function doStep() { 32 - const btn = frameRef.locator('.control-btn').nth(2); 32 + const btn = frameRef.locator(".control-btn").nth(2); 33 33 if (await btn.isDisabled()) return null; 34 34 await btn.click(); 35 35 await pageRef.waitForTimeout(50); 36 36 37 37 const rows = await getRows(); 38 - const texts = rows.map(r => r.text); 39 - const statuses = rows.map(r => r.status); 38 + const texts = rows.map((r) => r.text); 39 + const statuses = rows.map((r) => r.status); 40 40 41 41 for (let i = 0; i < Math.min(prevRowTexts.length, texts.length); i++) { 42 42 expect(texts[i], `row ${i} content changed`).toBe(prevRowTexts[i]); ··· 45 45 for (let i = 0; i < Math.min(prevStatuses.length, statuses.length); i++) { 46 46 const prev = prevStatuses[i]; 47 47 const curr = statuses[i]; 48 - if (prev === 'done') { 49 - expect(curr, `row ${i}: done should stay done`).toBe('done'); 50 - } else if (prev === 'next') { 51 - expect(curr, `row ${i}: next should become done`).toBe('done'); 48 + if (prev === "done") { 49 + expect(curr, `row ${i}: done should stay done`).toBe("done"); 50 + } else if (prev === "next") { 51 + expect(curr, `row ${i}: next should become done`).toBe("done"); 52 52 } 53 53 } 54 54 55 - const prevNextIdx = prevStatuses.indexOf('next'); 55 + const prevNextIdx = prevStatuses.indexOf("next"); 56 56 if (prevNextIdx !== -1 && prevNextIdx < statuses.length) { 57 - expect(statuses[prevNextIdx], `previous "next" row should be "done"`).toBe('done'); 57 + expect(statuses[prevNextIdx], `previous "next" row should be "done"`).toBe("done"); 58 58 } 59 59 60 60 prevRowTexts = texts; ··· 67 67 // Check for unasserted preview changes before stepping 68 68 const currentPreview = await getPreviewText(); 69 69 if (currentPreview !== prevPreview && !previewAsserted) { 70 - expect.fail(`preview changed without assertion. Was: "${prevPreview}" Now: "${currentPreview}"`); 70 + expect.fail( 71 + `preview changed without assertion. Was: "${prevPreview}" Now: "${currentPreview}"`, 72 + ); 71 73 } 72 74 previewAsserted = false; 73 75 return await doStep(); 74 76 } 75 77 76 78 async function waitForStepButton() { 77 - const btn = frameRef.locator('.control-btn').nth(2); 79 + const btn = frameRef.locator(".control-btn").nth(2); 78 80 // Wait for button to be enabled 79 - await expect.poll(async () => { 80 - return !(await btn.isDisabled()); 81 - }, { timeout: 10000 }).toBe(true); 81 + await expect 82 + .poll( 83 + async () => { 84 + return !(await btn.isDisabled()); 85 + }, 86 + { timeout: 10000 }, 87 + ) 88 + .toBe(true); 82 89 await pageRef.waitForTimeout(50); 83 90 } 84 91 ··· 86 93 // Check for unasserted preview changes before stepping 87 94 const currentPreview = await getPreviewText(); 88 95 if (currentPreview !== prevPreview && !previewAsserted) { 89 - expect.fail(`preview changed without assertion. Was: "${prevPreview}" Now: "${currentPreview}"`); 96 + expect.fail( 97 + `preview changed without assertion. Was: "${prevPreview}" Now: "${currentPreview}"`, 98 + ); 90 99 } 91 100 92 101 // Wait for steps to be available ··· 131 140 } 132 141 133 142 async function stepInfo() { 134 - return (await frameRef.locator('.step-info').innerText()).trim(); 143 + return (await frameRef.locator(".step-info").innerText()).trim(); 135 144 } 136 145 137 146 async function getRows() { 138 - return frameRef.locator('.flight-line').evaluateAll(els => 139 - els.map(el => ({ 140 - text: el.textContent, 141 - status: el.classList.contains('line-done') ? 'done' 142 - : el.classList.contains('line-next') ? 'next' 143 - : 'pending' 144 - })).filter(({ text }) => 145 - !text.startsWith(':N') && 146 - !/^\w+:D/.test(text) && 147 - !/^\w+:\{.*"name"/.test(text) && 148 - !/^\w+:\[\[/.test(text) 149 - ) 147 + return frameRef.locator(".flight-line").evaluateAll((els) => 148 + els 149 + .map((el) => ({ 150 + text: el.textContent, 151 + status: el.classList.contains("line-done") 152 + ? "done" 153 + : el.classList.contains("line-next") 154 + ? "next" 155 + : "pending", 156 + })) 157 + .filter( 158 + ({ text }) => 159 + !text.startsWith(":N") && 160 + !/^\w+:D/.test(text) && 161 + !/^\w+:\{.*"name"/.test(text) && 162 + !/^\w+:\[\[/.test(text), 163 + ), 150 164 ); 151 165 } 152 166 153 167 async function tree() { 154 168 // Find the log entry containing the "next" line, or the last done entry 155 - const treeText = await frameRef.locator('.log-entry').evaluateAll(entries => { 156 - const nextLine = document.querySelector('.line-next'); 169 + const treeText = await frameRef.locator(".log-entry").evaluateAll((entries) => { 170 + const nextLine = document.querySelector(".line-next"); 157 171 if (nextLine) { 158 - const entry = nextLine.closest('.log-entry'); 159 - const tree = entry?.querySelector('.log-entry-tree'); 172 + const entry = nextLine.closest(".log-entry"); 173 + const tree = entry?.querySelector(".log-entry-tree"); 160 174 return tree?.innerText?.trim() || null; 161 175 } 162 176 // No next line - get the last entry's tree 163 177 if (entries.length === 0) return null; 164 178 const lastEntry = entries[entries.length - 1]; 165 - const tree = lastEntry.querySelector('.log-entry-tree'); 179 + const tree = lastEntry.querySelector(".log-entry-tree"); 166 180 return tree?.innerText?.trim() || null; 167 181 }); 168 182 return treeText; ··· 174 188 175 189 // Consume remaining steps, but fail if tree or preview changes 176 190 while (true) { 177 - const btn = frameRef.locator('.control-btn').nth(2); 191 + const btn = frameRef.locator(".control-btn").nth(2); 178 192 if (await btn.isDisabled()) break; 179 193 180 194 await btn.click(); ··· 184 198 const currentPreview = await getPreviewText(); 185 199 186 200 if (currentTree !== initialTree) { 187 - expect.fail(`Unasserted tree change at end of test.\nWas: ${initialTree}\nNow: ${currentTree}`); 201 + expect.fail( 202 + `Unasserted tree change at end of test.\nWas: ${initialTree}\nNow: ${currentTree}`, 203 + ); 188 204 } 189 205 if (currentPreview !== initialPreview) { 190 - expect.fail(`Unasserted preview change at end of test.\nWas: "${initialPreview}"\nNow: "${currentPreview}"`); 206 + expect.fail( 207 + `Unasserted preview change at end of test.\nWas: "${initialPreview}"\nNow: "${currentPreview}"`, 208 + ); 191 209 } 192 210 } 193 211 } ··· 201 219 const interval = 50; 202 220 const start = Date.now(); 203 221 while (Date.now() - start < timeout) { 204 - const result = await frameRef.locator('body').evaluate(predicate); 222 + const result = await frameRef.locator("body").evaluate(predicate); 205 223 if (result) return; 206 224 await pageRef.waitForTimeout(interval); 207 225 } 208 226 throw new Error(`waitFor timed out after ${timeout}ms`); 209 227 } 210 228 211 - return { load, step, stepAll, stepInfo, getRows, preview, tree, checkNoRemainingSteps, frame, waitFor }; 229 + return { 230 + load, 231 + step, 232 + stepAll, 233 + stepInfo, 234 + getRows, 235 + preview, 236 + tree, 237 + checkNoRemainingSteps, 238 + frame, 239 + waitFor, 240 + }; 212 241 }
+5 -5
tests/kitchensink.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('kitchensink sample - renders all RSC protocol types', async () => { 22 - await h.load('kitchensink'); 21 + test("kitchensink sample - renders all RSC protocol types", async () => { 22 + await h.load("kitchensink"); 23 23 24 24 // Should have many rows for all the different types 25 25 const rows = await h.getRows();
+20 -12
tests/pagination.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('pagination sample', async () => { 22 - await h.load('pagination'); 21 + test("pagination sample", async () => { 22 + await h.load("pagination"); 23 23 24 24 // Initial render - Suspense with Pending first 25 25 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 79 79 expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Loading recipes..."`); 80 80 81 81 // First Load More 82 - await h.frame().locator('.preview-container button').click(); 83 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Loading..."`); 82 + await h.frame().locator(".preview-container button").click(); 83 + expect(await h.preview()).toMatchInlineSnapshot( 84 + `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Loading..."`, 85 + ); 84 86 85 87 // Action returns new items 86 88 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 121 123 hasMore: true 122 124 }" 123 125 `); 124 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Load More"`); 126 + expect(await h.preview()).toMatchInlineSnapshot( 127 + `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Load More"`, 128 + ); 125 129 126 130 // Second Load More 127 - await h.frame().locator('.preview-container button').click(); 128 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Loading..."`); 131 + await h.frame().locator(".preview-container button").click(); 132 + expect(await h.preview()).toMatchInlineSnapshot( 133 + `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Loading..."`, 134 + ); 129 135 130 136 // Final items, hasMore: false 131 137 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 166 172 hasMore: false 167 173 }" 168 174 `); 169 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Caesar Salad 15 min · Easy Mushroom Risotto 45 min · Hard"`); 175 + expect(await h.preview()).toMatchInlineSnapshot( 176 + `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Caesar Salad 15 min · Easy Mushroom Risotto 45 min · Hard"`, 177 + ); 170 178 171 179 // No more items - button should be gone 172 - expect(await h.frame().locator('.preview-container button').isVisible()).toBe(false); 180 + expect(await h.frame().locator(".preview-container button").isVisible()).toBe(false); 173 181 });
+10 -8
tests/refresh.spec.js
··· 1 - import { test, expect, beforeAll, afterAll, afterEach } from 'vitest'; 2 - import { chromium } from 'playwright'; 3 - import { createHelpers } from './helpers.js'; 1 + import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { chromium } from "playwright"; 3 + import { createHelpers } from "./helpers.js"; 4 4 5 5 let browser, page, h; 6 6 ··· 18 18 await h.checkNoRemainingSteps(); 19 19 }); 20 20 21 - test('refresh sample - renders router with async content', async () => { 22 - await h.load('refresh'); 21 + test("refresh sample - renders router with async content", async () => { 22 + await h.load("refresh"); 23 23 24 24 // Step to initial render (shows Pending) 25 25 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 33 33 </Suspense> 34 34 </div>" 35 35 `); 36 - expect(await h.preview()).toMatchInlineSnapshot(`"Router Refresh Client state persists across server navigations Loading..."`); 36 + expect(await h.preview()).toMatchInlineSnapshot( 37 + `"Router Refresh Client state persists across server navigations Loading..."`, 38 + ); 37 39 38 40 // Step to resolve async Timer content (color is random hsl value) 39 41 const tree = await h.stepAll(); ··· 42 44 43 45 // Wait for preview to render the timer (shows "0s" or similar) 44 46 await h.waitFor( 45 - () => /\d+s/.test(document.querySelector('.preview-container')?.textContent || ''), 46 - { timeout: 5000 } 47 + () => /\d+s/.test(document.querySelector(".preview-container")?.textContent || ""), 48 + { timeout: 5000 }, 47 49 ); 48 50 const preview = await h.preview(); 49 51 expect(preview).toMatch(/Router Refresh.*\d+s.*Refresh/);
+36 -35
vite.config.js
··· 1 - import { defineConfig } from 'vite'; 2 - import react from '@vitejs/plugin-react'; 3 - import { resolve, dirname } from 'path'; 4 - import { fileURLToPath } from 'url'; 1 + import { defineConfig } from "vite"; 2 + import react from "@vitejs/plugin-react"; 3 + import { resolve, dirname } from "path"; 4 + import { fileURLToPath } from "url"; 5 5 6 6 const __dirname = dirname(fileURLToPath(import.meta.url)); 7 7 8 8 function rolldownWorkerPlugin() { 9 - let mode = 'production'; 9 + let mode = "production"; 10 10 return { 11 - name: 'rolldown-worker', 12 - enforce: 'pre', 11 + name: "rolldown-worker", 12 + enforce: "pre", 13 13 configResolved(config) { 14 14 mode = config.mode; 15 15 }, 16 16 resolveId(id, importer) { 17 - if (id.includes('?rolldown-worker')) { 18 - return '\0worker:' + resolve(dirname(importer), id.replace('?rolldown-worker', '')); 17 + if (id.includes("?rolldown-worker")) { 18 + return "\0worker:" + resolve(dirname(importer), id.replace("?rolldown-worker", "")); 19 19 } 20 20 }, 21 21 load: { 22 22 filter: { 23 23 id: { 24 24 include: /^\0worker:/, 25 - } 25 + }, 26 26 }, 27 27 async handler(id) { 28 - const isProd = mode === 'production'; 29 - const { rolldown } = await import('rolldown'); 28 + const isProd = mode === "production"; 29 + const { rolldown } = await import("rolldown"); 30 30 // Use 'production' or 'development' condition to match React's export conditions 31 31 const conditions = isProd 32 - ? ['react-server', 'production', 'browser', 'import', 'default'] 33 - : ['react-server', 'development', 'browser', 'import', 'default']; 32 + ? ["react-server", "production", "browser", "import", "default"] 33 + : ["react-server", "development", "browser", "import", "default"]; 34 34 const bundle = await rolldown({ 35 - input: id.slice('\0worker:'.length), 36 - platform: 'browser', 35 + input: id.slice("\0worker:".length), 36 + platform: "browser", 37 37 resolve: { conditionNames: conditions }, 38 38 transform: { 39 39 define: { 40 - 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development'), 40 + "process.env.NODE_ENV": JSON.stringify(isProd ? "production" : "development"), 41 41 }, 42 42 }, 43 43 }); 44 - const { output } = await bundle.generate({ format: 'iife', minify: isProd }); 44 + const { output } = await bundle.generate({ format: "iife", minify: isProd }); 45 45 for (const dep of output[0].moduleIds) { 46 - if (dep.startsWith('/')) this.addWatchFile(dep); 46 + if (dep.startsWith("/")) this.addWatchFile(dep); 47 47 } 48 48 await bundle.close(); 49 49 return ` 50 50 export default URL.createObjectURL(new Blob([${JSON.stringify(output[0].code)}], { type: 'application/javascript' })); 51 51 if (import.meta.hot) import.meta.hot.accept(() => location.reload());`; 52 52 }, 53 - } 54 - } 53 + }, 54 + }; 55 55 } 56 56 57 57 function serveEmbedPlugin() { 58 58 return { 59 - name: 'serve-embed', 59 + name: "serve-embed", 60 60 configureServer(server) { 61 61 server.middlewares.use((req, res, next) => { 62 - if (req.url === '/embed.js') { 63 - req.url = '/src/embed.js'; 62 + if (req.url === "/embed.js") { 63 + req.url = "/src/embed.js"; 64 64 } 65 65 next(); 66 66 }); ··· 72 72 plugins: [react(), rolldownWorkerPlugin(), serveEmbedPlugin()], 73 73 server: { port: 3333 }, 74 74 define: { 75 - 'process.env.NODE_ENV': JSON.stringify(mode === 'development' ? 'development' : 'production'), 75 + "process.env.NODE_ENV": JSON.stringify(mode === "development" ? "development" : "production"), 76 76 }, 77 77 resolve: { 78 - conditions: mode === 'development' 79 - ? ['development', 'browser', 'import', 'default'] 80 - : ['production', 'browser', 'import', 'default'], 78 + conditions: 79 + mode === "development" 80 + ? ["development", "browser", "import", "default"] 81 + : ["production", "browser", "import", "default"], 81 82 }, 82 83 build: { 83 84 rolldownOptions: { 84 85 input: { 85 - main: resolve(__dirname, 'index.html'), 86 - embed: resolve(__dirname, 'embed.html'), 87 - 'embed-js': resolve(__dirname, 'src/embed.js'), 86 + main: resolve(__dirname, "index.html"), 87 + embed: resolve(__dirname, "embed.html"), 88 + "embed-js": resolve(__dirname, "src/embed.js"), 88 89 }, 89 90 output: { 90 91 entryFileNames: (chunkInfo) => { 91 - if (chunkInfo.name === 'embed-js') { 92 - return 'embed.js'; 92 + if (chunkInfo.name === "embed-js") { 93 + return "embed.js"; 93 94 } 94 - return 'assets/[name]-[hash].js'; 95 + return "assets/[name]-[hash].js"; 95 96 }, 96 97 }, 97 - preserveEntrySignatures: 'exports-only', 98 + preserveEntrySignatures: "exports-only", 98 99 }, 99 100 }, 100 101 }));
+2 -2
vitest.config.js
··· 1 - import { defineConfig } from 'vitest/config'; 1 + import { defineConfig } from "vitest/config"; 2 2 3 3 export default defineConfig({ 4 4 test: { 5 5 testTimeout: 15000, 6 6 fileParallelism: true, 7 - globalSetup: './tests/globalSetup.js', 7 + globalSetup: "./tests/globalSetup.js", 8 8 }, 9 9 });