feat: integrate Mock Service Worker (MSW) for API mocking and testing
Currently just used for demo page
- Added MSW as a dependency in package.json.
- Configured MSW in the demo HTML to start the worker and handle API requests.
- Created mockServiceWorker.js to manage service worker lifecycle and request handling.
- Implemented browser.ts to set up the MSW worker with defined request handlers.
- Developed handlers.ts to simulate API responses and manage application state for testing.
diff --git a/webui/.prettierignore b/webui/.prettierignore
index 77f3f9f..410552c 100644
--- a/webui/.prettierignore
+++ b/webui/.prettierignore
@@ -1,2 +1,3 @@
*.min.*
-src/types.ts
\ No newline at end of file
+src/types.ts
+mockServiceWorker.js
diff --git a/webui/esbuild.go b/webui/esbuild.go
index b86b7d7..8f1fcfe 100644
--- a/webui/esbuild.go
+++ b/webui/esbuild.go
@@ -176,6 +176,9 @@
}
return nil
}
+ if strings.HasSuffix(path, "mockServiceWorker.js") {
+ return nil
+ }
if strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".js") {
b, err := embedded.ReadFile(path)
if err != nil {
diff --git a/webui/mockServiceWorker.js b/webui/mockServiceWorker.js
new file mode 100644
index 0000000..8b841ba
--- /dev/null
+++ b/webui/mockServiceWorker.js
@@ -0,0 +1,307 @@
+/* eslint-disable */
+/* tslint:disable */
+
+/**
+ * Mock Service Worker.
+ * @see https://github.com/mswjs/msw
+ * - Please do NOT modify this file.
+ * - Please do NOT serve this file on production.
+ */
+
+const PACKAGE_VERSION = '2.7.5'
+const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
+const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
+const activeClientIds = new Set()
+
+self.addEventListener('install', function () {
+ self.skipWaiting()
+})
+
+self.addEventListener('activate', function (event) {
+ event.waitUntil(self.clients.claim())
+})
+
+self.addEventListener('message', async function (event) {
+ const clientId = event.source.id
+
+ if (!clientId || !self.clients) {
+ return
+ }
+
+ const client = await self.clients.get(clientId)
+
+ if (!client) {
+ return
+ }
+
+ const allClients = await self.clients.matchAll({
+ type: 'window',
+ })
+
+ switch (event.data) {
+ case 'KEEPALIVE_REQUEST': {
+ sendToClient(client, {
+ type: 'KEEPALIVE_RESPONSE',
+ })
+ break
+ }
+
+ case 'INTEGRITY_CHECK_REQUEST': {
+ sendToClient(client, {
+ type: 'INTEGRITY_CHECK_RESPONSE',
+ payload: {
+ packageVersion: PACKAGE_VERSION,
+ checksum: INTEGRITY_CHECKSUM,
+ },
+ })
+ break
+ }
+
+ case 'MOCK_ACTIVATE': {
+ activeClientIds.add(clientId)
+
+ sendToClient(client, {
+ type: 'MOCKING_ENABLED',
+ payload: {
+ client: {
+ id: client.id,
+ frameType: client.frameType,
+ },
+ },
+ })
+ break
+ }
+
+ case 'MOCK_DEACTIVATE': {
+ activeClientIds.delete(clientId)
+ break
+ }
+
+ case 'CLIENT_CLOSED': {
+ activeClientIds.delete(clientId)
+
+ const remainingClients = allClients.filter((client) => {
+ return client.id !== clientId
+ })
+
+ // Unregister itself when there are no more clients
+ if (remainingClients.length === 0) {
+ self.registration.unregister()
+ }
+
+ break
+ }
+ }
+})
+
+self.addEventListener('fetch', function (event) {
+ const { request } = event
+
+ // Bypass navigation requests.
+ if (request.mode === 'navigate') {
+ return
+ }
+
+ // Opening the DevTools triggers the "only-if-cached" request
+ // that cannot be handled by the worker. Bypass such requests.
+ if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
+ return
+ }
+
+ // Bypass all requests when there are no active clients.
+ // Prevents the self-unregistered worked from handling requests
+ // after it's been deleted (still remains active until the next reload).
+ if (activeClientIds.size === 0) {
+ return
+ }
+
+ // Generate unique request ID.
+ const requestId = crypto.randomUUID()
+ event.respondWith(handleRequest(event, requestId))
+})
+
+async function handleRequest(event, requestId) {
+ const client = await resolveMainClient(event)
+ const response = await getResponse(event, client, requestId)
+
+ // Send back the response clone for the "response:*" life-cycle events.
+ // Ensure MSW is active and ready to handle the message, otherwise
+ // this message will pend indefinitely.
+ if (client && activeClientIds.has(client.id)) {
+ ;(async function () {
+ const responseClone = response.clone()
+
+ sendToClient(
+ client,
+ {
+ type: 'RESPONSE',
+ payload: {
+ requestId,
+ isMockedResponse: IS_MOCKED_RESPONSE in response,
+ type: responseClone.type,
+ status: responseClone.status,
+ statusText: responseClone.statusText,
+ body: responseClone.body,
+ headers: Object.fromEntries(responseClone.headers.entries()),
+ },
+ },
+ [responseClone.body],
+ )
+ })()
+ }
+
+ return response
+}
+
+// Resolve the main client for the given event.
+// Client that issues a request doesn't necessarily equal the client
+// that registered the worker. It's with the latter the worker should
+// communicate with during the response resolving phase.
+async function resolveMainClient(event) {
+ const client = await self.clients.get(event.clientId)
+
+ if (activeClientIds.has(event.clientId)) {
+ return client
+ }
+
+ if (client?.frameType === 'top-level') {
+ return client
+ }
+
+ const allClients = await self.clients.matchAll({
+ type: 'window',
+ })
+
+ return allClients
+ .filter((client) => {
+ // Get only those clients that are currently visible.
+ return client.visibilityState === 'visible'
+ })
+ .find((client) => {
+ // Find the client ID that's recorded in the
+ // set of clients that have registered the worker.
+ return activeClientIds.has(client.id)
+ })
+}
+
+async function getResponse(event, client, requestId) {
+ const { request } = event
+
+ // Clone the request because it might've been already used
+ // (i.e. its body has been read and sent to the client).
+ const requestClone = request.clone()
+
+ function passthrough() {
+ // Cast the request headers to a new Headers instance
+ // so the headers can be manipulated with.
+ const headers = new Headers(requestClone.headers)
+
+ // Remove the "accept" header value that marked this request as passthrough.
+ // This prevents request alteration and also keeps it compliant with the
+ // user-defined CORS policies.
+ const acceptHeader = headers.get('accept')
+ if (acceptHeader) {
+ const values = acceptHeader.split(',').map((value) => value.trim())
+ const filteredValues = values.filter(
+ (value) => value !== 'msw/passthrough',
+ )
+
+ if (filteredValues.length > 0) {
+ headers.set('accept', filteredValues.join(', '))
+ } else {
+ headers.delete('accept')
+ }
+ }
+
+ return fetch(requestClone, { headers })
+ }
+
+ // Bypass mocking when the client is not active.
+ if (!client) {
+ return passthrough()
+ }
+
+ // Bypass initial page load requests (i.e. static assets).
+ // The absence of the immediate/parent client in the map of the active clients
+ // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
+ // and is not ready to handle requests.
+ if (!activeClientIds.has(client.id)) {
+ return passthrough()
+ }
+
+ // Notify the client that a request has been intercepted.
+ const requestBuffer = await request.arrayBuffer()
+ const clientMessage = await sendToClient(
+ client,
+ {
+ type: 'REQUEST',
+ payload: {
+ id: requestId,
+ url: request.url,
+ mode: request.mode,
+ method: request.method,
+ headers: Object.fromEntries(request.headers.entries()),
+ cache: request.cache,
+ credentials: request.credentials,
+ destination: request.destination,
+ integrity: request.integrity,
+ redirect: request.redirect,
+ referrer: request.referrer,
+ referrerPolicy: request.referrerPolicy,
+ body: requestBuffer,
+ keepalive: request.keepalive,
+ },
+ },
+ [requestBuffer],
+ )
+
+ switch (clientMessage.type) {
+ case 'MOCK_RESPONSE': {
+ return respondWithMock(clientMessage.data)
+ }
+
+ case 'PASSTHROUGH': {
+ return passthrough()
+ }
+ }
+
+ return passthrough()
+}
+
+function sendToClient(client, message, transferrables = []) {
+ return new Promise((resolve, reject) => {
+ const channel = new MessageChannel()
+
+ channel.port1.onmessage = (event) => {
+ if (event.data && event.data.error) {
+ return reject(event.data.error)
+ }
+
+ resolve(event.data)
+ }
+
+ client.postMessage(
+ message,
+ [channel.port2].concat(transferrables.filter(Boolean)),
+ )
+ })
+}
+
+async function respondWithMock(response) {
+ // Setting response status code to 0 is a no-op.
+ // However, when responding with a "Response.error()", the produced Response
+ // instance will have status code set to 0. Since it's not possible to create
+ // a Response instance with status code 0, handle that use-case separately.
+ if (response.status === 0) {
+ return Response.error()
+ }
+
+ const mockedResponse = new Response(response.body, response)
+
+ Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
+ value: true,
+ enumerable: true,
+ })
+
+ return mockedResponse
+}
diff --git a/webui/package-lock.json b/webui/package-lock.json
index 8abb565..920c4d9 100644
--- a/webui/package-lock.json
+++ b/webui/package-lock.json
@@ -28,6 +28,7 @@
"@web/test-runner-puppeteer": "^0.18.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.1",
+ "msw": "^2.7.5",
"prettier": "3.5.3",
"typescript": "^5.8.2",
"vite": "^6.3.2",
@@ -351,6 +352,43 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bundled-es-modules/cookie": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
+ "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==",
+ "dev": true,
+ "dependencies": {
+ "cookie": "^0.7.2"
+ }
+ },
+ "node_modules/@bundled-es-modules/statuses": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz",
+ "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==",
+ "dev": true,
+ "dependencies": {
+ "statuses": "^2.0.1"
+ }
+ },
+ "node_modules/@bundled-es-modules/statuses/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@bundled-es-modules/tough-cookie": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz",
+ "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==",
+ "dev": true,
+ "dependencies": {
+ "@types/tough-cookie": "^4.0.5",
+ "tough-cookie": "^4.1.4"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
@@ -782,6 +820,80 @@
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==",
"dev": true
},
+ "node_modules/@inquirer/confirm": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.9.tgz",
+ "integrity": "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==",
+ "dev": true,
+ "dependencies": {
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/core": {
+ "version": "10.1.10",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz",
+ "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==",
+ "dev": true,
+ "dependencies": {
+ "@inquirer/figures": "^1.0.11",
+ "@inquirer/type": "^3.0.6",
+ "ansi-escapes": "^4.3.2",
+ "cli-width": "^4.1.0",
+ "mute-stream": "^2.0.0",
+ "signal-exit": "^4.1.0",
+ "wrap-ansi": "^6.2.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/figures": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz",
+ "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/type": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz",
+ "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -850,6 +962,23 @@
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
+ "node_modules/@mswjs/interceptors": {
+ "version": "0.37.6",
+ "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.6.tgz",
+ "integrity": "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==",
+ "dev": true,
+ "dependencies": {
+ "@open-draft/deferred-promise": "^2.2.0",
+ "@open-draft/logger": "^0.3.0",
+ "@open-draft/until": "^2.0.0",
+ "is-node-process": "^1.2.0",
+ "outvariant": "^1.4.3",
+ "strict-event-emitter": "^0.5.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -888,6 +1017,28 @@
"node": ">= 8"
}
},
+ "node_modules/@open-draft/deferred-promise": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
+ "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
+ "dev": true
+ },
+ "node_modules/@open-draft/logger": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz",
+ "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==",
+ "dev": true,
+ "dependencies": {
+ "is-node-process": "^1.2.0",
+ "outvariant": "^1.4.0"
+ }
+ },
+ "node_modules/@open-draft/until": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz",
+ "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==",
+ "dev": true
+ },
"node_modules/@playwright/experimental-ct-core": {
"version": "1.51.1",
"resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.51.1.tgz",
@@ -1327,6 +1478,12 @@
"integrity": "sha512-ag0BfJLZf6CQz8VIuRIEYQ5Ggwk/82uvTQf27RcpyDNbY0Vw49LIPqAxk5tqYfrCs9xDaIMvl4aj7ZopnYL8bA==",
"dev": true
},
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "dev": true
+ },
"node_modules/@types/cookies": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz",
@@ -1521,6 +1678,18 @@
"@types/send": "*"
}
},
+ "node_modules/@types/statuses": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz",
+ "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==",
+ "dev": true
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true
+ },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -2101,6 +2270,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2511,21 +2702,6 @@
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
- "node_modules/chalk/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/chrome-launcher": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz",
@@ -2570,6 +2746,15 @@
"node": ">=8"
}
},
+ "node_modules/cli-width": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -2584,62 +2769,6 @@
"node": ">=12"
}
},
- "node_modules/cliui/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/cliui/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/cliui/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -2785,6 +2914,15 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/cookies": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
@@ -3335,6 +3473,11 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -3903,6 +4046,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graphql": {
+ "version": "16.10.0",
+ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz",
+ "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3952,6 +4104,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/headers-polyfill": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz",
+ "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==",
+ "dev": true
+ },
"node_modules/highlight.js": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
@@ -4309,6 +4467,12 @@
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
"dev": true
},
+ "node_modules/is-node-process": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz",
+ "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==",
+ "dev": true
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -4686,76 +4850,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/log-update/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/log-update/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/log-update/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
- },
- "node_modules/log-update/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/log-update/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/log-update/node_modules/wrap-ansi": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
- "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4899,6 +4993,71 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
+ "node_modules/msw": {
+ "version": "2.7.5",
+ "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.5.tgz",
+ "integrity": "sha512-00MyTlY3TJutBa5kiU+jWiz2z5pNJDYHn2TgPkGkh92kMmNH43RqvMXd8y/7HxNn8RjzUbvZWYZjcS36fdb6sw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@bundled-es-modules/cookie": "^2.0.1",
+ "@bundled-es-modules/statuses": "^1.0.1",
+ "@bundled-es-modules/tough-cookie": "^0.1.6",
+ "@inquirer/confirm": "^5.0.0",
+ "@mswjs/interceptors": "^0.37.0",
+ "@open-draft/deferred-promise": "^2.2.0",
+ "@open-draft/until": "^2.1.0",
+ "@types/cookie": "^0.6.0",
+ "@types/statuses": "^2.0.4",
+ "graphql": "^16.8.1",
+ "headers-polyfill": "^4.0.2",
+ "is-node-process": "^1.2.0",
+ "outvariant": "^1.4.3",
+ "path-to-regexp": "^6.3.0",
+ "picocolors": "^1.1.1",
+ "strict-event-emitter": "^0.5.1",
+ "type-fest": "^4.26.1",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "msw": "cli/index.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mswjs"
+ },
+ "peerDependencies": {
+ "typescript": ">= 4.8.x"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/msw/node_modules/type-fest": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
+ "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mute-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
+ "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
+ "dev": true,
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/nanocolors": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.13.tgz",
@@ -5076,6 +5235,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/outvariant": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
+ "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==",
+ "dev": true
+ },
"node_modules/p-event": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz",
@@ -5223,6 +5388,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -5411,6 +5582,18 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
"node_modules/pump": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
@@ -5597,6 +5780,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5679,6 +5868,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -6034,6 +6229,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@@ -6051,21 +6258,6 @@
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
- "node_modules/slice-ansi/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@@ -6150,6 +6342,36 @@
"bare-events": "^2.2.0"
}
},
+ "node_modules/strict-event-emitter": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz",
+ "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -6333,6 +6555,21 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "dev": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -6426,6 +6663,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -6466,6 +6712,16 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/urlpattern-polyfill": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz",
@@ -7121,6 +7377,20 @@
"node": ">=12.17"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -7191,47 +7461,6 @@
"node": ">=12"
}
},
- "node_modules/yargs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/yargs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
@@ -7251,6 +7480,18 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/yoctocolors-cjs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
+ "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
diff --git a/webui/package.json b/webui/package.json
index 262a4fc..e865167 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -39,10 +39,16 @@
"@web/test-runner-puppeteer": "^0.18.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.1",
+ "msw": "^2.7.5",
"prettier": "3.5.3",
"typescript": "^5.8.2",
"vite": "^6.3.2",
"vite-plugin-web-components-hmr": "^0.1.3"
},
- "keywords": []
+ "keywords": [],
+ "msw": {
+ "workerDirectory": [
+ ""
+ ]
+ }
}
diff --git a/webui/src/web-components/demo/mocks/browser.ts b/webui/src/web-components/demo/mocks/browser.ts
new file mode 100644
index 0000000..bcd82e4
--- /dev/null
+++ b/webui/src/web-components/demo/mocks/browser.ts
@@ -0,0 +1,4 @@
+import { setupWorker } from "msw/browser";
+import { handlers } from "./handlers";
+
+export const worker = setupWorker(...handlers);
diff --git a/webui/src/web-components/demo/mocks/fixtures/dummy.ts b/webui/src/web-components/demo/mocks/fixtures/dummy.ts
new file mode 100644
index 0000000..5dcf509
--- /dev/null
+++ b/webui/src/web-components/demo/mocks/fixtures/dummy.ts
@@ -0,0 +1,372 @@
+import { AgentMessage, State } from "../../../../types";
+
+export const initialMessages: AgentMessage[] = [
+ {
+ type: "user",
+ end_of_turn: false,
+ content:
+ "add a line to dummy.txt. Doesn't matter what it is and don't bother running tests it's just a dummy repo. Please commit afterward",
+ timestamp: "2025-04-24T10:32:00.679873+01:00",
+ conversation_id: "",
+ idx: 0,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content:
+ "I'll help you add a line to dummy.txt and commit the change. Let me first check if this file exists and create it if needed.",
+ tool_calls: [
+ {
+ name: "title",
+ input: '{"title":"Add a line to dummy.txt and commit the change"}',
+ tool_call_id: "toolu_01HSQ8xb9Ba8oP1gJPv1eD2s",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:03.988439+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 4,
+ cache_creation_input_tokens: 2228,
+ cache_read_input_tokens: 0,
+ output_tokens: 92,
+ cost_usd: 0.009747,
+ },
+ start_time: "2025-04-24T10:32:00.680125+01:00",
+ end_time: "2025-04-24T10:32:03.988434+01:00",
+ elapsed: 3308355125,
+ idx: 1,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "title",
+ input: '{"title":"Add a line to dummy.txt and commit the change"}',
+ tool_result: "Title set to: Add a line to dummy.txt and commit the change",
+ tool_call_id: "toolu_01HSQ8xb9Ba8oP1gJPv1eD2s",
+ timestamp: "2025-04-24T10:32:03.988567+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:03.988553+01:00",
+ end_time: "2025-04-24T10:32:03.988566+01:00",
+ elapsed: 13500,
+ idx: 2,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "",
+ tool_calls: [
+ {
+ name: "bash",
+ input: '{"command":"ls -la"}',
+ tool_call_id: "toolu_01RtkhetY8bqC6U1QBfVbukS",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:06.146332+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 116,
+ cache_read_input_tokens: 2228,
+ output_tokens: 54,
+ cost_usd: 0.0019314,
+ },
+ start_time: "2025-04-24T10:32:04.007226+01:00",
+ end_time: "2025-04-24T10:32:06.146325+01:00",
+ elapsed: 2139128375,
+ idx: 3,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "bash",
+ input: '{"command":"ls -la"}',
+ tool_result:
+ "total 1712\ndrwxr-xr-x 29 pokey staff 928 Apr 15 13:06 .\ndrwxr-xr-x 397 pokey staff 12704 Apr 23 09:53 ..\n-rw-r--r--@ 1 pokey staff 292 Jan 8 2024 .editorconfig\ndrwxr-xr-x 17 pokey staff 544 Apr 24 10:32 .git\ndrwxr-xr-x@ 3 pokey staff 96 Jan 8 2024 .github\n-rw-r--r--@ 1 pokey staff 777 Jan 8 2024 .gitignore\n-rw-r--r--@ 1 pokey staff 0 Apr 15 13:06 .PROMPT_DRAFT.md\n-rw------- 1 pokey staff 11811 Oct 4 2022 .tags\n-rw-r--r--@ 1 pokey staff 762 Jan 8 2024 .travis.yml\ndrwxr-xr-x 3 pokey staff 96 Apr 9 10:51 .vscode\n-rw-r--r--@ 1 pokey staff 156 Jan 8 2024 AUTHORS.rst\n-rw-r--r--@ 1 pokey staff 3249 Jan 8 2024 CONTRIBUTING.rst\ndrwxr-xr-x@ 12 pokey staff 384 Jan 8 2024 docs\n-rw-r--r-- 1 pokey staff 6644 Apr 23 15:31 dummy.txt\n-rw-r--r--@ 1 pokey staff 89 Jan 8 2024 HISTORY.rst\n-rw-r--r--@ 1 pokey staff 1070 Jan 8 2024 LICENSE\n-rw-r--r--@ 1 pokey staff 2303 Jan 8 2024 Makefile\n-rw-r--r--@ 1 pokey staff 264 Jan 8 2024 MANIFEST.in\n-rw-r--r-- 1 pokey staff 794413 Mar 7 2017 myVim.log\n-rw-r--r--@ 1 pokey staff 1043 Apr 8 12:21 README.rst\n-rw-r--r--@ 1 pokey staff 145 Jan 8 2024 requirements_dev.txt\n-rw-r--r--@ 1 pokey staff 342 Jan 8 2024 setup.cfg\n-rw-r--r--@ 1 pokey staff 1559 Jan 8 2024 setup.py\ndrwxr-xr-x 6 pokey staff 192 Jan 8 2024 spaghetti\ndrwxr-xr-x 9 pokey staff 288 Feb 26 2017 spaghetti.egg-info\ndrwxr-xr-x@ 5 pokey staff 160 Apr 4 19:07 tests\n-rw-r--r--@ 1 pokey staff 430 Jan 8 2024 tox.ini\n-rw-r--r--@ 1 pokey staff 3753 Jan 8 2024 travis_pypi_setup.py\ndrwxr-xr-x 2 pokey staff 64 Mar 11 2017 UltiSnips\n",
+ tool_call_id: "toolu_01RtkhetY8bqC6U1QBfVbukS",
+ timestamp: "2025-04-24T10:32:06.176176+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:06.146414+01:00",
+ end_time: "2025-04-24T10:32:06.176175+01:00",
+ elapsed: 29760750,
+ idx: 4,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content:
+ "I see that dummy.txt already exists. Let me add a new line to it.",
+ tool_calls: [
+ {
+ name: "patch",
+ input:
+ '{"path":"/Users/pokey/src/spaghetti/dummy.txt","patches":[{"operation":"append_eof","newText":"\\nAdding a new dummy line here - requested change"}]}',
+ tool_call_id: "toolu_01KjY2a2MWhfvwy6Pmci8SX2",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:09.724348+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 970,
+ cache_read_input_tokens: 2344,
+ output_tokens: 141,
+ cost_usd: 0.0064737,
+ },
+ start_time: "2025-04-24T10:32:06.195746+01:00",
+ end_time: "2025-04-24T10:32:09.724335+01:00",
+ elapsed: 3528638042,
+ idx: 5,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "patch",
+ input:
+ '{"path":"/Users/pokey/src/spaghetti/dummy.txt","patches":[{"operation":"append_eof","newText":"\\nAdding a new dummy line here - requested change"}]}',
+ tool_result: "- Applied all patches\n",
+ tool_call_id: "toolu_01KjY2a2MWhfvwy6Pmci8SX2",
+ timestamp: "2025-04-24T10:32:09.728267+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:09.725039+01:00",
+ end_time: "2025-04-24T10:32:09.728265+01:00",
+ elapsed: 3225875,
+ idx: 6,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "Now let me commit this change:",
+ tool_calls: [
+ {
+ name: "bash",
+ input:
+ '{"command":"GIT_AUTHOR_NAME=\\"Pokey Rule\\" GIT_AUTHOR_EMAIL=\\"755842+pokey@users.noreply.github.com\\" git add dummy.txt && git commit -m \\"Add a new line to dummy.txt\\" -m \\"Co-Authored-By: sketch\\" -m \\"Add a line to dummy.txt. Doesn\'t matter what it is and don\'t bother running tests it\'s just a dummy repo.\\""}',
+ tool_call_id: "toolu_01NQXuuU4JdUHn29S35oGLxF",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:13.306331+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 146,
+ cache_read_input_tokens: 3314,
+ output_tokens: 160,
+ cost_usd: 0.0039597,
+ },
+ start_time: "2025-04-24T10:32:09.76053+01:00",
+ end_time: "2025-04-24T10:32:13.306203+01:00",
+ elapsed: 3545723083,
+ idx: 7,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "bash",
+ input:
+ '{"command":"GIT_AUTHOR_NAME=\\"Pokey Rule\\" GIT_AUTHOR_EMAIL=\\"755842+pokey@users.noreply.github.com\\" git add dummy.txt && git commit -m \\"Add a new line to dummy.txt\\" -m \\"Co-Authored-By: sketch\\" -m \\"Add a line to dummy.txt. Doesn\'t matter what it is and don\'t bother running tests it\'s just a dummy repo.\\""}',
+ tool_result:
+ "branchless: processing 2 updates: branch main, ref HEAD\nbranchless: processed commit: 898178a Add a new line to dummy.txt\n[main 898178a] Add a new line to dummy.txt\n 1 file changed, 2 insertions(+), 1 deletion(-)\n",
+ tool_call_id: "toolu_01NQXuuU4JdUHn29S35oGLxF",
+ timestamp: "2025-04-24T10:32:13.652683+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:13.307345+01:00",
+ end_time: "2025-04-24T10:32:13.652678+01:00",
+ elapsed: 345337875,
+ idx: 8,
+ },
+ {
+ type: "commit",
+ end_of_turn: false,
+ content: "",
+ commits: [
+ {
+ hash: "898178a0c6681ce2b641cbe8994c22ed8bbb7dd7",
+ subject: "Add a new line to dummy.txt",
+ body: "Co-Authored-By: sketch\n\nAdd a line to dummy.txt. Doesn't matter what it is and don't bother running tests it's just a dummy repo.",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:13.690825+01:00",
+ conversation_id: "",
+ idx: 9,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "",
+ tool_calls: [
+ {
+ name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"not applicable","description":"The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_call_id: "toolu_01YMBDmBoCcE5tfZvbXAgWEa",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:19.07007+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 244,
+ cache_read_input_tokens: 3460,
+ output_tokens: 241,
+ cost_usd: 0.005586,
+ },
+ start_time: "2025-04-24T10:32:13.74753+01:00",
+ end_time: "2025-04-24T10:32:19.070058+01:00",
+ elapsed: 5322601792,
+ idx: 10,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"not applicable","description":"The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_result:
+ "codereview tool has not been run for commit 898178a0c6681ce2b641cbe8994c22ed8bbb7dd7",
+ tool_error: true,
+ tool_call_id: "toolu_01YMBDmBoCcE5tfZvbXAgWEa",
+ timestamp: "2025-04-24T10:32:19.121365+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:19.070249+01:00",
+ end_time: "2025-04-24T10:32:19.121358+01:00",
+ elapsed: 51109583,
+ idx: 11,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "Let me run a code review as required:",
+ tool_calls: [
+ {
+ name: "codereview",
+ input: "{}",
+ tool_call_id: "toolu_01Fgg5vaURskyxGcbwHoBu7z",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:21.918149+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 7,
+ cache_creation_input_tokens: 253,
+ cache_read_input_tokens: 3704,
+ output_tokens: 54,
+ cost_usd: 0.0028909499999999998,
+ },
+ start_time: "2025-04-24T10:32:19.14049+01:00",
+ end_time: "2025-04-24T10:32:21.918133+01:00",
+ elapsed: 2777681917,
+ idx: 12,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "codereview",
+ input: "{}",
+ tool_result: "OK",
+ tool_call_id: "toolu_01Fgg5vaURskyxGcbwHoBu7z",
+ timestamp: "2025-04-24T10:32:22.516658+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:21.918261+01:00",
+ end_time: "2025-04-24T10:32:22.516654+01:00",
+ elapsed: 598402125,
+ idx: 13,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "Now let me try the done call again:",
+ tool_calls: [
+ {
+ name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"yes","description":"Code review completed and no issues found. The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_call_id: "toolu_016TBxz5Ma8y3if3rY4DZBXE",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:26.926407+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 60,
+ cache_read_input_tokens: 3957,
+ output_tokens: 217,
+ cost_usd: 0.0046851,
+ },
+ start_time: "2025-04-24T10:32:22.534359+01:00",
+ end_time: "2025-04-24T10:32:26.926391+01:00",
+ elapsed: 4392093250,
+ idx: 14,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"yes","description":"Code review completed and no issues found. The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_result:
+ "Please ask the user to review your work. Be concise - users are more likely to read shorter comments.",
+ tool_call_id: "toolu_016TBxz5Ma8y3if3rY4DZBXE",
+ timestamp: "2025-04-24T10:32:26.978771+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:26.92661+01:00",
+ end_time: "2025-04-24T10:32:26.978758+01:00",
+ elapsed: 52149125,
+ idx: 15,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content:
+ 'I\'ve completed your request:\n\n1. Added a new line to dummy.txt: "Adding a new dummy line here - requested change"\n2. Created a git commit with the change using the specified author information\n3. Included "Co-Authored-By: sketch" in the commit message as required\n\nThe change has been successfully committed to the repository. Is there anything else you\'d like me to help with?',
+ timestamp: "2025-04-24T10:32:29.072661+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 5,
+ cache_creation_input_tokens: 250,
+ cache_read_input_tokens: 4017,
+ output_tokens: 92,
+ cost_usd: 0.0035376,
+ },
+ start_time: "2025-04-24T10:32:26.99749+01:00",
+ end_time: "2025-04-24T10:32:29.072654+01:00",
+ elapsed: 2075193375,
+ turnDuration: 28393844125,
+ idx: 16,
+ },
+];
+
+export const initialState: State = {
+ message_count: initialMessages.length,
+ total_usage: {
+ start_time: "2025-04-24T10:32:00.679057+01:00",
+ messages: 8,
+ input_tokens: 46,
+ output_tokens: 1051,
+ cache_read_input_tokens: 23024,
+ cache_creation_input_tokens: 4267,
+ total_cost_usd: 0.038811450000000004,
+ tool_uses: {
+ bash: 2,
+ codereview: 1,
+ done: 2,
+ patch: 1,
+ title: 1,
+ },
+ },
+ initial_commit: "a6c5a08a451ef1082774a7affb6af58775e7bc16",
+ title: "Add a line to dummy.txt and commit the change",
+ hostname: "MacBook-Pro-9.local",
+ working_dir: "/Users/pokey/src/spaghetti",
+ os: "darwin",
+ git_origin: "git@github.com:pokey/spaghetti.git",
+ inside_hostname: "MacBook-Pro-9.local",
+ inside_os: "darwin",
+ inside_working_dir: "/Users/pokey/src/spaghetti",
+};
diff --git a/webui/src/web-components/demo/mocks/handlers.ts b/webui/src/web-components/demo/mocks/handlers.ts
new file mode 100644
index 0000000..c112efd
--- /dev/null
+++ b/webui/src/web-components/demo/mocks/handlers.ts
@@ -0,0 +1,63 @@
+import { http, HttpResponse, delay } from "msw";
+import { initialState, initialMessages } from "./fixtures/dummy";
+
+// Mock state updates for long-polling simulation
+let currentState = { ...initialState };
+const messages = [...initialMessages];
+const ADD_NEW_MESSAGES =
+ new URL(window.location.href).searchParams.get("addNewMessages") === "1";
+
+export const handlers = [
+ // Unified state endpoint that handles both regular and polling requests
+ http.get("*/state", async ({ request }) => {
+ const url = new URL(request.url);
+ const isPoll = url.searchParams.get("poll") === "true";
+
+ if (!isPoll) {
+ // Regular state request
+ return HttpResponse.json(currentState);
+ }
+
+ // This is a long-polling request
+ await delay(ADD_NEW_MESSAGES ? 2000 : 60000); // Simulate waiting for changes
+
+ if (ADD_NEW_MESSAGES) {
+ // Simulate adding new messages
+ messages.push({
+ type: "agent",
+ end_of_turn: false,
+ content: "Here's a message",
+ timestamp: "2025-04-24T10:32:29.072661+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 5,
+ cache_creation_input_tokens: 250,
+ cache_read_input_tokens: 4017,
+ output_tokens: 92,
+ cost_usd: 0.0035376,
+ },
+ start_time: "2025-04-24T10:32:26.99749+01:00",
+ end_time: "2025-04-24T10:32:29.072654+01:00",
+ elapsed: 2075193375,
+ turnDuration: 28393844125,
+ idx: messages.length,
+ });
+
+ // Update the state with new messages
+ currentState = {
+ ...currentState,
+ message_count: messages.length,
+ };
+ }
+
+ return HttpResponse.json(currentState);
+ }),
+
+ // Messages endpoint
+ http.get("*/messages", ({ request }) => {
+ const url = new URL(request.url);
+ const startIndex = parseInt(url.searchParams.get("start") || "0");
+
+ return HttpResponse.json(messages.slice(startIndex));
+ }),
+];
diff --git a/webui/src/web-components/demo/sketch-app-shell.demo.html b/webui/src/web-components/demo/sketch-app-shell.demo.html
index 48fc100..8c4458b 100644
--- a/webui/src/web-components/demo/sketch-app-shell.demo.html
+++ b/webui/src/web-components/demo/sketch-app-shell.demo.html
@@ -5,7 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>sketch coding assistant</title>
<link rel="stylesheet" href="sketch-app-shell.css" />
- <script src="../sketch-app-shell.ts" type="module"></script>
+ <script type="module">
+ const { worker } = await import("./mocks/browser");
+ await worker.start();
+ await import("../sketch-app-shell.ts");
+ </script>
</head>
<body>
<sketch-app-shell></sketch-app-shell>