diff --git a/perf/efps/.depcheckrc.json b/perf/efps/.depcheckrc.json new file mode 100644 index 00000000000..c99cd71956c --- /dev/null +++ b/perf/efps/.depcheckrc.json @@ -0,0 +1,3 @@ +{ + "ignores": ["@swc-node/register", "@types/react", "@types/react-dom"] +} diff --git a/perf/efps/.env.template b/perf/efps/.env.template new file mode 100644 index 00000000000..63a9e413cd5 --- /dev/null +++ b/perf/efps/.env.template @@ -0,0 +1,3 @@ +VITE_PERF_EFPS_PROJECT_ID=qk0wb6qx +VITE_PERF_EFPS_DATASET=test +PERF_EFPS_SANITY_TOKEN= diff --git a/perf/efps/.gitignore b/perf/efps/.gitignore new file mode 100644 index 00000000000..d567662bd06 --- /dev/null +++ b/perf/efps/.gitignore @@ -0,0 +1,4 @@ +/builds +/results +/.exported +.env diff --git a/perf/efps/README.md b/perf/efps/README.md new file mode 100644 index 00000000000..578b160be06 --- /dev/null +++ b/perf/efps/README.md @@ -0,0 +1,85 @@ +# Editor "Frames per Second" — eFPS benchmarks + +This folder contains a performance test suite for benchmarking the Sanity Studio editor and ensuring smooth performance. The suite is designed to run various tests and measure the editor's performance using the eFPS (editor Frames Per Second) metric. + +## Overview + +The performance test suite is part of the Sanity Studio monorepo and is used to benchmark the editor's performance. It runs a series of tests on different document types and field configurations to measure the responsiveness and smoothness of the editing experience. + +## eFPS Metric + +The eFPS (editor Frames Per Second) metric is used to quantify the performance of the Sanity Studio editor. Here's how it works: + +1. The test suite measures the time it takes for the editor to respond to user input (e.g., typing in a field). +2. This response time is then converted into a "frames per second" analogy to provide an intuitive understanding of performance. +3. The eFPS is calculated as: `eFPS = 1000 / responseTime` + +We use the "frames per second" analogy because it helps us have a better intuition for what constitutes good or bad performance. Just like in video games or animations: + +- Higher eFPS values indicate smoother, more responsive performance. +- Lower eFPS values suggest lag or sluggishness in the editor. + +For example: + +- An eFPS of 60 or higher is generally considered very smooth. +- An eFPS between 30-60 is acceptable but may show some lag. +- An eFPS below 30 indicates noticeable performance issues. + +## Percentiles + +The test suite reports eFPS values at different percentiles (p50, p75, and p90) for each run. Here's why we use percentiles and what they tell us: + +- **p50 (50th percentile or median)**: This represents the typical performance. Half of the interactions were faster than this, and half were slower. +- **p75 (75th percentile)**: 75% of interactions were faster than this value. It gives us an idea of performance during slightly worse conditions. +- **p90 (90th percentile)**: 90% of interactions were faster than this value. This helps us understand performance during more challenging scenarios or edge cases. + +Using percentiles allows us to: + +1. Get a more comprehensive view of performance across various conditions. +2. Identify inconsistencies or outliers in performance. +3. Ensure that we're not just optimizing for average cases but also for worst-case scenarios. + +## Test Structure + +Each test in the suite has its own build. This approach offers several advantages: + +1. **Isolation**: Each test has its own schema and configuration, preventing interference between tests. +2. **Ease of Adding Tests**: New tests can be added without affecting existing ones, making the suite more modular and maintainable. +3. **Accurate Profiling**: Individual builds allow for more precise source maps, which leads to better profiling output and easier performance debugging. + +## Adding a New Test + +To add a new test to the suite: + +1. Create a new folder in the `tests` directory with your test name. +2. Create the following files in your test folder: + - `sanity.config.ts`: Define the Sanity configuration for your test. + - `sanity.types.ts`: Define TypeScript types for your schema (if needed). + - `.ts`: Implement your test using the `defineEfpsTest` function. +3. If your test requires assets, add them to an `assets` subfolder. +4. Update the `tests` array in `index.ts` to include your new test. + +Example structure for a new test: + +``` +tests/ + newtest/ + assets/ + sanity.config.ts + sanity.types.ts + newtest.ts +``` + +## CPU Profiles + +The test suite generates CPU profiles for each test run. These profiles are remapped to the original source code, making them easier to analyze. To inspect a CPU profile: + +1. Open Google Chrome DevTools. +2. Go to the "Performance" tab. +3. Click on "Load profile" and select the `.cpuprofile` file from the `results` directory. + +The mapped CPU profiles allow you to: + +- Identify performance bottlenecks in the original source code. +- Analyze the time spent in different functions and components. +- Optimize the areas of code that have the most significant impact on performance. diff --git a/perf/efps/entry.tsx b/perf/efps/entry.tsx new file mode 100644 index 00000000000..2583f1f8ddc --- /dev/null +++ b/perf/efps/entry.tsx @@ -0,0 +1,17 @@ +import {createRoot} from 'react-dom/client' +import {Studio} from 'sanity' +import {structureTool} from 'sanity/structure' + +import config from '#config' + +const configWithStructure = { + ...config, + plugins: [...(config.plugins || []), structureTool()], +} + +const container = document.getElementById('container') +if (!container) throw new Error('Could not find `#container`') + +const root = createRoot(container) + +root.render() diff --git a/perf/efps/helpers/calculatePercentile.ts b/perf/efps/helpers/calculatePercentile.ts new file mode 100644 index 00000000000..54acbded118 --- /dev/null +++ b/perf/efps/helpers/calculatePercentile.ts @@ -0,0 +1,21 @@ +export function calculatePercentile(numbers: number[], percentile: number): number { + // Sort the array in ascending order + const sorted = numbers.slice().sort((a, b) => a - b) + + // Calculate the index + const index = percentile * (sorted.length - 1) + + // If the index is an integer, return the value at that index + if (Number.isInteger(index)) { + return sorted[index] + } + + // Otherwise, interpolate between the two nearest values + const lowerIndex = Math.floor(index) + const upperIndex = Math.ceil(index) + const lowerValue = sorted[lowerIndex] + const upperValue = sorted[upperIndex] + + const fraction = index - lowerIndex + return lowerValue + (upperValue - lowerValue) * fraction +} diff --git a/perf/efps/helpers/exec.ts b/perf/efps/helpers/exec.ts new file mode 100644 index 00000000000..2fe01a07e74 --- /dev/null +++ b/perf/efps/helpers/exec.ts @@ -0,0 +1,72 @@ +import {spawn} from 'node:child_process' +import process from 'node:process' + +import chalk from 'chalk' +import {type Ora} from 'ora' + +interface ExecOptions { + spinner: Ora + command: string + text: [string, string] + cwd?: string +} + +export async function exec({ + spinner, + command, + text: [inprogressText, successText], + cwd, +}: ExecOptions): Promise { + spinner.start(inprogressText) + + const maxColumnLength = 80 + const maxLines = 12 + const outputLines: string[] = [] + + function updateSpinnerText() { + spinner.text = `${inprogressText}\n${outputLines + .map((line) => { + return chalk.dim( + `${chalk.cyan('│')} ${ + line.length > maxColumnLength ? `${line.slice(0, maxColumnLength)}…` : line + }`, + ) + }) + .join('\n')}` + } + + await new Promise((resolve, reject) => { + const childProcess = spawn(command, { + shell: true, + stdio: process.env.CI ? 'inherit' : ['inherit', 'pipe', 'pipe'], + cwd, + }) + + function handleOutput(data: Buffer) { + const newLines = data.toString().split('\n') + for (const line of newLines) { + if (line.trim() !== '') { + outputLines.push(line.trim()) + if (outputLines.length > maxLines) { + outputLines.shift() + } + updateSpinnerText() + } + } + } + + childProcess.stdout?.on('data', handleOutput) + childProcess.stderr?.on('data', handleOutput) + + childProcess.on('close', (code) => { + if (code === 0) resolve() + else reject(new Error(`Command exited with code ${code}`)) + }) + + childProcess.on('error', (error) => { + reject(error) + }) + }) + + spinner.succeed(successText) +} diff --git a/perf/efps/helpers/measureFpsForInput.ts b/perf/efps/helpers/measureFpsForInput.ts new file mode 100644 index 00000000000..4b0c584c795 --- /dev/null +++ b/perf/efps/helpers/measureFpsForInput.ts @@ -0,0 +1,82 @@ +import {type Locator} from 'playwright' + +import {type EfpsResult} from '../types' +import {calculatePercentile} from './calculatePercentile' + +export async function measureFpsForInput(input: Locator): Promise { + await input.waitFor({state: 'visible'}) + const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + + await input.click() + await new Promise((resolve) => setTimeout(resolve, 500)) + + const rendersPromise = input.evaluate(async (el: HTMLInputElement | HTMLTextAreaElement) => { + const updates: {value: string; timestamp: number}[] = [] + + const mutationObserver = new MutationObserver(() => { + updates.push({value: el.value, timestamp: Date.now()}) + }) + + if (el instanceof HTMLTextAreaElement) { + mutationObserver.observe(el, {childList: true, characterData: true, subtree: true}) + } else { + mutationObserver.observe(el, {attributes: true, attributeFilter: ['value']}) + } + + await new Promise((resolve) => { + const handler = () => { + el.removeEventListener('blur', handler) + resolve() + } + + el.addEventListener('blur', handler) + }) + + return updates + }) + await new Promise((resolve) => setTimeout(resolve, 500)) + + const inputEvents: {character: string; timestamp: number}[] = [] + + const startingMarker = '__START__|' + const endingMarker = '__END__' + + await input.pressSequentially(endingMarker) + await new Promise((resolve) => setTimeout(resolve, 500)) + for (let i = 0; i < endingMarker.length; i++) { + await input.press('ArrowLeft') + } + await input.pressSequentially(startingMarker) + await new Promise((resolve) => setTimeout(resolve, 500)) + + for (const character of characters) { + inputEvents.push({character, timestamp: Date.now()}) + await input.press(character) + await new Promise((resolve) => setTimeout(resolve, 0)) + } + + await input.blur() + + const renderEvents = await rendersPromise + + await new Promise((resolve) => setTimeout(resolve, 500)) + + const latencies = inputEvents.map((inputEvent) => { + const matchingEvent = renderEvents.find(({value}) => { + if (!value.includes(startingMarker) || !value.includes(endingMarker)) return false + + const [, afterStartingMarker] = value.split(startingMarker) + const [beforeEndingMarker] = afterStartingMarker.split(endingMarker) + return beforeEndingMarker.includes(inputEvent.character) + }) + if (!matchingEvent) throw new Error(`No matching event for ${inputEvent.character}`) + + return matchingEvent.timestamp - inputEvent.timestamp + }) + + const p50 = 1000 / calculatePercentile(latencies, 0.5) + const p75 = 1000 / calculatePercentile(latencies, 0.75) + const p90 = 1000 / calculatePercentile(latencies, 0.9) + + return {p50, p75, p90, latencies} +} diff --git a/perf/efps/helpers/measureFpsForPte.ts b/perf/efps/helpers/measureFpsForPte.ts new file mode 100644 index 00000000000..ffa233e9064 --- /dev/null +++ b/perf/efps/helpers/measureFpsForPte.ts @@ -0,0 +1,94 @@ +import {type Locator} from 'playwright' + +import {type EfpsResult} from '../types' +import {calculatePercentile} from './calculatePercentile' + +export async function measureFpsForPte(pteField: Locator): Promise { + const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + + await pteField.waitFor({state: 'visible'}) + await new Promise((resolve) => setTimeout(resolve, 500)) + + await pteField.click() + + const contentEditable = pteField.locator('[contenteditable="true"]') + await contentEditable.waitFor({state: 'visible'}) + + const rendersPromise = contentEditable.evaluate(async (el: HTMLElement) => { + const updates: { + value: string + timestamp: number + // with very large PTE fields, it may take time to serialize the result + // so we capture this time and remove it from the final metric + textContentProcessingTime: number + }[] = [] + + const mutationObserver = new MutationObserver(() => { + const start = performance.now() + const textContent = el.textContent || '' + const end = performance.now() + + updates.push({ + value: textContent, + timestamp: Date.now(), + textContentProcessingTime: end - start, + }) + }) + + mutationObserver.observe(el, {subtree: true, characterData: true}) + + await new Promise((resolve) => { + const handler = () => { + el.removeEventListener('blur', handler) + resolve() + } + + el.addEventListener('blur', handler) + }) + + return updates + }) + await new Promise((resolve) => setTimeout(resolve, 500)) + + const inputEvents: {character: string; timestamp: number}[] = [] + + const startingMarker = '__START__|' + const endingMarker = '__END__' + + await contentEditable.pressSequentially(endingMarker) + await new Promise((resolve) => setTimeout(resolve, 500)) + for (let i = 0; i < endingMarker.length; i++) { + await contentEditable.press('ArrowLeft') + } + await contentEditable.pressSequentially(startingMarker) + await new Promise((resolve) => setTimeout(resolve, 500)) + + for (const character of characters) { + inputEvents.push({character, timestamp: Date.now()}) + await contentEditable.press(character) + await new Promise((resolve) => setTimeout(resolve, 0)) + } + + await contentEditable.blur() + + const renderEvents = await rendersPromise + + const latencies = inputEvents.map((inputEvent) => { + const matchingEvent = renderEvents.find(({value}) => { + if (!value.includes(startingMarker) || !value.includes(endingMarker)) return false + + const [, afterStartingMarker] = value.split(startingMarker) + const [beforeEndingMarker] = afterStartingMarker.split(endingMarker) + return beforeEndingMarker.includes(inputEvent.character) + }) + if (!matchingEvent) throw new Error(`No matching event for ${inputEvent.character}`) + + return matchingEvent.timestamp - inputEvent.timestamp - matchingEvent.textContentProcessingTime + }) + + const p50 = 1000 / calculatePercentile(latencies, 0.5) + const p75 = 1000 / calculatePercentile(latencies, 0.75) + const p90 = 1000 / calculatePercentile(latencies, 0.9) + + return {p50, p75, p90, latencies} +} diff --git a/perf/efps/helpers/remapCpuProfile.ts b/perf/efps/helpers/remapCpuProfile.ts new file mode 100644 index 00000000000..e58bbef9e97 --- /dev/null +++ b/perf/efps/helpers/remapCpuProfile.ts @@ -0,0 +1,59 @@ +import fs from 'node:fs' +import path from 'node:path' + +import globby from 'globby' +import {type CDPSession} from 'playwright' +import SourceMap from 'source-map' + +type CpuProfile = Extract< + Awaited>, + {profile: {nodes: unknown[]; startTime: number; endTime: number}} +>['profile'] + +export async function remapCpuProfile( + cpuProfile: CpuProfile, + sourceMapsDir: string, +): Promise { + const sourceMaps = new Map() + const sourceMapFiles = await globby(path.join(sourceMapsDir, '**/*.map')) + + for (const sourceMapFile of sourceMapFiles) { + const mapContent = await fs.promises.readFile(sourceMapFile, 'utf8') + const consumer = await new SourceMap.SourceMapConsumer(JSON.parse(mapContent)) + sourceMaps.set(path.basename(sourceMapFile, '.map'), consumer) + } + + const remappedCpuProfile = { + ...cpuProfile, + nodes: await Promise.all( + cpuProfile.nodes.map(async (node) => { + const {callFrame} = node + if (callFrame.url && callFrame.lineNumber >= 0 && callFrame.columnNumber >= 0) { + const filename = path.basename(new URL(callFrame.url).pathname) + const mapConsumer = sourceMaps.get(filename) + + if (mapConsumer) { + const originalPosition = mapConsumer.originalPositionFor({ + line: callFrame.lineNumber + 1, + column: callFrame.columnNumber, + }) + + if (originalPosition.source) { + callFrame.url = originalPosition.source + callFrame.lineNumber = originalPosition.line - 1 + callFrame.columnNumber = originalPosition.column + callFrame.functionName = originalPosition.name || callFrame.functionName + } + } + } + return node + }), + ), + } + + for (const consumer of sourceMaps.values()) { + consumer.destroy() + } + + return remappedCpuProfile +} diff --git a/perf/efps/index.html b/perf/efps/index.html new file mode 100644 index 00000000000..77827b10647 --- /dev/null +++ b/perf/efps/index.html @@ -0,0 +1,22 @@ + + + + + + + Vite + React + TS + + + +
+ + + diff --git a/perf/efps/index.ts b/perf/efps/index.ts new file mode 100644 index 00000000000..c7cb95e7703 --- /dev/null +++ b/perf/efps/index.ts @@ -0,0 +1,115 @@ +/* eslint-disable no-console */ +// eslint-disable-next-line import/no-unassigned-import +import 'dotenv/config' + +import path from 'node:path' +import process from 'node:process' +import {fileURLToPath} from 'node:url' + +import {createClient} from '@sanity/client' +import chalk from 'chalk' +import Table from 'cli-table3' +import Ora from 'ora' + +// eslint-disable-next-line import/no-extraneous-dependencies +import {exec} from './helpers/exec' +import {runTest} from './runTest' +import article from './tests/article/article' +import recipe from './tests/recipe/recipe' +import singleString from './tests/singleString/singleString' +import synthetic from './tests/synthetic/synthetic' + +const headless = true +const tests = [singleString, recipe, article, synthetic] + +const projectId = process.env.VITE_PERF_EFPS_PROJECT_ID! +const dataset = process.env.VITE_PERF_EFPS_DATASET! +const token = process.env.PERF_EFPS_SANITY_TOKEN! + +const client = createClient({ + projectId, + dataset, + token, + useCdn: false, + apiVersion: 'v2024-08-08', +}) + +const workspaceDir = path.dirname(fileURLToPath(import.meta.url)) +const monorepoRoot = path.resolve(workspaceDir, '../..') +const timestamp = new Date() + +const resultsDir = path.join( + workspaceDir, + 'results', + // e.g. run__1724188682225__8-20-2024__4-18-02pm + // makes it sortable and still human parsable + `run__${timestamp.getTime()}__${timestamp + .toLocaleDateString('en-US') + .replaceAll('/', '-')}__${timestamp + .toLocaleTimeString('en-US') + .replaceAll(' ', '') + .replaceAll(':', '-') + .toLowerCase()}`, +) + +const spinner = Ora() + +spinner.info(`Running ${tests.length} tests: ${tests.map((t) => `'${t.name}'`).join(', ')}`) + +await exec({ + text: ['Building the monorepo…', 'Built monorepo'], + command: 'pnpm run build', + spinner, + cwd: monorepoRoot, +}) + +await exec({ + text: ['Ensuring playwright is installed…', 'Playwright is installed'], + command: 'npx playwright install', + spinner, +}) + +const table = new Table({ + head: [chalk.bold('benchmark'), 'eFPS p50', 'eFPS p75', 'eFPS p90'].map((cell) => + chalk.cyan(cell), + ), +}) + +const formatFps = (fps: number) => { + const rounded = fps.toFixed(1) + if (fps >= 60) return chalk.green(rounded) + if (fps < 20) return chalk.red(rounded) + return chalk.yellow(rounded) +} + +for (let i = 0; i < tests.length; i++) { + const test = tests[i] + const results = await runTest({ + prefix: `Running '${test.name}' [${i + 1}/${tests.length}]…`, + test, + resultsDir, + spinner, + client, + headless, + projectId, + }) + + for (const result of results) { + table.push({ + [[chalk.bold(test.name), result.label ? `(${result.label})` : ''].join(' ')]: [ + formatFps(result.p50), + formatFps(result.p75), + formatFps(result.p90), + ], + }) + } +} + +console.log(table.toString()) +console.log(` + +│ ${chalk.bold('eFPS — editor "Frames Per Second"')} +│ +│ The number of renders ("frames") that is assumed to be possible +│ within a second. Derived from input latency. ${chalk.green('Higher')} is better. +`) diff --git a/perf/efps/package.json b/perf/efps/package.json new file mode 100644 index 00000000000..6d5039cbde3 --- /dev/null +++ b/perf/efps/package.json @@ -0,0 +1,38 @@ +{ + "name": "efps", + "version": "3.53.0", + "private": true, + "description": "Sanity Studio eFPS perf suite", + "keywords": [], + "license": "MIT", + "author": "Sanity.io ", + "type": "module", + "imports": { + "#config": "./placeholderConfig.ts" + }, + "scripts": { + "start": "node --import @swc-node/register/esm-register ./index.ts", + "test": "npm start" + }, + "devDependencies": { + "@sanity/client": "^6.21.2", + "@swc-node/register": "^1.10.9", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/serve-handler": "^6.1.4", + "@vitejs/plugin-react": "^4.3.1", + "chalk": "^4.1.2", + "cli-table3": "^0.6.5", + "dotenv": "^16.0.3", + "globby": "^10.0.0", + "ora": "^8.0.1", + "playwright": "^1.46.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "rollup-plugin-sourcemaps": "^0.6.3", + "sanity": "workspace:*", + "serve-handler": "^6.1.5", + "source-map": "^0.7.4", + "vite": "^5.4.2" + } +} diff --git a/perf/efps/placeholderConfig.ts b/perf/efps/placeholderConfig.ts new file mode 100644 index 00000000000..45a4c757d06 --- /dev/null +++ b/perf/efps/placeholderConfig.ts @@ -0,0 +1,5 @@ +import {type SingleWorkspace} from 'sanity' + +declare const config: SingleWorkspace + +export default config diff --git a/perf/efps/runTest.ts b/perf/efps/runTest.ts new file mode 100644 index 00000000000..201487686df --- /dev/null +++ b/perf/efps/runTest.ts @@ -0,0 +1,159 @@ +import fs from 'node:fs' +import {createServer} from 'node:http' +import path from 'node:path' +import {fileURLToPath} from 'node:url' + +import {type SanityClient} from '@sanity/client' +import react from '@vitejs/plugin-react' +import {type Ora} from 'ora' +import {chromium} from 'playwright' +import sourcemaps from 'rollup-plugin-sourcemaps' +import handler from 'serve-handler' +import * as vite from 'vite' + +import {remapCpuProfile} from './helpers/remapCpuProfile' +import {type EfpsResult, type EfpsTest, type EfpsTestRunnerContext} from './types' + +const workspaceDir = path.dirname(fileURLToPath(import.meta.url)) + +interface RunTestOptions { + prefix: string + test: EfpsTest + resultsDir: string + spinner: Ora + projectId: string + headless: boolean + client: SanityClient +} + +export async function runTest({ + prefix, + test, + resultsDir, + spinner, + projectId, + headless, + client, +}: RunTestOptions): Promise { + const log = (text: string) => { + spinner.text = `${prefix}\n └ ${text}` + } + + spinner.start(prefix) + + const outDir = path.join(workspaceDir, 'builds', test.name) + const testResultsDir = path.join(resultsDir, test.name) + + await fs.promises.mkdir(outDir, {recursive: true}) + log('Building…') + + await vite.build({ + appType: 'spa', + build: {outDir, sourcemap: true}, + plugins: [{...sourcemaps(), enforce: 'pre'}, react()], + resolve: { + alias: {'#config': fileURLToPath(test.configPath!)}, + }, + logLevel: 'silent', + }) + + log('Starting server…') + const server = createServer((req, res) => { + handler(req, res, { + rewrites: [{source: '**', destination: '/index.html'}], + public: outDir, + }) + }) + + await new Promise((resolve) => server.listen(3300, resolve)) + + let browser + let document + let context + + try { + log('Launching browser…') + browser = await chromium.launch({headless}) + context = await browser.newContext({ + storageState: { + cookies: [], + origins: [ + { + origin: 'http://localhost:3300', + localStorage: [ + { + name: `__studio_auth_token_${projectId}`, + value: JSON.stringify({ + token: client.config().token, + time: new Date().toISOString(), + }), + }, + ], + }, + ], + }, + }) + + const page = await context.newPage() + + const runnerContext: EfpsTestRunnerContext = {browser, context, page, client} + + log('Creating test document…') + const documentToCreate = + typeof test.document === 'function' ? await test.document(runnerContext) : test.document + document = await client.create(documentToCreate) + + const cdp = await context.newCDPSession(page) + + log('Loading editor…') + await page.goto( + `http://localhost:3300/intent/edit/id=${encodeURIComponent(document._id)};type=${encodeURIComponent( + documentToCreate._type, + )}`, + ) + + await cdp.send('Profiler.enable') + await cdp.send('Profiler.start') + + log('Benchmarking…') + const result = await test.run({...runnerContext, document}) + + log('Saving results…') + const results = Array.isArray(result) ? result : [result] + + const {profile} = await cdp.send('Profiler.stop') + const remappedProfile = await remapCpuProfile(profile, outDir) + + await fs.promises.mkdir(testResultsDir, {recursive: true}) + await fs.promises.writeFile( + path.join(testResultsDir, 'results.json'), + JSON.stringify(results, null, 2), + ) + await fs.promises.writeFile( + path.join(testResultsDir, 'raw.cpuprofile'), + JSON.stringify(profile), + ) + await fs.promises.writeFile( + path.join(testResultsDir, 'mapped.cpuprofile'), + JSON.stringify(remappedProfile), + ) + + spinner.succeed(`Ran benchmark '${test.name}'`) + + return results + } finally { + await new Promise((resolve, reject) => + server.close((err) => (err ? reject(err) : resolve())), + ) + + await context?.close() + await browser?.close() + + if (document) { + await Promise.allSettled([ + client.delete(document._id), + client.delete(`drafts.${document._id}`), + ]) + } + } +} diff --git a/perf/efps/tests/article/article.ts b/perf/efps/tests/article/article.ts new file mode 100644 index 00000000000..f99cdde3b3f --- /dev/null +++ b/perf/efps/tests/article/article.ts @@ -0,0 +1,169 @@ +import fs from 'node:fs' +import path from 'node:path' +import {fileURLToPath} from 'node:url' + +import {measureFpsForInput} from '../../helpers/measureFpsForInput' +import {measureFpsForPte} from '../../helpers/measureFpsForPte' +import {defineEfpsTest} from '../../types' +import document from './document' +import {author, categories} from './references' +import {type Author, type Category, type Hero} from './sanity.types' + +const dirname = path.dirname(fileURLToPath(import.meta.url)) + +const generateKey = () => { + const rng = () => + Math.floor(Math.random() * 255) + .toString(16) + .padStart(2, '0') + + return Array.from({length: 6}, rng).join('') +} + +export default defineEfpsTest({ + name: 'article', + configPath: await import.meta.resolve?.('./sanity.config.ts'), + document: async ({client}) => { + const images = (await fs.promises.readdir(path.join(dirname, './assets'))).filter((name) => + name.endsWith('.webp'), + ) + + const imageAssets = await Promise.all( + images + .filter((name) => name.endsWith('.webp')) + .map((imageName) => + client.assets.upload( + 'image', + fs.createReadStream(path.join(dirname, './assets', imageName)), + { + source: {id: imageName, name: 'article-test'}, + }, + ), + ), + ) + const [asset1, asset2, asset3, asset4, asset5, asset6] = imageAssets + + const transaction = client.transaction() + + const authorWithImage: Omit = { + ...author, + profilePicture: { + _type: 'image', + asset: { + _ref: asset1._id, + _type: 'reference', + }, + }, + } + transaction.createOrReplace(authorWithImage) + + for (const category of categories) { + const categoryWithImage: Omit = { + ...category, + image: { + _type: 'image', + asset: { + _ref: asset2._id, + _type: 'reference', + }, + }, + } + + transaction.createOrReplace(categoryWithImage) + } + await transaction.commit() + + const createHero = (): Hero & {_key: string} => ({ + _key: generateKey(), + _type: 'hero', + image: { + _type: 'image', + asset: {_type: 'reference', _ref: asset5._id}, + }, + body: [ + { + _key: generateKey(), + _type: 'block', + children: [{_key: generateKey(), _type: 'span', marks: [], text: 'Example text'}], + markDefs: [], + style: 'normal', + }, + ], + }) + + document.mainImage = { + _type: 'image', + asset: {_ref: asset3._id, _type: 'reference'}, + } + + document.body?.splice(10, 0, { + _type: 'image', + _key: generateKey(), + asset: {_type: 'reference', _ref: asset1._id}, + }) + document.body?.splice(15, 0, createHero()) + + document.body?.splice(20, 0, { + _type: 'image', + _key: generateKey(), + asset: {_type: 'reference', _ref: asset2._id}, + }) + document.body?.splice(25, 0, createHero()) + + document.body?.splice(30, 0, { + _type: 'image', + _key: generateKey(), + asset: {_type: 'reference', _ref: asset3._id}, + }) + document.body?.splice(35, 0, createHero()) + + document.body?.splice(40, 0, { + _type: 'image', + _key: generateKey(), + asset: {_type: 'reference', _ref: asset4._id}, + }) + document.body?.splice(45, 0, createHero()) + + document.body?.splice(50, 0, { + _type: 'image', + _key: generateKey(), + asset: {_type: 'reference', _ref: asset5._id}, + }) + document.body?.splice(55, 0, createHero()) + + document.body?.splice(60, 0, { + _type: 'image', + _key: generateKey(), + asset: {_type: 'reference', _ref: asset6._id}, + }) + document.body?.splice(65, 0, createHero()) + + return document + }, + run: async ({page}) => { + return [ + { + label: 'title', + ...(await measureFpsForInput( + page.locator('[data-testid="field-title"] input[type="text"]'), + )), + }, + { + label: 'body', + ...(await measureFpsForPte(page.locator('[data-testid="field-body"]'))), + }, + { + label: 'string in object', + ...(await measureFpsForInput( + page.locator('[data-testid="field-seo.metaTitle"] input[type="text"]'), + )), + }, + { + label: 'string in array', + ...(await measureFpsForInput( + page.locator('[data-testid="field-tags"] input[type="text"]').first(), + )), + }, + ] + }, +}) diff --git a/perf/efps/tests/article/assets/image1.webp b/perf/efps/tests/article/assets/image1.webp new file mode 100644 index 00000000000..34bf26ace8f Binary files /dev/null and b/perf/efps/tests/article/assets/image1.webp differ diff --git a/perf/efps/tests/article/assets/image2.webp b/perf/efps/tests/article/assets/image2.webp new file mode 100644 index 00000000000..b67a2f00357 Binary files /dev/null and b/perf/efps/tests/article/assets/image2.webp differ diff --git a/perf/efps/tests/article/assets/image3.webp b/perf/efps/tests/article/assets/image3.webp new file mode 100644 index 00000000000..fc08b068b77 Binary files /dev/null and b/perf/efps/tests/article/assets/image3.webp differ diff --git a/perf/efps/tests/article/assets/image4.webp b/perf/efps/tests/article/assets/image4.webp new file mode 100644 index 00000000000..aa9ca2f1eaa Binary files /dev/null and b/perf/efps/tests/article/assets/image4.webp differ diff --git a/perf/efps/tests/article/assets/image5.webp b/perf/efps/tests/article/assets/image5.webp new file mode 100644 index 00000000000..7b2a4a43197 Binary files /dev/null and b/perf/efps/tests/article/assets/image5.webp differ diff --git a/perf/efps/tests/article/assets/image6.webp b/perf/efps/tests/article/assets/image6.webp new file mode 100644 index 00000000000..2b3ac06d02a Binary files /dev/null and b/perf/efps/tests/article/assets/image6.webp differ diff --git a/perf/efps/tests/article/document.ts b/perf/efps/tests/article/document.ts new file mode 100644 index 00000000000..ac621aca3dc --- /dev/null +++ b/perf/efps/tests/article/document.ts @@ -0,0 +1,3054 @@ +import {type Article} from './sanity.types' + +const article: Omit = { + _type: 'article', + body: [ + { + _key: 'fb9224599155', + _type: 'block', + children: [ + { + _key: '4fb33a378f640', + _type: 'span', + marks: [], + text: 'The workplace has undergone a seismic shift over the past few years, with remote work transitioning from a rare perk to a widespread necessity. What began as a temporary response to the COVID-19 pandemic has now evolved into a permanent fixture in many industries, reshaping the way we think about work. As businesses and employees alike have adjusted to this new normal, the question on everyone’s mind is: what comes next?', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'b074c253352d', + _type: 'block', + children: [ + { + _key: '5a07d81302da0', + _type: 'span', + marks: [], + text: 'In this rapidly changing landscape, technology has emerged as the driving force behind the evolution of remote work. From cloud computing to AI-powered tools, innovations are not just enabling remote work but are also redefining what it means to work. As we look to the future, it’s clear that the office of tomorrow will be more flexible, more digital, and more interconnected than ever before.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '1ad7da40078b', + _type: 'block', + children: [ + { + _key: 'e920168985b90', + _type: 'span', + marks: [], + text: 'This post delves into how these emerging technologies are shaping the future of remote work, offering new opportunities while also presenting fresh challenges. Whether you’re a business leader, an employee navigating this new terrain, or simply curious about where work is headed, understanding these trends is crucial. Let’s explore how the workplace is being reimagined and what it means for the future of work.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'b3482d976787', + _type: 'block', + children: [ + { + _key: '2d7159b09a290', + _type: 'span', + marks: [], + text: 'The Evolution of Remote Work', + }, + ], + markDefs: [], + style: 'h2', + }, + { + _key: '3398b3f72175', + _type: 'block', + children: [ + { + _key: 'd2dd8535a1250', + _type: 'span', + marks: [], + text: 'Brief History of Remote Work', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: 'bb784fb20e28', + _type: 'block', + children: [ + { + _key: '49b2f8f8abe10', + _type: 'span', + marks: [], + text: 'Remote work is not a new concept; in fact, its roots can be traced back several decades. The idea of telecommuting first gained traction in the 1970s, when advances in telecommunications technology made it possible for some employees to work from home. Early adopters were typically in niche fields, such as freelance writing, consulting, or sales, where the nature of the work allowed for flexibility. However, the majority of businesses still adhered to the traditional office model, where physical presence was equated with productivity and commitment.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'eb9a78ba7aea', + _type: 'block', + children: [ + { + _key: '35f97a752b8a0', + _type: 'span', + marks: [], + text: 'As technology continued to evolve through the 1990s and 2000s, remote work became more feasible, but it remained the exception rather than the rule. High-speed internet, email, and mobile phones began to untether workers from their desks, yet many companies were hesitant to fully embrace remote work, often citing concerns about collaboration, communication, and productivity. Despite this, the seeds of a remote work revolution were being planted, with the rise of digital nomadism and the gig economy hinting at the possibilities of a more flexible work environment.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'f85a670c02d6', + _type: 'block', + children: [ + { + _key: '527661a886700', + _type: 'span', + marks: [], + text: 'The Pandemic Catalyst', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: '0c9d2913cc01', + _type: 'block', + children: [ + { + _key: 'b58c71aef2cb0', + _type: 'span', + marks: [], + text: 'The COVID-19 pandemic served as a massive catalyst, forcing companies worldwide to adopt remote work almost overnight. What had previously been a slow, gradual shift became an urgent, global transformation. Organizations that had been reluctant to implement remote work policies were suddenly faced with no other option, and the concept of "business as usual" was redefined.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '7c24f04afaf8', + _type: 'block', + children: [ + { + _key: '250f18f846610', + _type: 'span', + marks: [], + text: 'For many businesses, the transition was rocky. Companies had to rapidly invest in technology and infrastructure to support remote operations, while employees grappled with the challenges of working from home, often in less-than-ideal conditions. Despite the initial difficulties, many organizations found that remote work was not only possible but also beneficial. Productivity levels often remained steady or even improved, and the flexibility of remote work allowed employees to balance their professional and personal lives more effectively.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '5af5845fc4db', + _type: 'block', + children: [ + { + _key: 'c0dc4ec0572d0', + _type: 'span', + marks: [], + text: 'Some companies, particularly those in tech, thrived in this new environment, quickly adapting their workflows and finding innovative ways to maintain team cohesion and productivity. Others struggled, particularly those whose business models were heavily reliant on in-person interactions or physical presence. The pandemic exposed the weaknesses in traditional work models and highlighted the need for adaptability and resilience.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '0c5b655ba68a', + _type: 'block', + children: [ + { + _key: '34b4151790520', + _type: 'span', + marks: [], + text: 'Now, as the world begins to emerge from the pandemic, remote work is no longer seen as a temporary solution. Instead, it has become a permanent and integral part of the work landscape. Companies are reevaluating their long-term strategies, with many opting for hybrid models that combine the benefits of remote work with the advantages of in-person collaboration. The pandemic has not only accelerated the adoption of remote work but has also fundamentally changed how we think about work itself.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '77c66d8f7522', + _type: 'block', + children: [ + { + _key: '2474c79e6f430', + _type: 'span', + marks: [], + text: 'Key Technologies Driving Remote Work', + }, + ], + markDefs: [], + style: 'h2', + }, + { + _key: '2fbf73be87b7', + _type: 'block', + children: [ + { + _key: '7b309b9488a10', + _type: 'span', + marks: [], + text: 'Cloud Computing', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: 'f0f0179940de', + _type: 'block', + children: [ + { + _key: 'a37450f1cb3b0', + _type: 'span', + marks: [], + text: 'One of the foundational pillars of the remote work revolution is cloud computing. The cloud has transformed how businesses operate, enabling employees to access files, applications, and systems from anywhere in the world with an internet connection. Before the advent of cloud technology, remote work was limited by the need for physical access to company servers and data centers. Today, cloud platforms like Amazon Web Services (AWS), Google Cloud, and Microsoft Azure provide the infrastructure necessary for businesses of all sizes to operate remotely with ease.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '08dc7c234419', + _type: 'block', + children: [ + { + _key: '75fd19cb78cf0', + _type: 'span', + marks: [], + text: 'Cloud computing allows for real-time collaboration, where multiple employees can work on the same document simultaneously, regardless of their location. This capability is essential for maintaining productivity and ensuring that teams can work together effectively, even when spread across different time zones. Additionally, the cloud offers scalable solutions that can grow with a business, making it possible to adapt quickly to changing demands and remote workforces.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '2c39ef4602f5', + _type: 'block', + children: [ + { + _key: 'bc25377ccc160', + _type: 'span', + marks: [], + text: 'The security features of cloud platforms are also critical, as they provide robust data protection, encryption, and access control measures. This ensures that sensitive company information remains secure, even when accessed remotely. As remote work continues to evolve, the reliance on cloud computing will only deepen, making it an indispensable tool for modern businesses.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'b2917701128c', + _type: 'block', + children: [ + { + _key: '96c8e73c0e7f0', + _type: 'span', + marks: [], + text: 'Communication and Collaboration Tools', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: '90390adc2062', + _type: 'block', + children: [ + { + _key: '1ff847de1c730', + _type: 'span', + marks: [], + text: 'Effective communication and collaboration are the lifeblood of any successful remote work strategy. With teams no longer sharing physical office space, the need for reliable and efficient communication tools has become paramount. Platforms like Slack, Zoom, and Microsoft Teams have risen to the challenge, offering comprehensive solutions that facilitate everything from daily stand-ups to large-scale virtual meetings.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'c5c42b475ff4', + _type: 'block', + children: [ + { + _key: '32451eccbdb10', + _type: 'span', + marks: [], + text: 'Slack, for example, has become a central hub for many remote teams, offering channels for team discussions, direct messaging, file sharing, and integration with other tools. It provides the immediacy of communication that was once only possible in person, fostering a sense of connection and collaboration even when team members are miles apart.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'e7f72ad6e66b', + _type: 'block', + children: [ + { + _key: '97248410e8220', + _type: 'span', + marks: [], + text: 'Zoom has become synonymous with video conferencing, offering high-quality video calls that have replaced in-person meetings for many companies. Its ease of use and robust feature set, including screen sharing and breakout rooms, make it ideal for both formal meetings and casual check-ins. Similarly, Microsoft Teams combines video conferencing with chat and collaboration features, tightly integrated with Microsoft Office products, making it a versatile tool for businesses already invested in the Microsoft ecosystem.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '599d96f227be', + _type: 'block', + children: [ + { + _key: '7a7048c9053a0', + _type: 'span', + marks: [], + text: 'Beyond these well-known platforms, a host of other tools have emerged to meet specific needs. Virtual whiteboards like Miro and MURAL enable creative brainstorming sessions, while project management tools like Trello, Asana, and Monday.com help teams track progress and stay organized. These tools collectively ensure that remote teams can work together as effectively as they would in a physical office.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'ea8065535257', + _type: 'block', + children: [ + { + _key: '9bca6fe2f7da0', + _type: 'span', + marks: [], + text: 'Cybersecurity Innovations', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: 'b3be339feb6d', + _type: 'block', + children: [ + { + _key: '2352e10cb5850', + _type: 'span', + marks: [], + text: 'As remote work becomes the norm, the importance of cybersecurity cannot be overstated. The shift to remote work has expanded the attack surface for cyber threats, making it essential for companies to invest in robust security measures. The challenge lies in securing a distributed workforce where employees access company networks from various locations, often using personal devices.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'd28f9ef088f9', + _type: 'block', + children: [ + { + _key: 'abb74ff13e740', + _type: 'span', + marks: [], + text: 'One of the key innovations in this space is the Zero Trust Architecture (ZTA). Unlike traditional security models that trust users within a network by default, Zero Trust operates on the principle of "never trust, always verify." This means that every user and device must be authenticated and authorized before being granted access to any part of the network, regardless of their location. ZTA is particularly well-suited to remote work, as it addresses the security challenges posed by employees working outside the traditional office environment.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '8b2059559631', + _type: 'block', + children: [ + { + _key: '57a49db345db0', + _type: 'span', + marks: [], + text: 'Virtual Private Networks (VPNs) have also become more sophisticated, providing encrypted connections that allow remote workers to securely access company resources. However, the limitations of traditional VPNs have led to the development of more advanced solutions, such as Secure Access Service Edge (SASE), which combines network security and wide-area networking into a single cloud-delivered service. SASE provides more comprehensive security for remote workers by integrating features like secure web gateways, firewalls, and zero-trust network access.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '07301000b694', + _type: 'block', + children: [ + { + _key: 'bafa3e52c5a50', + _type: 'span', + marks: [], + text: 'Furthermore, endpoint security has become a critical focus, as employees often use a variety of devices to connect to company systems. Endpoint detection and response (EDR) tools help monitor and protect these devices from cyber threats, ensuring that any potential breaches are detected and mitigated quickly.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'd76ee5b91e90', + _type: 'block', + children: [ + { + _key: 'f831b90da9760', + _type: 'span', + marks: [], + text: 'As the remote work landscape continues to evolve, cybersecurity innovations will play a crucial role in protecting businesses and ensuring that remote work remains a viable and secure option for companies around the world.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '157f77e70626', + _type: 'block', + children: [ + { + _key: '2e0c40c74b1c', + _type: 'span', + marks: [], + text: 'The Benefits and Challenges of Remote Work', + }, + ], + markDefs: [], + style: 'h2', + }, + { + _key: '76b83529d459', + _type: 'block', + children: [ + { + _key: 'ebfd5be5d8e30', + _type: 'span', + marks: [], + text: 'Benefits', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: '7a4b4a09d15c', + _type: 'block', + children: [ + { + _key: 'e55e231bffe90', + _type: 'span', + marks: [], + text: 'Flexibility and Work-Life Balance', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '225f9a5bcdd4', + _type: 'block', + children: [ + { + _key: '03ed8091a79a0', + _type: 'span', + marks: [], + text: 'One of the most significant advantages of remote work is the flexibility it offers. Employees can create work environments tailored to their needs, leading to increased satisfaction and productivity. The ability to work from home, a coffee shop, or even another country allows workers to integrate their professional and personal lives more seamlessly. This flexibility can reduce the stress of commuting, offer more time for family and hobbies, and provide opportunities to work during the hours when employees are most productive. Many workers report a better work-life balance, which contributes to overall job satisfaction and well-being.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '26b16e416ebf', + _type: 'block', + children: [ + { + _key: 'c3bec483e7c30', + _type: 'span', + marks: [], + text: 'Access to a Global Talent Pool', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: 'e7cd73cacf71', + _type: 'block', + children: [ + { + _key: '022753d58a430', + _type: 'span', + marks: [], + text: 'Remote work opens up a world of possibilities for businesses seeking talent. Companies are no longer limited by geographical boundaries when hiring, allowing them to access a diverse, global talent pool. This can lead to better hiring decisions, as businesses can choose the best candidates from a much broader selection. Additionally, remote work can help companies foster more diverse and inclusive teams by enabling them to hire individuals who may have been previously excluded due to location or mobility constraints.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '237b38762fce', + _type: 'block', + children: [ + { + _key: '5fffcd583d990', + _type: 'span', + marks: [], + text: 'Cost Savings for Businesses and Employees', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '8ca648b9300f', + _type: 'block', + children: [ + { + _key: '71643ceb18330', + _type: 'span', + marks: [], + text: 'Remote work can lead to significant cost savings for both employers and employees. For businesses, the reduction in the need for physical office space, utilities, and on-site amenities can result in substantial savings. Many companies have downsized or eliminated their office spaces, reinvesting those funds into technology and employee benefits. For employees, the savings come in the form of reduced commuting costs, less spending on work attire, and fewer daily expenses like lunches or coffee. These financial benefits make remote work an attractive option for both parties.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '853243a2c465', + _type: 'block', + children: [ + { + _key: '7e1ada0edbfe0', + _type: 'span', + marks: [], + text: 'Challenges', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: '8dcde703c942', + _type: 'block', + children: [ + { + _key: '7cb1cb5b84140', + _type: 'span', + marks: [], + text: 'Maintaining Company Culture and Employee Engagement', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: 'bee98d256363', + _type: 'block', + children: [ + { + _key: 'bf28f4fb81400', + _type: 'span', + marks: [], + text: "One of the biggest challenges of remote work is maintaining a strong company culture. When employees are scattered across different locations, it can be difficult to foster the same sense of camaraderie and shared purpose that comes naturally in a traditional office setting. Companies must be intentional about building and sustaining their culture remotely, which often requires new strategies and tools. Virtual team-building activities, regular check-ins, and clear communication of company values are essential for keeping employees engaged and aligned with the organization's mission.", + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '58c3c414f68d', + _type: 'block', + children: [ + { + _key: 'd7bcce2699ca0', + _type: 'span', + marks: [], + text: 'Addressing Mental Health and Burnout', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '0183eb247823', + _type: 'block', + children: [ + { + _key: '6b5e8d021a480', + _type: 'span', + marks: [], + text: 'While remote work offers many benefits, it can also blur the boundaries between work and personal life, leading to burnout and mental health issues. The lack of a clear separation between home and work can make it challenging for employees to "switch off" at the end of the day, resulting in longer working hours and increased stress. Additionally, remote work can lead to feelings of isolation, as employees may miss the social interactions and support systems that come with working in an office. Companies need to prioritize mental health by encouraging work-life balance, offering mental health resources, and fostering a supportive remote work environment.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'f062e7d6c104', + _type: 'block', + children: [ + { + _key: '1fd621dbc7260', + _type: 'span', + marks: [], + text: 'The Digital Divide and Accessibility Issues', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '3dd67db6d963', + _type: 'block', + children: [ + { + _key: 'b035d79041670', + _type: 'span', + marks: [], + text: 'Not all employees have equal access to the technology and resources needed for effective remote work. The digital divide refers to the gap between those who have reliable internet access, up-to-date devices, and a conducive work environment, and those who do not. This divide can create significant challenges for remote workers, particularly in regions with poor infrastructure or for individuals with disabilities. Ensuring that all employees have the tools and support they need to succeed in a remote work environment is crucial for promoting equity and inclusion. Companies may need to provide technology stipends, offer flexible work arrangements, or invest in accessibility solutions to bridge this gap.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'dc9d3ac303a4', + _type: 'block', + children: [ + { + _key: '733276c4dd970', + _type: 'span', + marks: [], + text: 'Remote work offers numerous benefits, but it also presents challenges that businesses and employees must navigate carefully. By understanding and addressing these challenges, organizations can create a remote work environment that is both productive and sustainable, allowing them to fully realize the advantages of this new way of working.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '7f518b487955', + _type: 'block', + children: [ + { + _key: 'c9a7452ea8990', + _type: 'span', + marks: [], + text: 'The Future of Remote Work', + }, + ], + markDefs: [], + style: 'h2', + }, + { + _key: '3140772eabbf', + _type: 'block', + children: [ + { + _key: '58496f8e43a30', + _type: 'span', + marks: [], + text: 'Hybrid Work Models', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: '3187e5fb681a', + _type: 'block', + children: [ + { + _key: 'ae1bbac4c59b0', + _type: 'span', + marks: [], + text: 'As the dust settles from the rapid shift to remote work during the pandemic, many companies are embracing a hybrid work model that blends remote and in-office work. This approach seeks to combine the best of both worlds: the flexibility and autonomy of remote work with the collaborative and social benefits of in-person interactions. Hybrid work models allow employees to choose where they work based on their tasks, preferences, and personal circumstances, fostering a more adaptable and personalized work experience.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'd838376ee18d', + _type: 'block', + children: [ + { + _key: 'da9e03b90bbc0', + _type: 'span', + marks: [], + text: 'Some companies are implementing structured hybrid models, where employees spend certain days in the office and the rest working remotely. Others are opting for more flexible arrangements, giving employees the freedom to decide when and how often they come into the office. For example, tech giants like Google and Microsoft have adopted hybrid models that prioritize employee choice while ensuring that teams can come together for collaboration and team-building when necessary.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'b844a3377a34', + _type: 'block', + children: [ + { + _key: '44fa6268e4e90', + _type: 'span', + marks: [], + text: 'Hybrid work models also offer businesses the opportunity to rethink their office spaces. Instead of maintaining large, dedicated office buildings, companies are exploring more dynamic office designs, such as coworking spaces, hot-desking, and collaborative hubs. These spaces are designed to facilitate teamwork and creativity while accommodating a workforce that no longer requires a traditional, nine-to-five office presence.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '3541d79780bb', + _type: 'block', + children: [ + { + _key: '6c7a9272d0fa0', + _type: 'span', + marks: [], + text: 'The Role of Artificial Intelligence', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: 'e038db967eed', + _type: 'block', + children: [ + { + _key: 'e528ed6aa7de0', + _type: 'span', + marks: [], + text: 'Artificial Intelligence (AI) is poised to play a transformative role in the future of remote work. AI-driven tools and virtual assistants are increasingly being integrated into the remote work environment, enhancing productivity, communication, and decision-making processes. These technologies can automate routine tasks, freeing up employees to focus on more complex and creative work.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'e288b34ea4f5', + _type: 'block', + children: [ + { + _key: 'daa677d3d8410', + _type: 'span', + marks: [], + text: 'For instance, AI-powered chatbots and virtual assistants can handle administrative tasks such as scheduling meetings, managing emails, and organizing files. This automation reduces the cognitive load on employees and allows them to concentrate on higher-value activities. Additionally, AI-driven analytics tools can provide insights into team performance, helping managers make data-driven decisions about workload distribution, project timelines, and employee well-being.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'f53bc7c829d5', + _type: 'block', + children: [ + { + _key: 'bca5233a5cea0', + _type: 'span', + marks: [], + text: 'AI also has the potential to enhance collaboration in remote teams. Machine learning algorithms can analyze communication patterns and suggest ways to improve team dynamics, such as identifying potential bottlenecks or recommending more effective communication channels. In virtual meetings, AI can assist by generating real-time transcripts, summarizing key points, and even suggesting action items based on the discussion.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '1a0407f33ef4', + _type: 'block', + children: [ + { + _key: 'd4093e3715ce0', + _type: 'span', + marks: [], + text: 'However, the integration of AI into remote work also raises important questions about job displacement and the future of work. As AI continues to evolve, companies will need to consider how to balance the benefits of automation with the need to retain and upskill their human workforce. The focus should be on using AI to augment human capabilities rather than replace them, ensuring that employees are empowered to thrive in an increasingly digital work environment.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'ba25302610f6', + _type: 'block', + children: [ + { + _key: '64051824da520', + _type: 'span', + marks: [], + text: 'The Impact on Global Workforces', + }, + ], + markDefs: [], + style: 'h3', + }, + { + _key: 'e20408cd5181', + _type: 'block', + children: [ + { + _key: '1f098f5ce8b90', + _type: 'span', + marks: [], + text: 'The shift to remote work is having a profound impact on global workforces, reshaping job markets, and altering economic landscapes. As remote work becomes more prevalent, companies are no longer limited by geographical boundaries when hiring talent. This has led to the rise of a truly global workforce, where employees from different countries and time zones collaborate seamlessly.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'b72005766a4e', + _type: 'block', + children: [ + { + _key: '4081a7bf03f90', + _type: 'span', + marks: [], + text: 'For workers, this globalization of the job market offers unprecedented opportunities. Skilled professionals can access job opportunities with companies around the world, often without the need to relocate. This shift is particularly significant for individuals in regions with limited local job prospects, as remote work allows them to participate in the global economy and access higher-paying jobs.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'a1bb6828168b', + _type: 'block', + children: [ + { + _key: '9c14d6d47b400', + _type: 'span', + marks: [], + text: 'However, the global nature of remote work also presents challenges. Competition for jobs has become more intense, as candidates from different regions compete for the same positions. Additionally, companies must navigate the complexities of managing a distributed workforce, including issues related to international labor laws, tax regulations, and cultural differences.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '58e8131efd9a', + _type: 'block', + children: [ + { + _key: '96d9778fe51a0', + _type: 'span', + marks: [], + text: 'The rise of remote work is also likely to accelerate the trend toward job automation. As companies become more comfortable with remote work, they may increasingly rely on AI and automation to manage remote teams and streamline operations. This could lead to job displacement in certain sectors, particularly for roles that are easily automated.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '4e9d5c2c89db', + _type: 'block', + children: [ + { + _key: 'e442537b012b0', + _type: 'span', + marks: [], + text: 'On the other hand, the demand for digital skills is expected to grow, creating new opportunities for workers who can adapt to the changing job market. To thrive in this environment, both employees and companies will need to invest in continuous learning and development, focusing on the skills that are most valuable in a remote and automated world.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '8eea99d21007', + _type: 'hero', + body: [ + { + _key: '5374630ae8dd', + _type: 'block', + children: [ + { + _key: 'd5e68ad84682', + _type: 'span', + marks: [], + text: 'The future of remote work is one of both opportunity and challenge. As companies and employees navigate this evolving landscape, the focus will need to be on creating flexible, inclusive, and sustainable work environments that harness the power of technology while addressing the human aspects of work. The decisions made today will shape the future of work for years to come, making it essential for businesses to stay ahead of the curve and adapt to the ongoing changes in the workplace.', + }, + ], + markDefs: [], + style: 'normal', + }, + ], + }, + { + _key: '71fee5027702', + _type: 'block', + children: [ + { + _key: 'cbc2cfc48e960', + _type: 'span', + marks: [], + text: '', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'c0f3536e5056', + _type: 'block', + children: [ + { + _key: '9c4d077533b20', + _type: 'span', + marks: [], + text: 'Preparing for the Future', + }, + ], + markDefs: [], + style: 'h2', + }, + { + _key: 'd6126de2135a', + _type: 'columns', + columns: [ + { + _key: 'c063a474534c', + content: [ + { + _key: 'df1c6f6758db', + _type: 'block', + children: [ + { + _key: 'db4bf355a5c7', + _type: 'span', + marks: [], + text: 'Strategies for Embracing Remote Work Long-Term', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '0624df0c2a98', + _type: 'block', + children: [ + { + _key: '689c69854458', + _type: 'span', + marks: [], + text: 'To successfully navigate the future of remote work, businesses must develop robust strategies that go beyond simply allowing employees to work from home. This involves rethinking company policies, investing in technology, and fostering a culture that supports remote work as a permanent option.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '933238f72ffb', + _type: 'block', + children: [ + { + _key: '50690381654a', + _type: 'span', + marks: [], + text: 'One of the first steps is to establish clear remote work policies that outline expectations, communication protocols, and performance metrics. These policies should be flexible enough to accommodate the diverse needs of a remote workforce while providing a framework for consistency and accountability. For instance, businesses might define core working hours for team collaboration while allowing employees the flexibility to manage their own schedules outside of these hours.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '62e9f862db3b', + _type: 'block', + children: [ + { + _key: '963f2e849bc4', + _type: 'span', + marks: [], + text: 'Investing in technology is crucial for enabling seamless remote work. Companies should ensure that their employees have access to the tools and platforms necessary for effective communication, collaboration, and task management. This includes not only the basics like high-speed internet and reliable hardware but also advanced software solutions that integrate AI, cybersecurity, and cloud services. Additionally, companies may need to provide ongoing training to help employees stay up-to-date with the latest tools and best practices for remote work.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '0769278b77f3', + _type: 'block', + children: [ + { + _key: '1901f71825ed', + _type: 'span', + marks: [], + text: 'Fostering a strong company culture in a remote environment requires intentional effort. Businesses should prioritize regular virtual team-building activities, transparent communication from leadership, and opportunities for professional development. By creating a sense of community and shared purpose, companies can maintain employee engagement and morale, even when workers are dispersed across different locations.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'f2f49393afba', + _type: 'block', + children: [ + { + _key: '3e493d1a128a', + _type: 'span', + marks: [], + text: 'Investment in Technology and Employee Training', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '21f7bcf5024c', + _type: 'block', + children: [ + { + _key: 'a6d2bbf03169', + _type: 'span', + marks: [], + text: 'As remote work continues to evolve, businesses must prioritize investments in both technology and employee training to remain competitive. The rapid pace of technological advancement means that the tools and platforms used for remote work today may be outdated in just a few years. Companies should adopt a proactive approach to technology adoption, regularly assessing and upgrading their systems to ensure they remain effective and secure.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '011a97014102', + _type: 'block', + children: [ + { + _key: '3cad8225ac5b', + _type: 'span', + marks: [], + text: 'Cybersecurity is a particularly critical area of investment. As remote work increases the potential for cyber threats, businesses must implement robust security measures to protect sensitive data and systems. This includes deploying advanced cybersecurity solutions, such as Zero Trust Architecture, endpoint security tools, and Secure Access Service Edge (SASE) frameworks. Regular security audits and employee training on cybersecurity best practices are essential to mitigate risks and ensure that all team members understand their role in maintaining security.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'ec422d99f610', + _type: 'block', + children: [ + { + _key: '464a9d384c20', + _type: 'span', + marks: [], + text: 'Employee training should also focus on developing the digital skills necessary for success in a remote work environment. This includes training on new software tools, AI-driven applications, and best practices for remote collaboration and communication. By investing in continuous learning and development, companies can empower their employees to adapt to the changing demands of remote work and remain productive and engaged.', + }, + ], + markDefs: [], + style: 'normal', + }, + ], + title: 'For Businesses', + }, + { + _key: '3ee4c083e49e', + content: [ + { + _key: '91cd64ab66c2', + _type: 'block', + children: [ + { + _key: '22ef1c1470dc', + _type: 'span', + marks: [], + text: 'Adapting to Remote Work and Developing New Skills', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: 'b439b3d823aa', + _type: 'block', + children: [ + { + _key: 'ea8570f92f31', + _type: 'span', + marks: [], + text: 'As remote work becomes more prevalent, employees must take an active role in adapting to this new way of working. Success in a remote work environment requires a combination of technical skills, self-discipline, and effective communication. Employees should start by creating a dedicated workspace that minimizes distractions and promotes productivity. Establishing a routine that includes regular breaks and boundaries between work and personal life is also crucial for maintaining a healthy work-life balance.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '1347248fbab8', + _type: 'block', + children: [ + { + _key: 'a15ef8fdc07d', + _type: 'span', + marks: [], + text: 'Developing new skills is essential for thriving in a remote work setting. Employees should focus on enhancing their digital literacy, including proficiency with communication and collaboration tools, project management software, and cybersecurity practices. Familiarity with AI-driven tools and an understanding of how to leverage data for decision-making can also give employees a competitive edge.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '45f774f45554', + _type: 'block', + children: [ + { + _key: '706070deddc5', + _type: 'span', + marks: [], + text: 'Communication skills are particularly important in a remote work environment. Employees should practice clear, concise, and timely communication, whether through email, video calls, or messaging platforms. Being proactive about staying in touch with colleagues and managers can help prevent misunderstandings and ensure that work progresses smoothly.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '2896763c89ad', + _type: 'block', + children: [ + { + _key: '1177c6dbf135', + _type: 'span', + marks: [], + text: 'Balancing Remote Work with Personal Well-Being', + }, + ], + markDefs: [], + style: 'h4', + }, + { + _key: '99621746fc0f', + _type: 'block', + children: [ + { + _key: 'a6edd74c7287', + _type: 'span', + marks: [], + text: 'While remote work offers many benefits, it can also pose challenges to personal well-being. The lack of physical separation between work and home can lead to longer working hours and difficulty disconnecting from work. To maintain well-being, employees should establish clear boundaries, such as setting specific work hours and creating rituals to signal the end of the workday.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '454d067029bf', + _type: 'block', + children: [ + { + _key: 'dc81d3277f21', + _type: 'span', + marks: [], + text: 'Staying connected with colleagues and participating in virtual social activities can help combat feelings of isolation. It’s also important for employees to prioritize their mental and physical health by incorporating regular exercise, healthy eating, and mindfulness practices into their routines. Taking time off when needed and communicating openly with managers about workload and well-being can prevent burnout and ensure a sustainable work-life balance.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '7e20f8d2b020', + _type: 'block', + children: [ + { + _key: 'a86df1fbe100', + _type: 'span', + marks: [], + text: 'In summary, preparing for the future of remote work requires both businesses and employees to be proactive, adaptable, and committed to continuous improvement. By embracing new technologies, fostering strong company cultures, and prioritizing personal well-being, organizations and individuals can thrive in the evolving landscape of work. The future of work is being shaped today, and those who are prepared will be well-positioned to succeed in a remote-first world.', + }, + ], + markDefs: [], + style: 'normal', + }, + ], + title: 'For Employees', + }, + ], + }, + { + _key: 'f4f83c891a72', + _type: 'block', + children: [ + { + _key: 'ae31592b568d0', + _type: 'span', + marks: [], + text: '', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'fa641e352752', + _type: 'block', + children: [ + { + _key: '86c8f1dbbfd7', + _type: 'span', + marks: [], + text: 'Conclusion', + }, + ], + markDefs: [], + style: 'h2', + }, + { + _key: '12d0c7365ae5', + _type: 'block', + children: [ + { + _key: '4ef47a1f6f3b0', + _type: 'span', + marks: [], + text: 'As we stand at the crossroads of a new era in the workforce, the rapid evolution of remote work invites us to reflect deeply on the implications, challenges, and opportunities it presents. The shift from traditional office environments to remote or hybrid models is more than just a change in where we work; it’s a profound transformation of how we work, how we connect, and how we define success in our professional lives. This transformation demands careful consideration, as the decisions we make today will reverberate for years to come, shaping the future of work in ways we can only begin to imagine.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '90c4aa97695f', + _type: 'block', + children: [ + { + _key: '70810cdc100c0', + _type: 'span', + marks: [], + text: 'One of the most striking aspects of this shift is the way technology is fundamentally altering the workplace. The integration of cloud computing, AI, and advanced communication tools has made it possible for teams to operate seamlessly across continents, breaking down geographical barriers that once limited collaboration and innovation. However, as we celebrate these advancements, we must also ask ourselves:', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'f050e810e07b', + _type: 'block', + children: [ + { + _key: '75666a24ad230', + _type: 'span', + marks: ['strong'], + text: 'Are we fully prepared for the societal and ethical implications that come with them? ', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '52b59273d817', + _type: 'block', + children: [ + { + _key: 'cd1c55744f25', + _type: 'span', + marks: [], + text: 'How do we ensure that the digital divide does not widen, leaving some behind in this new era of work? These questions are not just technical but deeply human, touching on issues of equality, accessibility, and the very nature of work itself.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '57fe87f9091a', + _type: 'block', + children: [ + { + _key: '00cb7cecaeba0', + _type: 'span', + marks: [], + text: 'As companies increasingly adopt hybrid models, the challenge of maintaining a cohesive company culture becomes ever more pressing. What does it mean to belong to a company when you may never meet your colleagues in person? How can we build and sustain meaningful connections in a world where face-to-face interactions are no longer the norm? These questions prompt us to rethink traditional approaches to team-building and leadership, pushing us to explore new ways of fostering engagement and loyalty in a digital-first world.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '61d71a0a4e26', + _type: 'block', + children: [ + { + _key: '6746d6599fa90', + _type: 'span', + marks: [], + text: 'At the same time, the role of AI in the workplace raises profound ethical questions. While AI promises to enhance productivity and streamline tasks, it also poses risks of job displacement and surveillance. How can businesses harness the power of AI without sacrificing the human touch that is so crucial to innovation and creativity? And how do we navigate the delicate balance between automation and employment, ensuring that technology empowers rather than displaces the workforce?', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '1d71a87349c6', + _type: 'block', + children: [ + { + _key: '2dbfd3f6a8860', + _type: 'span', + marks: [], + text: 'On a broader scale, the global impact of remote work is reshaping economies and societies in ways that are both exciting and uncertain. The ability to hire talent from anywhere in the world democratizes opportunity, but it also creates new challenges in terms of competition, regulation, and cultural integration. As we move forward, it’s essential to consider how these global dynamics will influence not just the business landscape, but also social structures, community ties, and even national identities.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '5adac3ef745b', + _type: 'block', + children: [ + { + _key: '3e18f1bf5d5a0', + _type: 'span', + marks: [], + text: 'For businesses, the future of remote work presents an opportunity to lead with vision and empathy. Companies that invest in technology, training, and culture today will be better positioned to navigate the uncertainties of tomorrow. However, this requires more than just financial investment; it demands a commitment to continuous learning, innovation, and a willingness to experiment with new models of work. Leaders must be agile, open to feedback, and ready to adapt as the landscape evolves.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '8121e9c5ba06', + _type: 'block', + children: [ + { + _key: 'd11ee7eebff30', + _type: 'span', + marks: [], + text: 'For employees, the remote work revolution offers both freedom and responsibility. The ability to work from anywhere empowers individuals to take control of their careers, but it also requires a high degree of self-discipline, adaptability, and resilience. As the lines between work and personal life continue to blur, finding balance will be more important than ever. Employees must be proactive in managing their well-being, seeking out opportunities for growth, and staying connected with their teams.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '9dbcfd9e07b8', + _type: 'block', + children: [ + { + _key: '2b8d7ff3b3ca0', + _type: 'span', + marks: [], + text: 'As we look ahead, it’s clear that the future of work will be shaped by our collective ability to innovate, adapt, and empathize. The remote work revolution is more than a trend; it’s a fundamental shift in how we live and work. The road ahead is filled with possibilities, and while it may be fraught with challenges, it also offers unparalleled opportunities for growth, connection, and reinvention.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: '42f8ae15f162', + _type: 'block', + children: [ + { + _key: '33cf0954cff40', + _type: 'span', + marks: [], + text: 'Let us approach this future with curiosity, courage, and a commitment to creating a world of work that is inclusive, flexible, and sustainable. As we continue to explore the possibilities of remote work, we must remain mindful of the broader implications and strive to ensure that the future we build is one that benefits ', + }, + { + _key: '9392fce0708e', + _type: 'span', + marks: ['em'], + text: 'everyone', + }, + { + _key: 'ea7c031eb260', + _type: 'span', + marks: [], + text: '.', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'df9956f6f444', + children: [ + { + _type: 'span', + marks: [], + text: 'Achieving Work-Life Balance in the Remote Work Era', + _key: '5b607bf912620', + }, + ], + markDefs: [], + _type: 'block', + style: 'h1', + }, + { + _key: 'ae84a904cfa8', + children: [ + { + _type: 'span', + marks: [], + text: 'The surge in remote work has revolutionized how we approach our professional lives, offering unprecedented flexibility and autonomy. Yet, this new way of working has also brought with it a unique set of challenges, particularly when it comes to maintaining a healthy work-life balance. Without the clear boundaries that a traditional office setting provides, the lines between work and personal life can easily blur, leading to longer hours, increased stress, and a sense of being perpetually "on."', + _key: 'd8517231d5d00', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f8ed46fd64c9', + children: [ + { + _type: 'span', + marks: [], + text: "For many, the shift to remote work has meant trading the daily commute for more time at home, but it has also introduced the challenge of defining when work begins and ends. The kitchen table has become the new office desk, and emails often follow us into the evening. While the convenience of remote work is undeniable, it also requires a new level of discipline and intentionality to ensure that we don't sacrifice our well-being in the process.", + _key: 'f85064a111870', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '0463ae7ceb28', + children: [ + { + _type: 'span', + marks: [], + text: "This article explores the strategies and mindsets necessary to achieve a sustainable work-life balance in the remote work era. From setting boundaries and creating routines to prioritizing mental and physical health, we'll delve into practical tips that can help you navigate the challenges of working from home. Whether you're a seasoned remote worker or new to this way of life, understanding how to balance your professional responsibilities with personal well-being is key to thriving in this new landscape.", + _key: '6d61e8a297a70', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'ab643f03d35d', + children: [ + { + _type: 'span', + marks: [], + text: 'Understanding the Challenges of Remote Work', + _key: '3537d27ecafe0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h2', + }, + { + _key: '1b668136bb97', + children: [ + { + _type: 'span', + marks: [], + text: 'Blurred Boundaries', + _key: '2cd8c6018b1c0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '6ef381449ebe', + children: [ + { + _type: 'span', + marks: [], + text: 'One of the most significant challenges of remote work is the blurring of boundaries between professional and personal life. In a traditional office setting, the physical separation between work and home naturally delineates when work begins and ends. However, when your home becomes your office, this boundary can quickly dissolve. The temptation to check emails after hours, take calls during dinner, or finish up that one last task late at night is strong, and over time, this can lead to an erosion of personal time.', + _key: '7122be0a9fa90', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'e24afc2ba6f8', + children: [ + { + _type: 'span', + marks: [], + text: 'Remote workers often find themselves working longer hours than they would in an office environment. The absence of a commute can make it easier to start the day earlier and finish later, gradually extending the workday without realizing it. Additionally, the lack of a clear signal to stop working—such as leaving the office—can make it difficult to know when to "switch off." This constant connectivity, while initially seeming productive, can eventually lead to burnout as the distinction between work time and personal time fades.', + _key: 'a37eeaee916d0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '95a2fc224296', + children: [ + { + _type: 'span', + marks: [], + text: 'Impact on Mental Health', + _key: '759580bc99210', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '16a982838769', + children: [ + { + _type: 'span', + marks: [], + text: 'The blurring of boundaries between work and home life can have a significant impact on mental health. The pressure to remain constantly available and productive, combined with the isolation of working alone, can increase stress levels and lead to feelings of burnout. Without the natural breaks and social interactions that come with working in an office, remote workers may find themselves more fatigued, anxious, or overwhelmed.', + _key: '04ab5019517f0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'a19010938ed1', + children: [ + { + _type: 'span', + marks: [], + text: 'Several studies have highlighted the mental health challenges associated with remote work. For example, a survey conducted by the American Psychological Association found that nearly half of remote workers reported feeling more isolated, with 67% experiencing increased stress levels compared to when they worked in an office. The lack of in-person interaction and the inability to easily separate work from personal life contribute to these feelings, underscoring the importance of addressing mental health proactively in a remote work environment.', + _key: '7531dc462e990', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '006583172b05', + children: [ + { + _type: 'span', + marks: [], + text: 'Moreover, the expectation to be constantly available can create a sense of pressure that is difficult to escape. This "always-on" culture, exacerbated by digital communication tools, can lead to a phenomenon known as "technostress," where the overwhelming presence of technology and work-related communication leads to stress and anxiety. Remote workers need to be aware of these risks and take steps to protect their mental health by setting clear boundaries and prioritizing self-care.', + _key: 'eaaffdafe9140', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'b0184532253c', + children: [ + { + _type: 'span', + marks: [], + text: 'Isolation and Loneliness', + _key: '0db2c08c9d0e0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '139dd73f8f96', + children: [ + { + _type: 'span', + marks: [], + text: 'While remote work offers the convenience of working from anywhere, it can also lead to feelings of isolation and loneliness. The social interactions that naturally occur in an office setting—casual conversations, team lunches, and impromptu brainstorming sessions—are often lost when working remotely. For many, these interactions are not just a source of connection but also a vital part of the work experience, contributing to a sense of belonging and team cohesion.', + _key: '474f0e5f13ad0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '670d661650b1', + children: [ + { + _type: 'span', + marks: [], + text: 'The absence of these social connections can make remote work feel isolating, particularly for those who thrive on interpersonal interactions. Loneliness can affect not only mental health but also productivity and job satisfaction. Remote workers may find it challenging to stay motivated or feel disconnected from their colleagues, leading to a decrease in overall engagement.', + _key: 'aafa0d0ef6f00', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '38d9d01071ea', + children: [ + { + _type: 'span', + marks: [], + text: "To combat isolation, it's essential for remote workers to find ways to stay connected with their colleagues. This might include regular virtual check-ins, participating in online team-building activities, or even scheduling informal video calls to catch up with coworkers. While these interactions may not fully replace in-person meetings, they can help foster a sense of community and support, making remote work more sustainable and enjoyable in the long term.", + _key: '12ea29b97f490', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1f33cfc58c1f', + children: [ + { + _type: 'span', + marks: [], + text: "Understanding these challenges is the first step toward overcoming them. By recognizing the potential pitfalls of remote work, individuals and organizations can take proactive measures to ensure that the benefits of working from home do not come at the cost of well-being. In the following sections, we'll explore practical strategies for setting boundaries, maintaining mental health, and building a work-life balance that supports long-term success.", + _key: '5ee893b395290', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '08253310f0fc', + children: [ + { + _type: 'span', + marks: [], + text: 'Setting Boundaries for a Healthy Work-Life Balance', + _key: 'cd4b40d434140', + }, + ], + markDefs: [], + _type: 'block', + style: 'h2', + }, + { + _key: 'f32cab913257', + children: [ + { + _type: 'span', + marks: [], + text: 'Designing a Dedicated Workspace', + _key: '75352d1e62730', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '76b793762ec7', + children: [ + { + _type: 'span', + marks: [], + text: 'One of the most effective ways to create a clear distinction between work and personal life in a remote environment is to design a dedicated workspace. When your home becomes your office, it’s crucial to carve out a specific area that is solely for work. This not only helps to mentally separate work tasks from personal activities but also enhances productivity by reducing distractions.', + _key: '1479d7f9310d0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '138d83ee0ec3', + children: [ + { + _type: 'span', + marks: [], + text: 'Your dedicated workspace doesn’t need to be a full home office; it could be as simple as a specific corner of a room or a particular desk that you use exclusively for work. The key is consistency—using the same space for work every day trains your brain to associate that environment with focus and productivity. When you leave that space at the end of the day, it becomes easier to mentally switch off from work.', + _key: '686afeee7b5f0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'fe8ef7a94696', + children: [ + { + _type: 'span', + marks: [], + text: 'Ergonomics also play a significant role in creating an effective workspace. Invest in a comfortable chair, ensure your desk is at the right height, and position your computer monitor at eye level to avoid strain. Good lighting, preferably natural, is important not only for reducing eye strain but also for maintaining energy levels throughout the day. Personalizing your workspace with items that inspire or motivate you can also make it a more enjoyable and productive environment.', + _key: '5bee6a1dba4d0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '9f2871f8f739', + children: [ + { + _type: 'span', + marks: [], + text: 'Establishing a Routine', + _key: '585b366836be0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '9e31cd8662d0', + children: [ + { + _type: 'span', + marks: [], + text: 'In the absence of the traditional 9-to-5 office routine, it’s easy for work hours to become fluid, extending into early mornings, late nights, and weekends. To maintain a healthy work-life balance, it’s essential to establish a consistent routine that delineates work hours from personal time.', + _key: 'b500917c68180', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '051678af4d02', + children: [ + { + _type: 'span', + marks: [], + text: 'Start by setting regular work hours and sticking to them as much as possible. Having a structured start and end time to your workday helps create a sense of normalcy and provides a clear signal to your brain about when it’s time to focus and when it’s time to relax. This routine should include not only your work hours but also breaks for meals, exercise, and other activities that recharge your energy.', + _key: '60f01810c9d80', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '5146d80b0767', + children: [ + { + _type: 'span', + marks: [], + text: 'Morning routines are particularly important in setting the tone for the day. Whether it’s having a cup of coffee, taking a short walk, or spending a few minutes planning your tasks, a morning ritual can help transition your mindset from home mode to work mode. Similarly, an end-of-day routine, such as shutting down your computer or going for an evening walk, can signal the end of the workday and help you unwind.', + _key: '3ecd7a91431c0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '663078e2feb6', + children: [ + { + _type: 'span', + marks: [], + text: 'It’s also beneficial to communicate your routine to others in your household. This can help manage expectations and minimize interruptions during work hours. If you live with family or roommates, make sure they understand your work schedule and the importance of your dedicated workspace, so they can support you in maintaining boundaries.', + _key: 'fa9e48580c5c0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f3f54f1851ab', + children: [ + { + _type: 'span', + marks: [], + text: 'Creating Digital Boundaries', + _key: '4a0cb77116f30', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '2855e1c19636', + children: [ + { + _type: 'span', + marks: [], + text: 'In the digital age, the boundary between work and personal life can become blurred, particularly when work-related notifications and emails are constantly pinging your phone or computer. To maintain a healthy work-life balance, it’s crucial to establish digital boundaries that prevent work from encroaching on your personal time.', + _key: '0b859b36f3e50', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'af1e00c8ef43', + children: [ + { + _type: 'span', + marks: [], + text: 'One effective strategy is to designate specific times for checking and responding to work-related emails and messages. Avoid the temptation to check emails first thing in the morning or late at night, as this can start your day with stress or prevent you from winding down properly. Instead, set aside dedicated times during your workday to manage communications, and stick to them as closely as possible.', + _key: '7dcfff1b4d0e0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'd4b589f75aad', + children: [ + { + _type: 'span', + marks: [], + text: 'Another important aspect of creating digital boundaries is managing your notifications. Disable or mute notifications from work apps during non-work hours to prevent unnecessary interruptions. Many smartphones and computers have “Do Not Disturb” features that can be scheduled to activate during your personal time, helping you disconnect from work.', + _key: '14217de002240', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '6691d470a8e9', + children: [ + { + _type: 'span', + marks: [], + text: 'It’s also helpful to separate work and personal devices if possible. Using different devices for work and leisure activities can create a clearer mental distinction between the two. If separate devices aren’t an option, consider using different user profiles or at least organizing your apps so that work-related ones are grouped separately from personal ones.', + _key: '0f687333f4ab0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f1946c5e63f4', + children: [ + { + _type: 'span', + marks: [], + text: 'Finally, consider setting expectations with your colleagues regarding your availability. Clearly communicate your work hours and encourage a culture of respecting each other’s time outside of those hours. By setting these digital boundaries, you can better protect your personal time and reduce the stress of being constantly connected to work.', + _key: 'fbb84e1348080', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'cca41755839e', + children: [ + { + _type: 'span', + marks: [], + text: 'Establishing boundaries is a crucial step in achieving a sustainable work-life balance in a remote work environment. By designing a dedicated workspace, establishing a routine, and creating digital boundaries, you can better manage the challenges of remote work and enjoy the benefits it offers without compromising your personal well-being. In the next section, we’ll explore how to prioritize mental and physical health to further support a balanced and fulfilling remote work experience.', + _key: 'ad5c438cd7200', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '5d54d1c7a6f5', + children: [ + { + _type: 'span', + marks: [], + text: 'Prioritizing Mental and Physical Well-Being', + _key: 'f9a58876ba8c0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h2', + }, + { + _key: '8df1a506c111', + markDefs: [], + _type: 'block', + style: 'h3', + children: [ + { + _type: 'span', + marks: [], + _key: '7adac0877a67', + text: 'Incorporating Breaks and Movement', + }, + ], + }, + { + _key: '0e2604051aa1', + children: [ + { + _type: 'span', + marks: [], + text: 'In a traditional office setting, natural breaks occur throughout the day—whether it’s walking to a colleague’s desk, grabbing a coffee, or attending an in-person meeting. However, when working remotely, these organic interruptions are often missing, leading to long periods of sitting and staring at a screen. To maintain both physical and mental well-being, it’s essential to intentionally incorporate breaks and movement into your day.', + _key: 'c2ac3aff10f80', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'ec8e05c198ae', + children: [ + { + _type: 'span', + marks: [], + text: 'Regular breaks are not just about stepping away from your work but also about giving your mind a chance to rest and recharge. The Pomodoro Technique, which involves working for 25 minutes followed by a 5-minute break, is one effective method to maintain focus while ensuring you take regular pauses. Use these breaks to stretch, walk around your home, or simply take a moment to breathe and reset.', + _key: '54a3d08553bd0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'ad12ef943389', + children: [ + { + _type: 'span', + marks: [], + text: 'Physical activity is equally important for maintaining health in a remote work environment. Prolonged sitting can lead to a range of health issues, including back pain, poor posture, and decreased energy levels. To counteract this, try incorporating short bursts of physical activity into your day. This could be as simple as a few minutes of stretching, a quick walk around the block, or even a short workout session. Many remote workers find that starting the day with exercise, such as yoga or a morning jog, sets a positive tone for the rest of the day.', + _key: '7b903cbad83b0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f10aef60424c', + children: [ + { + _type: 'span', + marks: [], + text: 'Additionally, consider investing in a standing desk or a convertible desk that allows you to alternate between sitting and standing. This can help reduce the strain associated with long hours of sitting and improve your overall energy levels. Even small changes, like adjusting your posture or using a stability ball as a chair, can have significant benefits for your physical well-being.', + _key: '3e3c9c9353e00', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '214d62b7ea12', + children: [ + { + _type: 'span', + marks: [], + text: 'Mindfulness and Stress Management', + _key: '9568fcc153e60', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: 'afdd03bf9197', + children: [ + { + _type: 'span', + marks: [], + text: 'The fast pace and constant connectivity of remote work can lead to heightened stress levels, making it crucial to practice mindfulness and stress management techniques. Mindfulness involves being fully present in the moment, which can help reduce anxiety and improve focus, especially when juggling multiple tasks in a remote work setting.', + _key: '2fa0012fe8060', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '2d6ee5e5fcda', + children: [ + { + _type: 'span', + marks: [], + text: 'One simple way to practice mindfulness is through deep breathing exercises. Taking a few moments throughout the day to focus on your breath can help calm your mind and reduce stress. Techniques like the 4-7-8 breathing method, where you inhale for 4 seconds, hold your breath for 7 seconds, and exhale for 8 seconds, can be particularly effective in moments of high stress.', + _key: 'cbec54e8abab0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '3a6acfd58296', + children: [ + { + _type: 'span', + marks: [], + text: 'Meditation is another powerful tool for managing stress. Even just a few minutes of meditation each day can help clear your mind, enhance concentration, and improve your overall sense of well-being. There are numerous apps available that offer guided meditations tailored to different needs, whether you’re looking to reduce stress, improve focus, or simply relax.', + _key: '0882a301a5e50', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'dd59eb74d3dd', + children: [ + { + _type: 'span', + marks: [], + text: 'In addition to mindfulness practices, it’s important to recognize and address the sources of stress in your workday. If you find certain tasks overwhelming, break them down into smaller, more manageable steps. Prioritizing tasks and setting realistic goals for what can be accomplished in a day can also help prevent the feeling of being overwhelmed.', + _key: 'fc7e4eeb22e80', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '203308686644', + children: [ + { + _type: 'span', + marks: [], + text: 'It’s equally important to build time into your schedule for activities that bring you joy and relaxation, whether that’s reading a book, cooking a meal, or spending time with loved ones. Engaging in hobbies and leisure activities can help balance the demands of work and provide a much-needed mental break.', + _key: '9946cc70eeb10', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '65f8fffa98a7', + children: [ + { + _type: 'span', + marks: [], + text: 'Seeking Social Connections', + _key: 'cad63e349bd90', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: 'b5f94e96d9a9', + children: [ + { + _type: 'span', + marks: [], + text: 'One of the challenges of remote work is the potential for social isolation. Without the daily interactions that occur naturally in an office setting, it’s easy to feel disconnected from your colleagues and the broader work community. However, maintaining social connections is crucial for both mental health and job satisfaction.', + _key: '7a60ff6f30090', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '9991dd7d9bb3', + children: [ + { + _type: 'span', + marks: [], + text: 'To combat isolation, it’s important to actively seek out opportunities for social interaction, even if they are virtual. Regular check-ins with colleagues can help maintain a sense of connection and camaraderie. These can be formal, such as weekly team meetings, or informal, like virtual coffee breaks where you catch up on non-work-related topics. Video calls, in particular, can help replicate the face-to-face interactions that are often missed in remote work.', + _key: 'f0065b85d79c0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'a485fca2685a', + children: [ + { + _type: 'span', + marks: [], + text: 'Participating in virtual team-building activities can also strengthen bonds with colleagues. Many companies organize online games, trivia contests, or virtual happy hours to help teams stay connected and engaged. These activities not only provide a break from work but also help build a sense of community and belonging.', + _key: '24944dc7b1df0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '440b0cf5e8ae', + children: [ + { + _type: 'span', + marks: [], + text: 'Outside of work, it’s important to nurture your personal relationships. Make time to connect with friends and family, whether through phone calls, video chats, or socially distanced in-person visits. These interactions are vital for emotional support and can provide a counterbalance to the stresses of work.', + _key: 'ae42397529b20', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '250e11283ba9', + children: [ + { + _type: 'span', + marks: [], + text: 'If you’re feeling particularly isolated, consider joining online communities or groups related to your interests or profession. These can provide a sense of belonging and offer opportunities to connect with like-minded individuals who share your experiences and challenges.', + _key: 'fd4157335def0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '464ef7dc4808', + children: [ + { + _type: 'span', + marks: [], + text: 'Prioritizing mental and physical well-being is essential for thriving in a remote work environment. By incorporating breaks and movement, practicing mindfulness and stress management, and actively seeking social connections, you can create a more balanced and fulfilling work-life experience. In the next section, we’ll explore how to leverage technology to further support your work-life balance and maintain long-term well-being.', + _key: '7256b0d76ff30', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'b78c54a7d372', + children: [ + { + _type: 'span', + marks: [], + text: 'Leveraging Technology to Support Work-Life Balance', + _key: '556bb77f30a10', + }, + ], + markDefs: [], + _type: 'block', + style: 'h2', + }, + { + _key: '5a7ad151e678', + children: [ + { + _type: 'span', + marks: [], + text: 'Using Productivity Tools Wisely', + _key: '22197d685bc10', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: 'f1d00b6677b9', + children: [ + { + _type: 'span', + marks: [], + text: 'In the remote work environment, technology is both a boon and a challenge. While it enables us to stay connected and productive from anywhere, it can also be overwhelming if not managed properly. The key to leveraging technology effectively is to use productivity tools that help you stay organized and focused, without letting them dominate your workday.', + _key: '9d1b6c6675970', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '3d889108a51c', + children: [ + { + _type: 'span', + marks: [], + text: 'Productivity apps like Trello, Asana, and Monday.com are excellent for managing tasks and projects. These tools allow you to break down large projects into smaller, manageable tasks, assign deadlines, and track progress in a visual, organized way. By keeping all your tasks in one place, these tools can reduce the mental clutter that comes with trying to remember everything you need to do, allowing you to focus on the task at hand.', + _key: 'd9086587de640', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '520f3b9fd228', + children: [ + { + _type: 'span', + marks: [], + text: 'Calendar apps, such as Google Calendar or Outlook, are crucial for time management. Scheduling your tasks, meetings, and breaks can help you structure your day more effectively and ensure that you allocate time for both work and personal activities. Many of these apps allow you to set reminders, ensuring that you don’t miss important deadlines or meetings.', + _key: '1d8c7b91dfab0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'ae10e15b96d3', + children: [ + { + _type: 'span', + marks: [], + text: 'However, it’s important to use these tools wisely and not let them take over your workday. Over-scheduling or micromanaging your time can lead to unnecessary stress. Instead, use these tools to create a flexible framework for your day, allowing room for spontaneity and adjustments as needed. Remember, the goal is to enhance productivity and work-life balance, not to create additional pressure.', + _key: '9163971cc6830', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'c14e9b270cd4', + children: [ + { + _type: 'span', + marks: [], + text: 'Setting Up Digital Detox Periods', + _key: '0445de81dad00', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '09c0f22d12df', + children: [ + { + _type: 'span', + marks: [], + text: 'While technology is essential for remote work, it’s equally important to disconnect from it periodically to maintain a healthy work-life balance. Digital detox periods—times when you consciously step away from screens and digital devices—are crucial for reducing stress, preventing burnout, and preserving your mental health.', + _key: '264d830be5a40', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '2ea6e50c441a', + children: [ + { + _type: 'span', + marks: [], + text: 'One effective strategy is to establish “tech-free” zones in your home, such as the dining room or bedroom, where you avoid using phones, laptops, or tablets. This helps create a clear boundary between work and relaxation spaces, allowing you to unwind more effectively during your personal time.', + _key: '5d9e546720f90', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f619ec0b98a7', + children: [ + { + _type: 'span', + marks: [], + text: 'Scheduling regular digital detox periods throughout your day can also be beneficial. For example, you might decide to unplug for an hour during lunch, or turn off all devices an hour before bed. This not only gives your eyes and mind a break from screen time but also encourages you to engage in other activities, such as reading, exercising, or spending time with loved ones.', + _key: '5725b902964b0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '6a96b9c0eed3', + children: [ + { + _type: 'span', + marks: [], + text: 'Many devices and apps now offer “Do Not Disturb” or “Focus” modes, which can be scheduled to activate automatically during certain hours. These features block notifications and limit distractions, helping you stay focused during work hours and disconnect during personal time. Additionally, some apps track your screen time and provide insights into your digital habits, allowing you to make more informed decisions about when and how to unplug.', + _key: '04f43873b1b20', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'e8e4cb7d6703', + children: [ + { + _type: 'span', + marks: [], + text: 'It’s also important to communicate your digital detox periods with colleagues and set expectations about your availability. Letting your team know that you won’t be reachable outside of certain hours not only respects your boundaries but also encourages a healthier work culture overall.', + _key: '7b36e4d57a1b0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'ce42fc6eb087', + children: [ + { + _type: 'span', + marks: [], + text: 'Balancing Work and Personal Communication', + _key: '205e4e44c9b70', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '5a4a297ae560', + children: [ + { + _type: 'span', + marks: [], + text: 'In a remote work environment, communication is key to staying connected and productive. However, the constant influx of emails, messages, and notifications can quickly become overwhelming, making it difficult to maintain a work-life balance. To manage this, it’s important to strike a balance between work and personal communication.', + _key: '95a2824d7c410', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1672e23df08d', + children: [ + { + _type: 'span', + marks: [], + text: 'One strategy is to separate work and personal communication channels. Use different email addresses or messaging apps for work-related and personal communications. This allows you to check and respond to work messages during work hours and personal messages during your personal time, reducing the temptation to mix the two.', + _key: '2e6d612a4b330', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1eff34202d3a', + children: [ + { + _type: 'span', + marks: [], + text: 'Managing your email inbox effectively can also help reduce stress. Consider using filters or labels to prioritize important messages and archive or delete unnecessary ones. Setting specific times to check and respond to emails, rather than constantly monitoring your inbox, can help you stay focused on your tasks without being distracted by incoming messages.', + _key: '5eb920d31ea90', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '180181fd94f4', + children: [ + { + _type: 'span', + marks: [], + text: 'For messaging apps like Slack or Teams, it’s important to set boundaries around your availability. Turn off notifications during non-work hours or when you need to focus on deep work. Many of these platforms allow you to set a status indicating when you’re available or offline, which can help manage expectations with colleagues.', + _key: 'd9c2304c8e320', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f3df6ced0ef9', + children: [ + { + _type: 'span', + marks: [], + text: 'Finally, be mindful of your communication habits. Avoid sending work-related messages late at night or on weekends, and respect others’ boundaries by not expecting immediate responses outside of regular work hours. By fostering a culture of respect and understanding, you can help create a more balanced and healthy work environment for yourself and your team.', + _key: '918b48e80c5a0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '001953fe0f6a', + children: [ + { + _type: 'span', + marks: [], + text: 'Leveraging Technology for Well-Being', + _key: 'ceee7545dd360', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: 'fb4a6c853685', + children: [ + { + _type: 'span', + marks: [], + text: 'While technology is often associated with work, it can also be a powerful tool for enhancing your well-being. There are numerous apps and platforms designed to support mental and physical health, which can be particularly beneficial in a remote work environment.', + _key: '98be43681c350', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1ce9b6730721', + children: [ + { + _type: 'span', + marks: [], + text: 'Fitness apps, such as Fitbit or MyFitnessPal, can help you stay active by tracking your workouts, steps, and overall physical activity. Many of these apps offer guided workouts or challenges that can be done at home, making it easier to incorporate exercise into your daily routine.', + _key: '8bb00bfca6110', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '97816b71dfa4', + children: [ + { + _type: 'span', + marks: [], + text: 'For mental health, apps like Headspace or Calm offer guided meditation, mindfulness exercises, and sleep aids that can help you manage stress and maintain a positive mindset. These tools are particularly useful for taking short mental breaks throughout the day or winding down before bed.', + _key: 'dbd30cdd2b970', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '9ffa9bd083ca', + children: [ + { + _type: 'span', + marks: [], + text: 'Additionally, consider using apps that promote healthy habits, such as water intake reminders, posture checkers, or meal planning tools. These small, consistent habits can have a significant impact on your overall well-being, helping you stay healthy and energized in your remote work environment.', + _key: 'f2058b65bf410', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'ae4225d556a1', + children: [ + { + _type: 'span', + marks: [], + text: 'Finally, virtual communities and social networks can provide valuable support and connection, especially if you’re feeling isolated. Platforms like Meetup or Facebook Groups offer opportunities to connect with like-minded individuals who share your interests, whether it’s for professional networking or personal hobbies.', + _key: '1a1b92e32cb80', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1d43a0508471', + children: [ + { + _type: 'span', + marks: [], + text: 'By leveraging technology thoughtfully, you can enhance both your productivity and your well-being, creating a more balanced and fulfilling remote work experience. In the next and final section, we’ll explore long-term strategies for sustaining this balance and adapting to the ongoing evolution of remote work.', + _key: '56e0f56eae340', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '75aafbce9c22', + children: [ + { + _type: 'span', + marks: [], + text: 'Long-Term Strategies for Sustaining Balance', + _key: '9cb9a0b866010', + }, + ], + markDefs: [], + _type: 'block', + style: 'h2', + }, + { + _key: '79c9d7e82245', + children: [ + { + _type: 'span', + marks: [], + text: 'Continuous Self-Assessment', + _key: 'e144be4f04ec0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: 'e1494005eef7', + children: [ + { + _type: 'span', + marks: [], + text: 'Achieving and maintaining a healthy work-life balance is not a one-time task but an ongoing process that requires regular self-assessment and adjustment. As your work responsibilities, personal life, and external circumstances evolve, so too should your approach to balancing them. Regular self-assessment helps you stay attuned to your needs and ensures that you are making the necessary adjustments to maintain harmony between work and life.', + _key: '62ac2b8d77a50', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '6002ad6ad492', + children: [ + { + _type: 'span', + marks: [], + text: 'Start by setting aside time each week or month to reflect on how well you are managing your work-life balance. Consider questions such as: Are you consistently working beyond your set hours? Do you feel stressed or overwhelmed more often than not? Are you neglecting personal activities or relationships that are important to you? Reflecting on these questions can help you identify areas where you may need to make changes.', + _key: '6a3b3dfd43900', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'eeb85d385867', + children: [ + { + _type: 'span', + marks: [], + text: 'Journaling can be a helpful tool for tracking your thoughts and feelings over time. By noting down your experiences and reflecting on them regularly, you can gain insights into patterns and triggers that affect your work-life balance. This awareness allows you to make more informed decisions about how to adjust your routines and boundaries.', + _key: '44518009274c0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '887ef5f3b5fe', + children: [ + { + _type: 'span', + marks: [], + text: 'Another useful tool for self-assessment is a work-life balance audit. This involves mapping out how you spend your time each day or week, categorizing activities into work, personal, and leisure time. By visually seeing how your time is distributed, you can identify imbalances and areas where you may need to reallocate your time and energy.', + _key: 'b7d1d5c4623f0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'bcc069b39695', + children: [ + { + _type: 'span', + marks: [], + text: 'Finally, don’t be afraid to seek feedback from others. Talk to colleagues, friends, or family members about how they perceive your work-life balance. Sometimes, an outside perspective can reveal blind spots or offer valuable suggestions for improvement.', + _key: '025af34c707f0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'f6ce36cb6ccf', + children: [ + { + _type: 'span', + marks: [], + text: 'Developing Resilience and Adaptability', + _key: '2f778d925f1e0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '9409d3059d92', + children: [ + { + _type: 'span', + marks: [], + text: 'The ability to maintain work-life balance in the long term depends heavily on your resilience and adaptability. Remote work environments can be unpredictable, with changing demands, evolving technologies, and shifting personal circumstances. Developing resilience helps you manage stress and bounce back from setbacks, while adaptability allows you to adjust your strategies as needed.', + _key: '1e149bf5f2ac0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'dd61a61fbe34', + children: [ + { + _type: 'span', + marks: [], + text: 'Resilience can be cultivated through a combination of mindset and practice. Embracing a growth mindset—the belief that challenges are opportunities for learning and development—can help you view setbacks as temporary and manageable. When faced with difficulties, remind yourself of past challenges you’ve overcome and the strengths you’ve developed along the way.', + _key: '4681ec6f88d60', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1f78b3f6f2eb', + children: [ + { + _type: 'span', + marks: [], + text: 'Building resilience also involves taking care of your physical and mental health. Regular exercise, sufficient sleep, healthy eating, and mindfulness practices all contribute to a strong foundation of well-being that supports resilience. Additionally, maintaining a support network of friends, family, and colleagues provides emotional resources you can draw on during tough times.', + _key: '1f877bcbb8150', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '7132ed45d3cb', + children: [ + { + _type: 'span', + marks: [], + text: 'Adaptability, on the other hand, involves being open to change and willing to experiment with new approaches. In a remote work environment, this might mean trying out different routines, tools, or communication methods to see what works best for you. It’s important to stay flexible and recognize that what worked in the past may not always be effective in the future.', + _key: '4b723a75ab2f0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'a6cf6b5a7b48', + children: [ + { + _type: 'span', + marks: [], + text: 'To develop adaptability, practice viewing change as a normal part of life rather than something to be feared or resisted. When faced with a new challenge or change in your work environment, approach it with curiosity and a willingness to learn. Ask yourself what you can gain from the new situation and how you can adjust your strategies to make the most of it. This mindset not only helps you cope with change more effectively but also allows you to thrive in a dynamic work environment.', + _key: '22141641ed810', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '41748863be67', + children: [ + { + _type: 'span', + marks: [], + text: 'One way to build adaptability is to regularly review and update your work habits and routines. For instance, if you find that a particular productivity tool or routine is no longer serving you well, don’t hesitate to explore alternatives. Experiment with different approaches to managing your time, tasks, and communication, and be open to integrating new technologies or methods that can enhance your work-life balance.', + _key: 'ce5e38f838e90', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '3d7e12a07cd1', + children: [ + { + _type: 'span', + marks: [], + text: 'Another key aspect of adaptability is being proactive in seeking out opportunities for continuous learning and skill development. The remote work landscape is constantly evolving, with new tools, platforms, and practices emerging all the time. By staying curious and committed to personal and professional growth, you can remain agile and better equipped to handle the changes that come your way.', + _key: 'f3136c731ffb0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '8b7ace17a34f', + children: [ + { + _type: 'span', + marks: [], + text: 'Involving Family and Loved Ones', + _key: '0a554d857b120', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '783f796730b5', + children: [ + { + _type: 'span', + marks: [], + text: 'Maintaining a healthy work-life balance isn’t just about managing your own time and energy; it also involves the people around you, particularly family and loved ones. Their support and understanding can play a crucial role in helping you achieve and sustain balance, especially in a remote work environment where the boundaries between work and home are often blurred.', + _key: '27acb5a08c820', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'def5ef198b32', + children: [ + { + _type: 'span', + marks: [], + text: 'Communication is key to involving your family in your work-life balance efforts. Make sure they understand your work schedule, including when you are available and when you need uninterrupted time to focus. This helps prevent misunderstandings and ensures that everyone’s needs are respected. For instance, you might have a conversation with your partner or children about the importance of quiet time during certain hours, or explain why it’s important for you to have a dedicated workspace.', + _key: '695a8bda80e70', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '8930d0aaac2f', + children: [ + { + _type: 'span', + marks: [], + text: 'It’s also important to set aside quality time for family and loved ones, just as you would for work tasks. Schedule regular activities that you can enjoy together, whether it’s having dinner as a family, going for a walk, or engaging in a shared hobby. These moments not only strengthen your relationships but also provide a necessary break from work, helping you recharge and maintain perspective.', + _key: '3901fd5a5b3d0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '8c518eb813e2', + children: [ + { + _type: 'span', + marks: [], + text: 'Involving your family in your work-life balance also means being open to feedback. They can often offer valuable insights into how your work habits are impacting your personal life and may have suggestions for how you can better manage your time and energy. Listening to their perspectives and making adjustments as needed can lead to a more harmonious balance between your professional and personal responsibilities.', + _key: '1d3bd0ce56030', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'c9772cb127d0', + children: [ + { + _type: 'span', + marks: [], + text: 'Finally, recognize that your work-life balance needs may change over time, and so too will the needs of your family. Stay flexible and willing to renegotiate boundaries and routines as circumstances evolve. This collaborative approach ensures that everyone’s well-being is prioritized and that you can continue to thrive in both your professional and personal life.', + _key: '222c3ae1ba7a0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '468d64becf07', + children: [ + { + _type: 'span', + marks: [], + text: 'Embracing the Journey of Balance', + _key: 'bfc28d1312280', + }, + ], + markDefs: [], + _type: 'block', + style: 'h3', + }, + { + _key: '97e69fa9ff6d', + children: [ + { + _type: 'span', + marks: [], + text: 'Achieving work-life balance in the remote work era is not a destination but an ongoing journey. It requires regular reflection, a willingness to adapt, and the support of those around you. As you navigate the challenges and opportunities of remote work, remember that balance is a dynamic process, one that may require constant tweaking and adjustment as your life and work evolve.', + _key: '0fb12a80a0730', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'c918d30c0e39', + children: [ + { + _type: 'span', + marks: [], + text: 'The strategies discussed in this article—setting boundaries, prioritizing mental and physical well-being, leveraging technology wisely, and involving family and loved ones—are all tools to help you maintain a sustainable work-life balance. However, the most important factor in achieving balance is your mindset. Approach this journey with patience, self-compassion, and a commitment to continuous improvement. Understand that there will be times when the scales tip more toward work or life, and that’s okay. What matters is your ability to recognize when adjustments are needed and to make those changes with intention.', + _key: '5ed8aacbe7990', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '9e2bb25ed89e', + children: [ + { + _type: 'span', + marks: [], + text: 'In the long run, the effort you put into maintaining a healthy work-life balance will pay off in the form of increased well-being, greater productivity, and deeper satisfaction in both your professional and personal life. By staying mindful of your needs, being adaptable in the face of change, and fostering strong relationships with those around you, you can create a balanced and fulfilling remote work experience that supports your overall quality of life.', + _key: 'b70196cba4a50', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '7d7325c71ed0', + children: [ + { + _type: 'span', + marks: [], + text: 'As the future of work continues to evolve, so too will the ways in which we manage and balance our responsibilities. By embracing this journey with openness and resilience, you can not only survive but thrive in the ever-changing landscape of remote work.', + _key: '395109d2bc4d0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '93688f883d5a', + children: [ + { + _type: 'span', + marks: [], + text: 'Conclusion', + _key: 'c6641e4146eb0', + }, + ], + markDefs: [], + _type: 'block', + style: 'h2', + }, + { + _key: '0000ec97e811', + children: [ + { + _type: 'span', + marks: [], + text: "The transition to remote work has undoubtedly transformed the way we live and work, offering unparalleled flexibility and autonomy. However, with this freedom comes the challenge of maintaining a healthy work-life balance—a task that requires ongoing effort, mindfulness, and adaptability. As we've explored throughout this article, achieving balance in a remote work environment involves more than just managing your time; it requires a holistic approach that prioritizes your mental, physical, and emotional well-being.", + _key: 'cc2423ab91000', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '1d3c39b9cdc2', + children: [ + { + _type: 'span', + marks: [], + text: 'Setting clear boundaries, both physically and digitally, is essential for creating a structured workday that allows you to focus on your tasks while also giving you the space to unwind and recharge. By designing a dedicated workspace, establishing a routine, and setting up digital detox periods, you can create a clearer separation between your professional and personal life, reducing the risk of burnout and enhancing your overall productivity.', + _key: '11d4e89b11830', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '9b119a47fd86', + children: [ + { + _type: 'span', + marks: [], + text: 'Prioritizing your well-being is equally important. Regular breaks, physical activity, and mindfulness practices can help you manage stress and maintain your energy levels throughout the day. Additionally, staying socially connected, whether through virtual team-building activities or personal relationships, provides the emotional support needed to navigate the isolation that can sometimes accompany remote work.', + _key: '02efae8e92b70', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '69f65f151089', + children: [ + { + _type: 'span', + marks: [], + text: 'Technology, while a powerful enabler of remote work, must be used wisely. By leveraging productivity tools, setting digital boundaries, and embracing digital detox periods, you can prevent technology from overwhelming your life and instead use it to support your work-life balance.', + _key: '0e0a775a1e630', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'e8986d8e590d', + children: [ + { + _type: 'span', + marks: [], + text: 'Ultimately, the journey toward achieving work-life balance is ongoing and requires continuous self-assessment and adaptation. Your needs, circumstances, and priorities will evolve, and so too should your strategies for managing them. Involving family and loved ones in this process ensures that your work-life balance supports not only your well-being but also the well-being of those around you.', + _key: 'c23db5ad01bf0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: 'acfa6db00d1e', + children: [ + { + _type: 'span', + marks: [], + text: 'As the future of work continues to shift, the skills and habits you develop now will serve as a foundation for long-term success and fulfillment. By embracing the journey of balance with resilience and a growth mindset, you can thrive in the remote work era, creating a life that is both professionally rewarding and personally enriching.', + _key: 'b1d2a96b6c510', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + { + _key: '005060a31e4f', + children: [ + { + _type: 'span', + marks: [], + text: 'Remember, achieving work-life balance is not about perfection but about making intentional choices that align with your values and well-being. As you continue to navigate the complexities of remote work, stay committed to your own health and happiness, and know that the effort you invest in balancing your life will yield lasting benefits for both your career and your overall quality of life.', + _key: '3e1141a95f3a0', + }, + ], + markDefs: [], + _type: 'block', + style: 'normal', + }, + ], + categories: [ + { + _key: '1ac46ad21f72', + _ref: '9cb3d2c9-e4bd-46f0-8a8a-f6018cc293bc', + _type: 'reference', + }, + { + _key: '7c6ba89fa465', + _ref: '643e60de-db8a-49d5-b9e1-ed7fbec30885', + _type: 'reference', + }, + ], + description: + 'This article explores the profound transformation of the workplace as remote work becomes a permanent fixture in many industries. From the rise of hybrid work models to the integration of AI and advanced technologies, it delves into how businesses and employees are adapting to this new normal. The article also addresses the challenges of maintaining company culture, ensuring cybersecurity, and balancing work-life dynamics in a remote environment. As the future of work unfolds, it offers insights and strategies for navigating the evolving landscape, emphasizing the importance of flexibility, innovation, and global interconnectedness.', + excerpt: [ + { + _key: 'cbed85515648', + _type: 'block', + children: [ + { + _key: 'a6daeb11f85a0', + _type: 'span', + marks: [], + text: 'The ', + }, + { + _key: '80b0f47bad9b', + _type: 'span', + marks: ['strong'], + text: 'future of work', + }, + { + _key: '007234a9076a', + _type: 'span', + marks: [], + text: ' is being ', + }, + { + _key: 'a50fc696a47a', + _type: 'span', + marks: ['em'], + text: 'reshaped', + }, + { + _key: 'fdce3ccf8abb', + _type: 'span', + marks: [], + text: ' before our eyes, with remote work at the forefront of this transformation. As businesses and employees adapt to new technologies and flexible work models, the traditional office is giving way to a more dynamic and interconnected world. This article delves into the ', + }, + { + _key: '04d3100817c2', + _type: 'span', + marks: ['em'], + text: 'benefits', + }, + { + _key: 'db604d54b9d6', + _type: 'span', + marks: [], + text: ' and ', + }, + { + _key: '8dd4aaa674da', + _type: 'span', + marks: ['em'], + text: 'challenges', + }, + { + _key: 'b738882aa398', + _type: 'span', + marks: [], + text: ' of remote work, explores the role of ', + }, + { + _key: 'e5336ae06f26', + _type: 'span', + marks: ['strong'], + text: 'AI', + }, + { + _key: '0ec96df14df9', + _type: 'span', + marks: [], + text: ' and ', + }, + { + _key: '5f83f5e4cfab', + _type: 'span', + marks: ['strong'], + text: 'cybersecurity', + }, + { + _key: '1120c0ab715d', + _type: 'span', + marks: [], + text: ', and considers the impact of a ', + }, + { + _key: '9784540a7fb7', + _type: 'span', + marks: ['strong'], + text: 'global workforce', + }, + { + _key: '9b594adcf942', + _type: 'span', + marks: [], + text: '. Whether you’re navigating this shift as an employer or employee, understanding these trends is essential for thriving in the evolving landscape of work.', + }, + ], + markDefs: [], + style: 'normal', + }, + ], + seo: { + _type: 'seo', + excludeFromSearchEngines: false, + metaDescription: + 'Explore the future of remote work and its impact on the workplace. Discover how technology, hybrid models, and global connections are redefining how we work, collaborate, and innovate in the digital age.', + metaTitle: 'The Future of Remote Work: How Technology is Shaping the New Workplace', + slug: { + _type: 'slug', + current: 'future-of-remote-work-technology-impact', + }, + }, + tags: [ + 'Remote Work', + 'Hybrid Work', + 'Future of Work', + 'Workplace Innovation', + 'AI in the Workplace', + 'Digital Transformation', + 'Global Workforce', + 'Work-Life Balance', + 'Remote Work Tools', + 'Cybersecurity', + ], + title: 'The Future of Remote Work: How Technology is Shaping the New Normal', + author: {_type: 'reference', _ref: 'reference-author'}, +} + +export default article diff --git a/perf/efps/tests/article/references.ts b/perf/efps/tests/article/references.ts new file mode 100644 index 00000000000..e8b87bcd0d1 --- /dev/null +++ b/perf/efps/tests/article/references.ts @@ -0,0 +1,32 @@ +import {type Author, type Category} from './sanity.types' + +export const author: Omit = { + _id: 'reference-author', + _type: 'author', + name: 'Sarah Mitchell', + bio: [ + { + _key: 'c4a812b3f609', + _type: 'block', + children: [ + { + _key: 'a9f3d67b8c12', + _type: 'span', + text: 'Sarah Mitchell is a seasoned writer and remote work advocate with over a decade of experience in digital communication and workplace wellness. With a passion for helping individuals and businesses thrive in the modern work environment, Sarah has written extensively on topics related to work-life balance, productivity, and mental health. When she’s not writing, Sarah enjoys practicing yoga, exploring the great outdoors, and experimenting with new recipes in her home kitchen. She believes that the key to a fulfilling career is finding harmony between work and life, and she’s dedicated to sharing practical strategies to help others achieve that balance.', + }, + ], + }, + ], +} +export const categories: Omit[] = [ + { + _id: 'category-0', + _type: 'category', + name: 'Future of Work', + }, + { + _id: 'category-1', + _type: 'category', + name: 'Mental Health', + }, +] diff --git a/perf/efps/tests/article/sanity.config.ts b/perf/efps/tests/article/sanity.config.ts new file mode 100644 index 00000000000..802fb052a75 --- /dev/null +++ b/perf/efps/tests/article/sanity.config.ts @@ -0,0 +1,94 @@ +import {defineConfig, defineField, defineType} from 'sanity' + +export default defineConfig({ + projectId: import.meta.env.VITE_PERF_EFPS_PROJECT_ID, + dataset: import.meta.env.VITE_PERF_EFPS_DATASET, + schema: { + types: [ + defineType({ + name: 'blockContent', + type: 'array', + of: [{type: 'block'}, {type: 'image'}, {type: 'columns'}, {type: 'hero'}], + }), + defineType({ + name: 'columns', + type: 'object', + fields: [ + defineField({ + name: 'columns', + type: 'array', + of: [ + { + type: 'object', + fields: [ + defineField({name: 'title', type: 'string'}), + defineField({name: 'content', type: 'blockContent'}), + ], + }, + ], + }), + ], + }), + defineType({ + name: 'hero', + type: 'object', + fields: [ + defineField({name: 'image', type: 'image'}), + defineField({name: 'body', type: 'blockContent'}), + ], + }), + defineType({ + name: 'seo', + type: 'object', + fields: [ + defineField({ + name: 'slug', + type: 'slug', + description: + 'Defines the part of the URL that uniquely defines this entity from others.', + validation: (rule) => rule.required(), + }), + defineField({name: 'metaTitle', type: 'string'}), + defineField({name: 'metaDescription', type: 'text'}), + defineField({name: 'excludeFromSearchEngines', type: 'boolean'}), + ], + }), + defineType({ + name: 'article', + type: 'document', + fields: [ + defineField({name: 'title', type: 'string'}), + defineField({name: 'description', type: 'string'}), + defineField({name: 'mainImage', type: 'image'}), + defineField({name: 'author', type: 'reference', to: [{type: 'author'}]}), + defineField({name: 'body', type: 'blockContent'}), + defineField({name: 'excerpt', type: 'blockContent'}), + defineField({ + name: 'categories', + type: 'array', + of: [{type: 'reference', to: [{type: 'category'}]}], + }), + defineField({name: 'tags', type: 'array', of: [{type: 'string'}]}), + defineField({name: 'seo', title: 'SEO Fields', type: 'seo'}), + ], + }), + defineType({ + name: 'author', + type: 'document', + fields: [ + defineField({name: 'name', type: 'string'}), + defineField({name: 'profilePicture', type: 'image'}), + defineField({name: 'bio', type: 'blockContent'}), + ], + }), + defineType({ + name: 'category', + type: 'document', + fields: [ + defineField({name: 'name', type: 'string'}), + defineField({name: 'image', type: 'image'}), + ], + }), + ], + }, +}) diff --git a/perf/efps/tests/article/sanity.types.ts b/perf/efps/tests/article/sanity.types.ts new file mode 100644 index 00000000000..cb25ee66857 --- /dev/null +++ b/perf/efps/tests/article/sanity.types.ts @@ -0,0 +1,489 @@ +/** + * --------------------------------------------------------------------------------- + * This file has been generated by Sanity TypeGen. + * Command: `sanity typegen generate` + * + * Any modifications made directly to this file will be overwritten the next time + * the TypeScript definitions are generated. Please make changes to the Sanity + * schema definitions and/or GROQ queries if you need to update these types. + * + * For more information on how to use Sanity TypeGen, visit the official documentation: + * https://www.sanity.io/docs/sanity-typegen + * --------------------------------------------------------------------------------- + */ + +// Source: schema.json +export type SanityImagePaletteSwatch = { + _type: 'sanity.imagePaletteSwatch' + background?: string + foreground?: string + population?: number + title?: string +} + +export type SanityImagePalette = { + _type: 'sanity.imagePalette' + darkMuted?: SanityImagePaletteSwatch + lightVibrant?: SanityImagePaletteSwatch + darkVibrant?: SanityImagePaletteSwatch + vibrant?: SanityImagePaletteSwatch + dominant?: SanityImagePaletteSwatch + lightMuted?: SanityImagePaletteSwatch + muted?: SanityImagePaletteSwatch +} + +export type SanityImageDimensions = { + _type: 'sanity.imageDimensions' + height?: number + width?: number + aspectRatio?: number +} + +export type SanityFileAsset = { + _id: string + _type: 'sanity.fileAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + source?: SanityAssetSourceData +} + +export type Geopoint = { + _type: 'geopoint' + lat?: number + lng?: number + alt?: number +} + +export type Category = { + _id: string + _type: 'category' + _createdAt: string + _updatedAt: string + _rev: string + name?: string + image?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + } +} + +export type Article = { + _id: string + _type: 'article' + _createdAt: string + _updatedAt: string + _rev: string + title?: string + description?: string + mainImage?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + } + author?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'author' + } + body?: Array< + | { + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + } + | { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string + } + | ({ + _key: string + } & Columns) + | ({ + _key: string + } & Hero) + > + excerpt?: Array< + | { + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + } + | { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string + } + | ({ + _key: string + } & Columns) + | ({ + _key: string + } & Hero) + > + categories?: Array<{ + _ref: string + _type: 'reference' + _weak?: boolean + _key: string + [internalGroqTypeReferenceTo]?: 'category' + }> + tags?: Array + seo?: Seo +} + +export type Author = { + _id: string + _type: 'author' + _createdAt: string + _updatedAt: string + _rev: string + name?: string + profilePicture?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + } + bio?: Array< + | { + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + } + | { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string + } + | ({ + _key: string + } & Columns) + | ({ + _key: string + } & Hero) + > +} + +export type Seo = { + _type: 'seo' + slug?: Slug + metaTitle?: string + metaDescription?: string + excludeFromSearchEngines?: boolean +} + +export type Slug = { + _type: 'slug' + current?: string + source?: string +} + +export type Hero = { + _type: 'hero' + image?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + } + body?: Array< + | { + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + } + | { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string + } + | ({ + _key: string + } & Columns) + | ({ + _key: string + } & Hero) + > +} + +export type Columns = { + _type: 'columns' + columns?: Array<{ + title?: string + content?: Array< + | { + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + } + | { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string + } + | ({ + _key: string + } & Columns) + | ({ + _key: string + } & Hero) + > + _key: string + }> +} + +export type BlockContent = Array< + | { + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + } + | { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string + } + | ({ + _key: string + } & Columns) + | ({ + _key: string + } & Hero) +> + +export type SanityImageCrop = { + _type: 'sanity.imageCrop' + top?: number + bottom?: number + left?: number + right?: number +} + +export type SanityImageHotspot = { + _type: 'sanity.imageHotspot' + x?: number + y?: number + height?: number + width?: number +} + +export type SanityImageAsset = { + _id: string + _type: 'sanity.imageAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + metadata?: SanityImageMetadata + source?: SanityAssetSourceData +} + +export type SanityAssetSourceData = { + _type: 'sanity.assetSourceData' + name?: string + id?: string + url?: string +} + +export type SanityImageMetadata = { + _type: 'sanity.imageMetadata' + location?: Geopoint + dimensions?: SanityImageDimensions + palette?: SanityImagePalette + lqip?: string + blurHash?: string + hasAlpha?: boolean + isOpaque?: boolean +} + +export type AllSanitySchemaTypes = + | SanityImagePaletteSwatch + | SanityImagePalette + | SanityImageDimensions + | SanityFileAsset + | Geopoint + | Category + | Article + | Author + | Seo + | Slug + | Hero + | Columns + | BlockContent + | SanityImageCrop + | SanityImageHotspot + | SanityImageAsset + | SanityAssetSourceData + | SanityImageMetadata +export declare const internalGroqTypeReferenceTo: unique symbol diff --git a/perf/efps/tests/recipe/assets/pizza.webp b/perf/efps/tests/recipe/assets/pizza.webp new file mode 100644 index 00000000000..2b171fd241a Binary files /dev/null and b/perf/efps/tests/recipe/assets/pizza.webp differ diff --git a/perf/efps/tests/recipe/recipe.ts b/perf/efps/tests/recipe/recipe.ts new file mode 100644 index 00000000000..3c9f8e4c230 --- /dev/null +++ b/perf/efps/tests/recipe/recipe.ts @@ -0,0 +1,181 @@ +import fs from 'node:fs' +import path from 'node:path' +import {fileURLToPath} from 'node:url' + +import {measureFpsForInput} from '../../helpers/measureFpsForInput' +import {measureFpsForPte} from '../../helpers/measureFpsForPte' +import {defineEfpsTest} from '../../types' +import {type Category, type Recipe} from './sanity.types' + +const dirname = path.dirname(fileURLToPath(import.meta.url)) + +const generateKey = () => { + const rng = () => + Math.floor(Math.random() * 255) + .toString(16) + .padStart(2, '0') + + return Array.from({length: 6}, rng).join('') +} + +export default defineEfpsTest({ + name: 'recipe', + configPath: await import.meta.resolve?.('./sanity.config.ts'), + document: async ({client}) => { + const imageAsset = await client.assets.upload( + 'image', + fs.createReadStream(path.join(dirname, 'assets', 'pizza.webp')), + { + source: {id: 'pizza', name: 'recipe-test'}, + }, + ) + + const category: Omit = { + _id: 'example-recipe-category', + _type: 'category', + name: 'Italian', + description: + 'Italian pizzas are known for their simple, high-quality ingredients and traditional preparation methods. The Margherita, with its fresh tomato sauce, mozzarella, and basil on a thin, crispy crust, is a classic representation of this beloved cuisine.', + } + + await client.createOrReplace(category) + + const recipe: Omit = { + _type: 'recipe', + name: 'Classic Margherita Pizza', + slug: {_type: 'slug', current: 'classic-margherita-pizza'}, + description: 'A simple yet delicious Neapolitan pizza with fresh ingredients.', + difficulty: 'easy', + image: { + _type: 'image', + asset: {_type: 'reference', _ref: imageAsset._id}, + }, + prepTime: 20, + cookTime: 10, + servings: 2, + ingredients: [ + {_key: generateKey(), _type: 'ingredient', item: 'Pizza dough', amount: 250, unit: 'g'}, + {_key: generateKey(), _type: 'ingredient', item: 'Tomato sauce', amount: 80, unit: 'ml'}, + { + _key: generateKey(), + _type: 'ingredient', + item: 'Fresh mozzarella', + amount: 125, + unit: 'g', + }, + { + _key: generateKey(), + _type: 'ingredient', + item: 'Fresh basil leaves', + amount: 5, + unit: 'leaves', + }, + { + _key: generateKey(), + _type: 'ingredient', + item: 'Extra virgin olive oil', + amount: 1, + unit: 'tbsp', + }, + ], + category: { + _ref: category._id, + _type: 'reference', + }, + instructions: [ + { + _type: 'block', + _key: generateKey(), + children: [ + { + _type: 'span', + _key: generateKey(), + text: 'Preheat your oven to 450°F (230°C) with a pizza stone or baking sheet inside.', + }, + ], + style: 'normal', + }, + { + _type: 'block', + _key: generateKey(), + children: [ + { + _type: 'span', + _key: generateKey(), + text: 'Roll out the pizza dough on a floured surface to about 12 inches in diameter.', + }, + ], + style: 'normal', + }, + { + _type: 'block', + _key: generateKey(), + children: [ + { + _type: 'span', + _key: generateKey(), + text: 'Spread the tomato sauce evenly over the dough, leaving a small border around the edges.', + }, + ], + style: 'normal', + }, + { + _type: 'block', + _key: generateKey(), + children: [ + { + _type: 'span', + _key: generateKey(), + text: 'Tear the mozzarella into small pieces and distribute evenly over the sauce.', + }, + ], + style: 'normal', + }, + { + _type: 'block', + _key: generateKey(), + children: [ + { + _type: 'span', + _key: generateKey(), + text: 'Carefully transfer the pizza to the preheated stone or baking sheet and bake for 8-10 minutes, or until the crust is golden and the cheese is bubbly.', + }, + ], + style: 'normal', + }, + { + _type: 'block', + _key: generateKey(), + children: [ + { + _type: 'span', + _key: generateKey(), + text: 'Remove from the oven, drizzle with olive oil, and top with fresh basil leaves. Slice and serve immediately.', + }, + ], + style: 'normal', + }, + ], + } + + return recipe + }, + run: async ({page}) => { + return [ + { + label: 'name', + ...(await measureFpsForInput( + page.locator('[data-testid="field-name"] input[type="text"]'), + )), + }, + { + label: 'description', + ...(await measureFpsForInput(page.locator('[data-testid="field-description"] textarea'))), + }, + { + label: 'instructions', + ...(await measureFpsForPte(page.locator('[data-testid="field-instructions"]'))), + }, + ] + }, +}) diff --git a/perf/efps/tests/recipe/sanity.config.ts b/perf/efps/tests/recipe/sanity.config.ts new file mode 100644 index 00000000000..fb31318f872 --- /dev/null +++ b/perf/efps/tests/recipe/sanity.config.ts @@ -0,0 +1,57 @@ +import {defineConfig, defineField, defineType} from 'sanity' + +export default defineConfig({ + projectId: import.meta.env.VITE_PERF_EFPS_PROJECT_ID, + dataset: import.meta.env.VITE_PERF_EFPS_DATASET, + schema: { + types: [ + defineType({ + name: 'recipe', + type: 'document', + title: 'Recipe', + fields: [ + defineField({name: 'name', type: 'string'}), + defineField({name: 'slug', type: 'slug', options: {source: 'name'}}), + defineField({name: 'description', type: 'text'}), + defineField({name: 'image', type: 'image'}), + defineField({name: 'prepTime', type: 'number', title: 'Preparation Time (minutes)'}), + defineField({name: 'cookTime', type: 'number', title: 'Cooking Time (minutes)'}), + defineField({name: 'servings', type: 'number', title: 'Number of Servings'}), + defineField({name: 'ingredients', type: 'array', of: [{type: 'ingredient'}]}), + defineField({name: 'instructions', type: 'array', of: [{type: 'block'}]}), + defineField({name: 'category', type: 'reference', to: [{type: 'category'}]}), + defineField({ + name: 'difficulty', + type: 'string', + title: 'Difficulty', + options: { + list: [ + {title: 'Easy', value: 'easy'}, + {title: 'Medium', value: 'medium'}, + {title: 'Hard', value: 'hard'}, + ], + }, + }), + ], + }), + defineType({ + name: 'ingredient', + type: 'object', + fields: [ + defineField({name: 'item', type: 'string'}), + defineField({name: 'amount', type: 'number'}), + defineField({name: 'unit', type: 'string'}), + ], + }), + defineType({ + name: 'category', + type: 'document', + title: 'Category', + fields: [ + defineField({name: 'name', type: 'string'}), + defineField({name: 'description', type: 'text'}), + ], + }), + ], + }, +}) diff --git a/perf/efps/tests/recipe/sanity.types.ts b/perf/efps/tests/recipe/sanity.types.ts new file mode 100644 index 00000000000..2b9a1f55bc7 --- /dev/null +++ b/perf/efps/tests/recipe/sanity.types.ts @@ -0,0 +1,221 @@ +/** + * --------------------------------------------------------------------------------- + * This file has been generated by Sanity TypeGen. + * Command: `sanity typegen generate` + * + * Any modifications made directly to this file will be overwritten the next time + * the TypeScript definitions are generated. Please make changes to the Sanity + * schema definitions and/or GROQ queries if you need to update these types. + * + * For more information on how to use Sanity TypeGen, visit the official documentation: + * https://www.sanity.io/docs/sanity-typegen + * --------------------------------------------------------------------------------- + */ + +// Source: schema.json +export type SanityImagePaletteSwatch = { + _type: 'sanity.imagePaletteSwatch' + background?: string + foreground?: string + population?: number + title?: string +} + +export type SanityImagePalette = { + _type: 'sanity.imagePalette' + darkMuted?: SanityImagePaletteSwatch + lightVibrant?: SanityImagePaletteSwatch + darkVibrant?: SanityImagePaletteSwatch + vibrant?: SanityImagePaletteSwatch + dominant?: SanityImagePaletteSwatch + lightMuted?: SanityImagePaletteSwatch + muted?: SanityImagePaletteSwatch +} + +export type SanityImageDimensions = { + _type: 'sanity.imageDimensions' + height?: number + width?: number + aspectRatio?: number +} + +export type SanityFileAsset = { + _id: string + _type: 'sanity.fileAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + source?: SanityAssetSourceData +} + +export type Geopoint = { + _type: 'geopoint' + lat?: number + lng?: number + alt?: number +} + +export type Ingredient = { + _type: 'ingredient' + item?: string + amount?: number + unit?: string +} + +export type Recipe = { + _id: string + _type: 'recipe' + _createdAt: string + _updatedAt: string + _rev: string + name?: string + slug?: Slug + description?: string + image?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + } + prepTime?: number + cookTime?: number + servings?: number + ingredients?: Array< + { + _key: string + } & Ingredient + > + instructions?: Array<{ + children?: Array<{ + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' + listItem?: 'bullet' | 'number' + markDefs?: Array<{ + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + }> + category?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'category' + } + difficulty?: 'easy' | 'medium' | 'hard' +} + +export type Category = { + _id: string + _type: 'category' + _createdAt: string + _updatedAt: string + _rev: string + name?: string + description?: string +} + +export type SanityImageCrop = { + _type: 'sanity.imageCrop' + top?: number + bottom?: number + left?: number + right?: number +} + +export type SanityImageHotspot = { + _type: 'sanity.imageHotspot' + x?: number + y?: number + height?: number + width?: number +} + +export type SanityImageAsset = { + _id: string + _type: 'sanity.imageAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + metadata?: SanityImageMetadata + source?: SanityAssetSourceData +} + +export type SanityAssetSourceData = { + _type: 'sanity.assetSourceData' + name?: string + id?: string + url?: string +} + +export type SanityImageMetadata = { + _type: 'sanity.imageMetadata' + location?: Geopoint + dimensions?: SanityImageDimensions + palette?: SanityImagePalette + lqip?: string + blurHash?: string + hasAlpha?: boolean + isOpaque?: boolean +} + +export type Slug = { + _type: 'slug' + current?: string + source?: string +} + +export type AllSanitySchemaTypes = + | SanityImagePaletteSwatch + | SanityImagePalette + | SanityImageDimensions + | SanityFileAsset + | Geopoint + | Ingredient + | Recipe + | Category + | SanityImageCrop + | SanityImageHotspot + | SanityImageAsset + | SanityAssetSourceData + | SanityImageMetadata + | Slug +export declare const internalGroqTypeReferenceTo: unique symbol diff --git a/perf/efps/tests/singleString/sanity.config.ts b/perf/efps/tests/singleString/sanity.config.ts new file mode 100644 index 00000000000..f12ed398683 --- /dev/null +++ b/perf/efps/tests/singleString/sanity.config.ts @@ -0,0 +1,15 @@ +import {defineConfig, defineField, defineType} from 'sanity' + +export default defineConfig({ + projectId: import.meta.env.VITE_PERF_EFPS_PROJECT_ID, + dataset: import.meta.env.VITE_PERF_EFPS_DATASET, + schema: { + types: [ + defineType({ + name: 'singleString', + type: 'document', + fields: [defineField({name: 'stringField', type: 'string'})], + }), + ], + }, +}) diff --git a/perf/efps/tests/singleString/singleString.ts b/perf/efps/tests/singleString/singleString.ts new file mode 100644 index 00000000000..be6ea933ff4 --- /dev/null +++ b/perf/efps/tests/singleString/singleString.ts @@ -0,0 +1,16 @@ +import path from 'node:path' +import {fileURLToPath} from 'node:url' + +import {measureFpsForInput} from '../../helpers/measureFpsForInput' +import {defineEfpsTest} from '../../types' + +export default defineEfpsTest({ + name: path.basename(fileURLToPath(import.meta.url), path.extname(fileURLToPath(import.meta.url))), + configPath: await import.meta.resolve?.('./sanity.config.ts'), + document: {_type: 'singleString'}, + run: async ({page}) => { + const input = page.locator('[data-testid="field-stringField"] input[type="text"]') + const result = await measureFpsForInput(input) + return result + }, +}) diff --git a/perf/efps/tests/synthetic/assets/file.txt b/perf/efps/tests/synthetic/assets/file.txt new file mode 100644 index 00000000000..c7c7da3c64e --- /dev/null +++ b/perf/efps/tests/synthetic/assets/file.txt @@ -0,0 +1 @@ +hello there diff --git a/perf/efps/tests/synthetic/assets/image.webp b/perf/efps/tests/synthetic/assets/image.webp new file mode 100644 index 00000000000..4fa1ed4c6b6 Binary files /dev/null and b/perf/efps/tests/synthetic/assets/image.webp differ diff --git a/perf/efps/tests/synthetic/sanity.config.ts b/perf/efps/tests/synthetic/sanity.config.ts new file mode 100644 index 00000000000..94996ecfbcf --- /dev/null +++ b/perf/efps/tests/synthetic/sanity.config.ts @@ -0,0 +1,41 @@ +import {defineConfig, defineField, defineType} from 'sanity' + +export default defineConfig({ + projectId: import.meta.env.VITE_PERF_EFPS_PROJECT_ID, + dataset: import.meta.env.VITE_PERF_EFPS_DATASET, + schema: { + types: [ + defineType({ + name: 'synthetic', + type: 'document', + fields: [ + defineField({name: 'title', type: 'string'}), + defineField({name: 'arrayOfObjects', type: 'array', of: [{type: 'syntheticObject'}]}), + defineField({name: 'syntheticObject', type: 'syntheticObject'}), + ], + }), + defineType({ + name: 'syntheticObject', + type: 'object', + fields: [ + defineField({name: 'name', type: 'string'}), + defineField({name: 'image', type: 'image'}), + defineField({name: 'file', type: 'file'}), + defineField({name: 'geopoint', type: 'geopoint'}), + defineField({name: 'number', type: 'number'}), + defineField({name: 'string', type: 'string'}), + defineField({name: 'boolean', type: 'boolean'}), + defineField({name: 'slug', type: 'slug'}), + defineField({name: 'text', type: 'text'}), + defineField({name: 'reference', type: 'reference', to: [{type: 'synthetic'}]}), + defineField({name: 'date', type: 'date'}), + defineField({name: 'datetime', type: 'datetime'}), + defineField({name: 'nestedObject', type: 'syntheticObject'}), + ...Array.from({length: 20}).map((_, index) => + defineField({name: `field${index}`, type: 'string'}), + ), + ], + }), + ], + }, +}) diff --git a/perf/efps/tests/synthetic/sanity.types.ts b/perf/efps/tests/synthetic/sanity.types.ts new file mode 100644 index 00000000000..1a94630fd90 --- /dev/null +++ b/perf/efps/tests/synthetic/sanity.types.ts @@ -0,0 +1,223 @@ +/** + * --------------------------------------------------------------------------------- + * This file has been generated by Sanity TypeGen. + * Command: `sanity typegen generate` + * + * Any modifications made directly to this file will be overwritten the next time + * the TypeScript definitions are generated. Please make changes to the Sanity + * schema definitions and/or GROQ queries if you need to update these types. + * + * For more information on how to use Sanity TypeGen, visit the official documentation: + * https://www.sanity.io/docs/sanity-typegen + * --------------------------------------------------------------------------------- + */ + +// Source: schema.json +export type SanityImagePaletteSwatch = { + _type: 'sanity.imagePaletteSwatch' + background?: string + foreground?: string + population?: number + title?: string +} + +export type SanityImagePalette = { + _type: 'sanity.imagePalette' + darkMuted?: SanityImagePaletteSwatch + lightVibrant?: SanityImagePaletteSwatch + darkVibrant?: SanityImagePaletteSwatch + vibrant?: SanityImagePaletteSwatch + dominant?: SanityImagePaletteSwatch + lightMuted?: SanityImagePaletteSwatch + muted?: SanityImagePaletteSwatch +} + +export type SanityImageDimensions = { + _type: 'sanity.imageDimensions' + height?: number + width?: number + aspectRatio?: number +} + +export type Slug = { + _type: 'slug' + current?: string + source?: string +} + +export type Geopoint = { + _type: 'geopoint' + lat?: number + lng?: number + alt?: number +} + +export type Synthetic = { + _id: string + _type: 'synthetic' + _createdAt: string + _updatedAt: string + _rev: string + title?: string + arrayOfObjects?: Array< + { + _key: string + } & SyntheticObject + > + syntheticObject?: SyntheticObject +} + +export type SyntheticObject = { + _type: 'syntheticObject' + name?: string + image?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' + } + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + } + file?: { + asset?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.fileAsset' + } + _type: 'file' + } + geopoint?: Geopoint + number?: number + string?: string + boolean?: boolean + slug?: Slug + text?: string + reference?: { + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'synthetic' + } + date?: string + datetime?: string + nestedObject?: SyntheticObject + field0?: string + field1?: string + field2?: string + field3?: string + field4?: string + field5?: string + field6?: string + field7?: string + field8?: string + field9?: string + field10?: string + field11?: string + field12?: string + field13?: string + field14?: string + field15?: string + field16?: string + field17?: string + field18?: string + field19?: string +} + +export type SanityFileAsset = { + _id: string + _type: 'sanity.fileAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + source?: SanityAssetSourceData +} + +export type SanityImageCrop = { + _type: 'sanity.imageCrop' + top?: number + bottom?: number + left?: number + right?: number +} + +export type SanityImageHotspot = { + _type: 'sanity.imageHotspot' + x?: number + y?: number + height?: number + width?: number +} + +export type SanityImageAsset = { + _id: string + _type: 'sanity.imageAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + metadata?: SanityImageMetadata + source?: SanityAssetSourceData +} + +export type SanityAssetSourceData = { + _type: 'sanity.assetSourceData' + name?: string + id?: string + url?: string +} + +export type SanityImageMetadata = { + _type: 'sanity.imageMetadata' + location?: Geopoint + dimensions?: SanityImageDimensions + palette?: SanityImagePalette + lqip?: string + blurHash?: string + hasAlpha?: boolean + isOpaque?: boolean +} + +export type AllSanitySchemaTypes = + | SanityImagePaletteSwatch + | SanityImagePalette + | SanityImageDimensions + | Slug + | Geopoint + | Synthetic + | SyntheticObject + | SanityFileAsset + | SanityImageCrop + | SanityImageHotspot + | SanityImageAsset + | SanityAssetSourceData + | SanityImageMetadata +export declare const internalGroqTypeReferenceTo: unique symbol diff --git a/perf/efps/tests/synthetic/synthetic.ts b/perf/efps/tests/synthetic/synthetic.ts new file mode 100644 index 00000000000..2ed731428b8 --- /dev/null +++ b/perf/efps/tests/synthetic/synthetic.ts @@ -0,0 +1,139 @@ +import fs from 'node:fs' +import path from 'node:path' +import {fileURLToPath} from 'node:url' + +import {measureFpsForInput} from '../../helpers/measureFpsForInput' +import {defineEfpsTest} from '../../types' +import {type Synthetic, type SyntheticObject} from './sanity.types' + +const dirname = path.dirname(fileURLToPath(import.meta.url)) + +const generateKey = () => { + const rng = () => + Math.floor(Math.random() * 255) + .toString(16) + .padStart(2, '0') + + return Array.from({length: 6}, rng).join('') +} + +const getRandomWords = () => { + const words = [ + 'whisper', + 'kaleidoscope', + 'tundra', + 'labyrinth', + 'quasar', + 'ember', + 'flux', + 'verdant', + 'obsidian', + 'ripple', + 'zephyr', + 'nebula', + 'lattice', + 'prism', + 'cascade', + 'fable', + 'twilight', + 'echo', + 'thistle', + ] + + // Generate a random number of words to combine (between 2 and 5) + const numberOfWords = Math.floor(Math.random() * 4) + 2 + + // Shuffle the words array and take the first 'numberOfWords' elements + const randomWords = words.sort(() => 0.5 - Math.random()).slice(0, numberOfWords) + + // Join the words with a space in between + return randomWords.join(' ') +} + +export default defineEfpsTest({ + name: 'synthetic', + configPath: await import.meta.resolve?.('./sanity.config.ts'), + document: async ({client}) => { + const imageAsset = await client.assets.upload( + 'image', + fs.createReadStream(path.join(dirname, 'assets', 'image.webp')), + {source: {id: 'image', name: 'synthetic-test'}}, + ) + const fileAsset = await client.assets.upload( + 'file', + fs.createReadStream(path.join(dirname, 'assets', 'file.txt')), + { + source: {id: 'file', name: 'synthetic-test'}, + contentType: 'text/plain', + filename: 'file.txt', + }, + ) + + const reference = await client.createOrReplace({ + _id: 'synthetic-reference', + _type: 'synthetic', + title: 'reference document', + }) + + const generateObject = (): SyntheticObject & {_key: string} => ({ + _key: generateKey(), + _type: 'syntheticObject', + boolean: false, + date: new Date().toLocaleDateString('en-CA'), + datetime: new Date().toISOString(), + image: {_type: 'image', asset: {_ref: imageAsset._id, _type: 'reference'}}, + file: {_type: 'file', asset: {_ref: fileAsset._id, _type: 'reference'}}, + reference: {_ref: reference._id, _type: 'reference'}, + geopoint: {_type: 'geopoint', lat: 41.8781, lng: 87.6298}, + name: 'an object', + number: 5, + string: 'a string', + text: + 'In the stillness of the early morning, when the world is just ' + + 'beginning to stir, there is a quiet magic that blankets everything. ' + + 'The first rays of sunlight pierce through the horizon, casting a ' + + 'golden glow across the landscape. The air is crisp and cool, ' + + 'carrying with it the faint scent of dew-kissed grass and blooming ' + + 'flowers. Birds begin their morning songs, a chorus that welcomes ' + + 'the new day with hope and promise. In these moments, time seems to ' + + 'pause, allowing you to soak in the serenity and beauty of a world ' + + 'waking up, reminding you of the simple yet profound joys of life.', + ...Object.fromEntries( + Array.from({length: 20}).map((_, i) => [`field${i}`, getRandomWords()]), + ), + }) + + const synthetic: Omit = { + _type: 'synthetic', + arrayOfObjects: Array.from({length: 100}, generateObject), + syntheticObject: { + ...generateObject(), + nestedObject: { + ...generateObject(), + nestedObject: { + ...generateObject(), + nestedObject: generateObject(), + }, + }, + }, + } + + return synthetic + }, + run: async ({page}) => { + return [ + { + label: 'title', + ...(await measureFpsForInput( + page.locator('[data-testid="field-title"] input[type="text"]'), + )), + }, + { + label: 'string in object', + ...(await measureFpsForInput( + page.locator('[data-testid="field-syntheticObject.name"] input[type="text"]'), + )), + }, + ] + }, +}) diff --git a/perf/efps/tsconfig.json b/perf/efps/tsconfig.json new file mode 100644 index 00000000000..5df3fda2442 --- /dev/null +++ b/perf/efps/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "preserve", + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, + "strict": true, + "target": "esnext" + } +} diff --git a/perf/efps/turbo.json b/perf/efps/turbo.json new file mode 100644 index 00000000000..dea276bc6bd --- /dev/null +++ b/perf/efps/turbo.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": {} +} diff --git a/perf/efps/types.ts b/perf/efps/types.ts new file mode 100644 index 00000000000..910932926e3 --- /dev/null +++ b/perf/efps/types.ts @@ -0,0 +1,30 @@ +import {type SanityClient, type SanityDocument, type SanityDocumentStub} from '@sanity/client' +import {type Browser, type BrowserContext, type Page} from 'playwright' + +export interface EfpsTestRunnerContext { + page: Page + context: BrowserContext + browser: Browser + client: SanityClient +} + +export interface EfpsTest { + name: string + configPath: string | undefined + document: SanityDocumentStub | ((context: EfpsTestRunnerContext) => Promise) + run: (context: EfpsTestRunnerContext & {document: SanityDocument}) => Promise +} + +export interface EfpsResult { + label?: string + p50: number + p75: number + p90: number + latencies: number[] +} + +export type EfpsTestResult = EfpsResult | EfpsResult[] + +export function defineEfpsTest(config: EfpsTest): EfpsTest { + return config +} diff --git a/perf/efps/vite-env.d.ts b/perf/efps/vite-env.d.ts new file mode 100644 index 00000000000..11f02fe2a00 --- /dev/null +++ b/perf/efps/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/perf/readme.md b/perf/readme.md index d2efa9adc7e..1c7e72005c4 100644 --- a/perf/readme.md +++ b/perf/readme.md @@ -2,9 +2,10 @@ ## Overview -- **Perf Studio** - (`perf/studio`) this is the Sanity Studio that we run the test suite against (using playwright). This studio is set up to capture various cases that we want to measure performance of (e.g. a very large document) -- **Performance Test Runner and tests** (`perf/tests`) - this includes both performance tests and the runner script that sets up the required config, identifies the set of performance tests to run, instantiates a playwright instance that navigates to the set of perf studio deployments (urls), runs each defined performance test against each deployment and compares the difference. -- Performance test helpers - (`perf/tests/helpers`) A set of performance test helpers that will be injected into the Performance Studio and exposed on `window.perf`. +- **Perf Studio** — (`perf/studio`) this is the Sanity Studio that we run the test suite against (using playwright). This studio is set up to capture various cases that we want to measure performance of (e.g. a very large document) +- **Performance Test Runner and tests** (`perf/tests`) — this includes both performance tests and the runner script that sets up the required config, identifies the set of performance tests to run, instantiates a playwright instance that navigates to the set of perf studio deployments (urls), runs each defined performance test against each deployment and compares the difference. +- **Editor "Frames per Second" (eFPS) suite** — these are specialized performance benchmarks that are separate from the `perf/studio` and `perf/tests`. This suite aims to be more isolated and easier to run with more profiling data. See the [README](./efps/README.md) for more info. +- Performance test helpers — (`perf/tests/helpers`) A set of performance test helpers that will be injected into the Performance Studio and exposed on `window.perf`. ## Prerequisites diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c91f28bcf6..6968086c887 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,7 +182,7 @@ importers: version: 9.1.4 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + version: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -191,7 +191,7 @@ importers: version: 4.1.0 lerna: specifier: ^8.1.8 - version: 8.1.8(babel-plugin-macros@3.1.0)(encoding@0.1.13) + version: 8.1.8(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))(babel-plugin-macros@3.1.0)(encoding@0.1.13) lint-staged: specifier: ^12.1.2 version: 12.5.0(enquirer@2.4.1) @@ -1850,6 +1850,66 @@ importers: specifier: ^5.3.0 version: 5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@19.0.0-rc-a7d1240c-20240731)(react@18.3.1) + perf/efps: + devDependencies: + '@sanity/client': + specifier: ^6.21.2 + version: 6.21.3(debug@4.3.7) + '@swc-node/register': + specifier: ^1.10.9 + version: 1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2) + '@types/react': + specifier: ^18.3.3 + version: 18.3.5 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@types/serve-handler': + specifier: ^6.1.4 + version: 6.1.4 + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.1(vite@5.4.2(@types/node@22.5.4)(terser@5.32.0)) + chalk: + specifier: ^4.1.2 + version: 4.1.2 + cli-table3: + specifier: ^0.6.5 + version: 0.6.5 + dotenv: + specifier: ^16.0.3 + version: 16.4.5 + globby: + specifier: ^10.0.0 + version: 10.0.2 + ora: + specifier: ^8.0.1 + version: 8.0.1 + playwright: + specifier: ^1.46.1 + version: 1.46.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + rollup-plugin-sourcemaps: + specifier: ^0.6.3 + version: 0.6.3(@types/node@22.5.4)(rollup@4.21.3) + sanity: + specifier: workspace:* + version: link:../../packages/sanity + serve-handler: + specifier: ^6.1.5 + version: 6.1.5 + source-map: + specifier: ^0.7.4 + version: 0.7.4 + vite: + specifier: ^5.4.2 + version: 5.4.2(@types/node@22.5.4)(terser@5.32.0) + perf/studio: dependencies: react: @@ -1909,7 +1969,7 @@ importers: version: 0.21.5 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@18.19.44)(typescript@5.6.2) + version: 10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2) typescript: specifier: 5.6.2 version: 5.6.2 @@ -2640,6 +2700,10 @@ packages: '@codemirror/view@6.33.0': resolution: {integrity: sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==} + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -4008,6 +4072,61 @@ packages: resolution: {integrity: sha512-pEzPjvEnWHQCTIv8j/6IYdYBJQUL/Z9Vo0SB2yr5GZNgf0OAznapjilOb7JY9dBEgXtbgtTgSpANZAiipsjhhw==} engines: {node: '>= 12'} + '@oxc-resolver/binding-darwin-arm64@1.10.2': + resolution: {integrity: sha512-aOCZYXqmFL+2sXlaVkYbAOtICGGeTFtmdul8OimQfOXHJods6YHJ2nR6+rEeBcJzaXyXPP18ne1IsEc4AYL1IA==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@1.10.2': + resolution: {integrity: sha512-6WD7lHGkoduFZfUgnC2suKOlqttQRKxWsiVXiiGPu3mfXvQAhMd/gekuH1t8vOhFlPJduaww15n5UB0bSjCK+w==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@1.10.2': + resolution: {integrity: sha512-nEqHWx/Ot5p7Mafj8qH6vFlLSvHjECxAcZwhnAMqRuQu1NgXC/QM3emkdhVGy7QJgsxZbHpPaF6TERNf5/NL9Q==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@1.10.2': + resolution: {integrity: sha512-+AlZI0fPnpfArh8aC5k2295lmQrxa2p8gBLxC3buvCkz0ZpbVLxyyAXz3J2jGwJnmc5MUPLEqPYw6ZlAGH4XHA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@1.10.2': + resolution: {integrity: sha512-8fZ8NszFaUZaoA8eUwkF2lHjgUs76aFiewWgG/cjcZmwKp+ErZQLW8eOvIWZ4SohHQ+ScvhVsSaU2PU38c88gw==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-musl@1.10.2': + resolution: {integrity: sha512-oPrLICrw96Ym9n04FWXWGkbkpF6qJtZ57JSnqI3oQ24xHTt4iWyjHKHQO46NbJAK9sFb3Qce4BzV8faDI5Rifg==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-gnu@1.10.2': + resolution: {integrity: sha512-eli74jTAUiIfqi8IPFqiPxQS69Alcr6w/IFRyf3XxrkxeFGgcgxJkRIxWNTKJ6T3EXxjuma+49LdZn6l9rEj7A==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-musl@1.10.2': + resolution: {integrity: sha512-HH9zmjNSQo3rkbqJH5nIjGrtjC+QPrUy0KGGMR/oRCSLuD0cNFJ/Uly1XAugwSm4oEw0+rv6PmeclXmVTKsxhw==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-wasm32-wasi@1.10.2': + resolution: {integrity: sha512-3ItX23q33sfVBtMMdMhVDSe0NX5zBHxHfmFiXhSJuwNaVIwGpLFU7WU2nmq9oNdnmTOvjL8vlhOqiGvumBLlRA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@1.10.2': + resolution: {integrity: sha512-aVoj2V+jmQ1N+lVy9AhaLmzssJM0lcKt8D0UL83aNLZJ5lSN7hgBuUXTVmL+VF268f167khjo38z+fbELDVm8Q==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@1.10.2': + resolution: {integrity: sha512-l8BDQWyP0Piw8hlmYPUqTRKLsq+ceG9h+9p6ZrjNzwW9AmJX7T7T2hgoVVHqS6f4WNA/CFkb3RyZP9QTzNkyyA==} + cpu: [x64] + os: [win32] + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -4169,6 +4288,12 @@ packages: rollup: optional: true + '@rollup/pluginutils@3.1.0': + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + '@rollup/pluginutils@5.1.0': resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} @@ -4604,6 +4729,91 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@swc-node/core@1.13.3': + resolution: {integrity: sha512-OGsvXIid2Go21kiNqeTIn79jcaX4l0G93X2rAnas4LFoDyA9wAwVK7xZdm+QsKoMn5Mus2yFLCc4OtX2dD/PWA==} + engines: {node: '>= 10'} + peerDependencies: + '@swc/core': '>= 1.4.13' + '@swc/types': '>= 0.1' + + '@swc-node/register@1.10.9': + resolution: {integrity: sha512-iXy2sjP0phPEpK2yivjRC3PAgoLaT4sjSk0LDWCTdcTBJmR4waEog0E6eJbvoOkLkOtWw37SB8vCkl/bbh4+8A==} + peerDependencies: + '@swc/core': '>= 1.4.13' + typescript: '>= 4.3' + + '@swc-node/sourcemap-support@0.5.1': + resolution: {integrity: sha512-JxIvIo/Hrpv0JCHSyRpetAdQ6lB27oFYhv0PKCNf1g2gUXOjpeR1exrXccRxLMuAV5WAmGFBwRnNOJqN38+qtg==} + + '@swc/core-darwin-arm64@1.7.14': + resolution: {integrity: sha512-V0OUXjOH+hdGxDYG8NkQzy25mKOpcNKFpqtZEzLe5V/CpLJPnpg1+pMz70m14s9ZFda9OxsjlvPbg1FLUwhgIQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.7.14': + resolution: {integrity: sha512-9iFvUnxG6FC3An5ogp5jbBfQuUmTTwy8KMB+ZddUoPB3NR1eV+Y9vOh/tfWcenSJbgOKDLgYC5D/b1mHAprsrQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.7.14': + resolution: {integrity: sha512-zGJsef9qPivKSH8Vv4F/HiBXBTHZ5Hs3ZjVGo/UIdWPJF8fTL9OVADiRrl34Q7zOZEtGXRwEKLUW1SCQcbDvZA==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.7.14': + resolution: {integrity: sha512-AxV3MPsoI7i4B8FXOew3dx3N8y00YoJYvIPfxelw07RegeCEH3aHp2U2DtgbP/NV1ugZMx0TL2Z2DEvocmA51g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.7.14': + resolution: {integrity: sha512-JDLdNjUj3zPehd4+DrQD8Ltb3B5lD8D05IwePyDWw+uR/YPc7w/TX1FUVci5h3giJnlMCJRvi1IQYV7K1n7KtQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.7.14': + resolution: {integrity: sha512-Siy5OvPCLLWmMdx4msnEs8HvEVUEigSn0+3pbLjv78iwzXd0qSBNHUPZyC1xeurVaUbpNDxZTpPRIwpqNE2+Og==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.7.14': + resolution: {integrity: sha512-FtEGm9mwtRYQNK43WMtUIadxHs/ja2rnDurB99os0ZoFTGG2IHuht2zD97W0wB8JbqEabT1XwSG9Y5wmN+ciEQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.7.14': + resolution: {integrity: sha512-Jp8KDlfq7Ntt2/BXr0y344cYgB1zf0DaLzDZ1ZJR6rYlAzWYSccLYcxHa97VGnsYhhPspMpmCvHid97oe2hl4A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.7.14': + resolution: {integrity: sha512-I+cFsXF0OU0J9J4zdWiQKKLURO5dvCujH9Jr8N0cErdy54l9d4gfIxdctfTF+7FyXtWKLTCkp+oby9BQhkFGWA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.7.14': + resolution: {integrity: sha512-NNrprQCK6d28mG436jVo2TD+vACHseUECacEBGZ9Ef0qfOIWS1XIt2MisQKG0Oea2VvLFl6tF/V4Lnx/H0Sn3Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.7.14': + resolution: {integrity: sha512-9aeXeifnyuvc2pcuuhPQgVUwdpGEzZ+9nJu0W8/hNl/aESFsJGR5i9uQJRGu0atoNr01gK092fvmqMmQAPcKow==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -4613,6 +4823,9 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@swc/types@0.1.12': + resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} + '@tanem/react-nprogress@5.0.51': resolution: {integrity: sha512-YxNUCpznuBVA+PhjEzFmxaa1czXgU+5Ojchw5JBK7DQS6SHIgNudpFohWpNBWMu2KWByGJ2OLH2OwbM/XyP18Q==} peerDependencies: @@ -4757,6 +4970,9 @@ packages: '@types/decompress@4.2.7': resolution: {integrity: sha512-9z+8yjKr5Wn73Pt17/ldnmQToaFHZxK0N1GHysuk/JIPT8RIdQeoInM01wWPgypRcvb6VH1drjuFpQ4zmY437g==} + '@types/estree@0.0.39': + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -4917,6 +5133,9 @@ packages: '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + '@types/serve-handler@6.1.4': + resolution: {integrity: sha512-aXy58tNie0NkuSCY291xUxl0X+kGYy986l4kqW6Gi4kEXgr6Tx0fpSH7YwUSa5usPpG3s9DBeIR6hHcDtL2IvQ==} + '@types/shallow-equals@1.0.3': resolution: {integrity: sha512-xZx/hZsf1p9J5lGN/nGTsuW/chJCdlyGxilwg1TS78rygBCU5bpY50zZiFcIimlnl0p41kAyaASsy0bqU7WyBA==} @@ -5579,6 +5798,10 @@ packages: resolution: {integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==} engines: {node: '>=12.17'} + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -5735,6 +5958,10 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -5883,6 +6110,10 @@ packages: console-table-printer@2.12.1: resolution: {integrity: sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==} + content-disposition@0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + engines: {node: '>= 0.6'} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -6799,6 +7030,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -6920,6 +7154,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-url-parser@1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + fast-xml-parser@4.4.1: resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true @@ -8636,10 +8873,18 @@ packages: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} + mime-db@1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} + engines: {node: '>= 0.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-types@2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} @@ -9130,6 +9375,9 @@ packages: outdent@0.8.0: resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + oxc-resolver@1.10.2: + resolution: {integrity: sha512-NIbwVqoU8Bhl7PVtItHCg+VFFokIDwBgIgFUwFG2Y8ePhxftFh5xG+KLar5PLWXlCP4WunPIuXD3jr3v6/MfRw==} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -9314,6 +9562,9 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + path-key@2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} @@ -9332,6 +9583,9 @@ packages: path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + path-to-regexp@2.2.1: + resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} + path-to-regexp@6.1.0: resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} @@ -9421,6 +9675,11 @@ packages: engines: {node: '>=16'} hasBin: true + playwright-core@1.46.1: + resolution: {integrity: sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==} + engines: {node: '>=18'} + hasBin: true + playwright-core@1.47.0: resolution: {integrity: sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==} engines: {node: '>=18'} @@ -9431,6 +9690,11 @@ packages: engines: {node: '>=16'} hasBin: true + playwright@1.46.1: + resolution: {integrity: sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==} + engines: {node: '>=18'} + hasBin: true + playwright@1.47.0: resolution: {integrity: sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==} engines: {node: '>=18'} @@ -9610,6 +9874,9 @@ packages: pumpify@1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -9648,6 +9915,10 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} + engines: {node: '>= 0.6'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -10080,6 +10351,16 @@ packages: esbuild: '>=0.18.0' rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 + rollup-plugin-sourcemaps@0.6.3: + resolution: {integrity: sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==} + engines: {node: '>=10.0.0'} + peerDependencies: + '@types/node': '>=10.0.0' + rollup: '>=0.31.2' + peerDependenciesMeta: + '@types/node': + optional: true + rollup@3.29.4: resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -10224,6 +10505,9 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serve-handler@6.1.5: + resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} @@ -10389,6 +10673,10 @@ packages: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated + source-map-resolve@0.6.0: + resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -10407,6 +10695,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + space-separated-tokens@1.1.5: resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} @@ -11264,6 +11556,37 @@ packages: terser: optional: true + vite@5.4.2: + resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@5.4.5: resolution: {integrity: sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -12547,6 +12870,9 @@ snapshots: style-mod: 4.1.2 w3c-keyname: 2.2.8 + '@colors/colors@1.5.0': + optional: true + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -13206,7 +13532,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@10.0.1) @@ -13220,7 +13546,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -13398,12 +13724,12 @@ snapshots: '@juggle/resize-observer@3.4.0': {} - '@lerna/create@8.1.8(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.6.2)': + '@lerna/create@8.1.8(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.6.2)': dependencies: '@npmcli/arborist': 7.5.4 '@npmcli/package-json': 5.2.0 '@npmcli/run-script': 8.1.0 - '@nx/devkit': 19.5.7(nx@19.5.7) + '@nx/devkit': 19.5.7(nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))) '@octokit/plugin-enterprise-rest': 6.0.1 '@octokit/rest': 19.0.11(encoding@0.1.13) aproba: 2.0.0 @@ -13442,7 +13768,7 @@ snapshots: npm-package-arg: 11.0.2 npm-packlist: 8.0.2 npm-registry-fetch: 17.1.0 - nx: 19.5.7 + nx: 19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)) p-map: 4.0.0 p-map-series: 2.1.0 p-queue: 6.6.2 @@ -13803,29 +14129,29 @@ snapshots: - bluebird - supports-color - '@nrwl/devkit@19.5.7(nx@19.5.7)': + '@nrwl/devkit@19.5.7(nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)))': dependencies: - '@nx/devkit': 19.5.7(nx@19.5.7) + '@nx/devkit': 19.5.7(nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))) transitivePeerDependencies: - nx - '@nrwl/tao@19.5.7': + '@nrwl/tao@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))': dependencies: - nx: 19.5.7 + nx: 19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)) tslib: 2.7.0 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' - debug - '@nx/devkit@19.5.7(nx@19.5.7)': + '@nx/devkit@19.5.7(nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)))': dependencies: - '@nrwl/devkit': 19.5.7(nx@19.5.7) + '@nrwl/devkit': 19.5.7(nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))) ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 minimatch: 9.0.3 - nx: 19.5.7 + nx: 19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)) semver: 7.6.3 tmp: 0.2.3 tslib: 2.7.0 @@ -13955,6 +14281,41 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.11 + '@oxc-resolver/binding-darwin-arm64@1.10.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@1.10.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@1.10.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@1.10.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@1.10.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@1.10.2': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@1.10.2': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@1.10.2': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@1.10.2': + dependencies: + '@napi-rs/wasm-runtime': 0.2.4 + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@1.10.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@1.10.2': + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -14143,6 +14504,13 @@ snapshots: optionalDependencies: rollup: 4.21.3 + '@rollup/pluginutils@3.1.0(rollup@4.21.3)': + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 4.21.3 + '@rollup/pluginutils@5.1.0(rollup@4.21.3)': dependencies: '@types/estree': 1.0.5 @@ -15091,6 +15459,78 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@swc-node/core@1.13.3(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)': + dependencies: + '@swc/core': 1.7.14(@swc/helpers@0.5.11) + '@swc/types': 0.1.12 + + '@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2)': + dependencies: + '@swc-node/core': 1.13.3(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12) + '@swc-node/sourcemap-support': 0.5.1 + '@swc/core': 1.7.14(@swc/helpers@0.5.11) + colorette: 2.0.20 + debug: 4.3.7(supports-color@9.4.0) + oxc-resolver: 1.10.2 + pirates: 4.0.6 + tslib: 2.7.0 + typescript: 5.6.2 + transitivePeerDependencies: + - '@swc/types' + - supports-color + + '@swc-node/sourcemap-support@0.5.1': + dependencies: + source-map-support: 0.5.21 + tslib: 2.7.0 + + '@swc/core-darwin-arm64@1.7.14': + optional: true + + '@swc/core-darwin-x64@1.7.14': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.7.14': + optional: true + + '@swc/core-linux-arm64-gnu@1.7.14': + optional: true + + '@swc/core-linux-arm64-musl@1.7.14': + optional: true + + '@swc/core-linux-x64-gnu@1.7.14': + optional: true + + '@swc/core-linux-x64-musl@1.7.14': + optional: true + + '@swc/core-win32-arm64-msvc@1.7.14': + optional: true + + '@swc/core-win32-ia32-msvc@1.7.14': + optional: true + + '@swc/core-win32-x64-msvc@1.7.14': + optional: true + + '@swc/core@1.7.14(@swc/helpers@0.5.11)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.12 + optionalDependencies: + '@swc/core-darwin-arm64': 1.7.14 + '@swc/core-darwin-x64': 1.7.14 + '@swc/core-linux-arm-gnueabihf': 1.7.14 + '@swc/core-linux-arm64-gnu': 1.7.14 + '@swc/core-linux-arm64-musl': 1.7.14 + '@swc/core-linux-x64-gnu': 1.7.14 + '@swc/core-linux-x64-musl': 1.7.14 + '@swc/core-win32-arm64-msvc': 1.7.14 + '@swc/core-win32-ia32-msvc': 1.7.14 + '@swc/core-win32-x64-msvc': 1.7.14 + '@swc/helpers': 0.5.11 + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.11': @@ -15102,6 +15542,10 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.7.0 + '@swc/types@0.1.12': + dependencies: + '@swc/counter': 0.1.3 + '@tanem/react-nprogress@5.0.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 @@ -15265,6 +15709,8 @@ snapshots: dependencies: '@types/node': 18.19.44 + '@types/estree@0.0.39': {} + '@types/estree@1.0.5': {} '@types/event-source-polyfill@1.0.5': {} @@ -15446,6 +15892,10 @@ snapshots: '@types/mime': 1.3.5 '@types/node': 18.19.44 + '@types/serve-handler@6.1.4': + dependencies: + '@types/node': 18.19.44 + '@types/shallow-equals@1.0.3': {} '@types/speakingurl@13.0.6': {} @@ -15706,6 +16156,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-react@4.3.1(vite@5.4.2(@types/node@22.5.4)(terser@5.32.0))': + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.2(@types/node@22.5.4)(terser@5.32.0) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-react@4.3.1(vite@5.4.5(@types/node@18.19.44)(terser@5.32.0))': dependencies: '@babel/core': 7.25.2 @@ -16365,6 +16826,8 @@ snapshots: byte-size@8.1.1: {} + bytes@3.0.0: {} + bytes@3.1.2: {} cac@6.7.14: {} @@ -16535,6 +16998,12 @@ snapshots: cli-spinners@2.9.2: {} + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-truncate@2.1.0: dependencies: slice-ansi: 3.0.0 @@ -16695,6 +17164,8 @@ snapshots: dependencies: simple-wcswidth: 1.0.1 + content-disposition@0.5.2: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -16820,13 +17291,13 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.5.2 - create-jest@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)): + create-jest@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -17964,6 +18435,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@1.0.1: {} + estree-walker@2.0.2: {} esutils@2.0.3: {} @@ -18144,6 +18617,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-url-parser@1.1.3: + dependencies: + punycode: 1.4.1 + fast-xml-parser@4.4.1: dependencies: strnum: 1.0.5 @@ -19412,16 +19889,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)): + jest-cli@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + create-jest: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -19433,7 +19910,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)): + jest-config@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)): dependencies: '@babel/core': 7.25.2 '@jest/test-sequencer': 29.7.0 @@ -19459,7 +19936,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 18.19.44 - ts-node: 10.9.2(@types/node@18.19.44)(typescript@5.6.2) + ts-node: 10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -19694,12 +20171,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)): + jest@29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2)) + jest-cli: 29.7.0(@types/node@18.19.44)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2)) optionalDependencies: node-notifier: 10.0.1 transitivePeerDependencies: @@ -19906,13 +20383,13 @@ snapshots: dependencies: readable-stream: 2.3.8 - lerna@8.1.8(babel-plugin-macros@3.1.0)(encoding@0.1.13): + lerna@8.1.8(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))(babel-plugin-macros@3.1.0)(encoding@0.1.13): dependencies: - '@lerna/create': 8.1.8(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.6.2) + '@lerna/create': 8.1.8(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.6.2) '@npmcli/arborist': 7.5.4 '@npmcli/package-json': 5.2.0 '@npmcli/run-script': 8.1.0 - '@nx/devkit': 19.5.7(nx@19.5.7) + '@nx/devkit': 19.5.7(nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11))) '@octokit/plugin-enterprise-rest': 6.0.1 '@octokit/rest': 19.0.11(encoding@0.1.13) aproba: 2.0.0 @@ -19957,7 +20434,7 @@ snapshots: npm-package-arg: 11.0.2 npm-packlist: 8.0.2 npm-registry-fetch: 17.1.0 - nx: 19.5.7 + nx: 19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)) p-map: 4.0.0 p-map-series: 2.1.0 p-pipe: 3.1.0 @@ -20307,8 +20784,14 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.33.0: {} + mime-db@1.52.0: {} + mime-types@2.1.18: + dependencies: + mime-db: 1.33.0 + mime-types@2.1.35: dependencies: mime-db: 1.52.0 @@ -20742,10 +21225,10 @@ snapshots: nwsapi@2.2.12: {} - nx@19.5.7: + nx@19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)): dependencies: '@napi-rs/wasm-runtime': 0.2.4 - '@nrwl/tao': 19.5.7 + '@nrwl/tao': 19.5.7(@swc-node/register@1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.14(@swc/helpers@0.5.11)) '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 @@ -20790,6 +21273,8 @@ snapshots: '@nx/nx-linux-x64-musl': 19.5.7 '@nx/nx-win32-arm64-msvc': 19.5.7 '@nx/nx-win32-x64-msvc': 19.5.7 + '@swc-node/register': 1.10.9(@swc/core@1.7.14(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.2) + '@swc/core': 1.7.14(@swc/helpers@0.5.11) transitivePeerDependencies: - debug @@ -20935,6 +21420,20 @@ snapshots: outdent@0.8.0: {} + oxc-resolver@1.10.2: + optionalDependencies: + '@oxc-resolver/binding-darwin-arm64': 1.10.2 + '@oxc-resolver/binding-darwin-x64': 1.10.2 + '@oxc-resolver/binding-freebsd-x64': 1.10.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 1.10.2 + '@oxc-resolver/binding-linux-arm64-gnu': 1.10.2 + '@oxc-resolver/binding-linux-arm64-musl': 1.10.2 + '@oxc-resolver/binding-linux-x64-gnu': 1.10.2 + '@oxc-resolver/binding-linux-x64-musl': 1.10.2 + '@oxc-resolver/binding-wasm32-wasi': 1.10.2 + '@oxc-resolver/binding-win32-arm64-msvc': 1.10.2 + '@oxc-resolver/binding-win32-x64-msvc': 1.10.2 + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -21121,6 +21620,8 @@ snapshots: path-is-absolute@1.0.1: {} + path-is-inside@1.0.2: {} + path-key@2.0.1: {} path-key@3.1.1: {} @@ -21134,6 +21635,8 @@ snapshots: path-to-regexp@0.1.10: {} + path-to-regexp@2.2.1: {} + path-to-regexp@6.1.0: {} path-to-regexp@6.2.2: {} @@ -21196,6 +21699,8 @@ snapshots: playwright-core@1.44.1: {} + playwright-core@1.46.1: {} + playwright-core@1.47.0: optional: true @@ -21205,6 +21710,12 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + playwright@1.46.1: + dependencies: + playwright-core: 1.46.1 + optionalDependencies: + fsevents: 2.3.2 + playwright@1.47.0: dependencies: playwright-core: 1.47.0 @@ -21380,6 +21891,8 @@ snapshots: inherits: 2.0.4 pump: 2.0.1 + punycode@1.4.1: {} + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -21412,6 +21925,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + range-parser@1.2.0: {} + range-parser@1.2.1: {} raw-body@2.5.2: @@ -21898,6 +22413,14 @@ snapshots: transitivePeerDependencies: - supports-color + rollup-plugin-sourcemaps@0.6.3(@types/node@22.5.4)(rollup@4.21.3): + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@4.21.3) + rollup: 4.21.3 + source-map-resolve: 0.6.0 + optionalDependencies: + '@types/node': 22.5.4 + rollup@3.29.4: optionalDependencies: fsevents: 2.3.3 @@ -22112,6 +22635,17 @@ snapshots: dependencies: randombytes: 2.1.0 + serve-handler@6.1.5: + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + fast-url-parser: 1.1.3 + mime-types: 2.1.18 + minimatch: 3.1.2 + path-is-inside: 1.0.2 + path-to-regexp: 2.2.1 + range-parser: 1.2.0 + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -22342,6 +22876,11 @@ snapshots: source-map-url: 0.4.1 urix: 0.1.0 + source-map-resolve@0.6.0: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -22358,6 +22897,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.4: {} + space-separated-tokens@1.1.5: {} spdx-correct@3.2.0: @@ -22887,7 +23428,7 @@ snapshots: dependencies: typescript: 5.6.2 - ts-node@10.9.2(@types/node@18.19.44)(typescript@5.6.2): + ts-node@10.9.2(@swc/core@1.7.14(@swc/helpers@0.5.11))(@types/node@18.19.44)(typescript@5.6.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -22904,6 +23445,8 @@ snapshots: typescript: 5.6.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.7.14(@swc/helpers@0.5.11) tsconfck@3.1.1(typescript@5.6.2): optionalDependencies: @@ -23260,6 +23803,16 @@ snapshots: fsevents: 2.3.3 terser: 5.32.0 + vite@5.4.2(@types/node@22.5.4)(terser@5.32.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.21.3 + optionalDependencies: + '@types/node': 22.5.4 + fsevents: 2.3.3 + terser: 5.32.0 + vite@5.4.5(@types/node@18.19.44)(terser@5.32.0): dependencies: esbuild: 0.21.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d7e830df433..5bd945df1d0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,6 @@ packages: - 'dev/*' - - 'perf/studio' - - 'perf/tests' + - 'perf/*' - 'examples/*' - 'packages/@repo/*' - 'packages/@sanity/*' diff --git a/turbo.json b/turbo.json index 491286b4f25..be93f6c7f5e 100644 --- a/turbo.json +++ b/turbo.json @@ -21,7 +21,11 @@ "PKG_FILE_PATH", "RUNNER_OS", "SANITY_BASE_PATH", - "TZ" + "TZ", + // these is for the perf/efps perf suite and should not be cached + "VITE_PERF_EFPS_PROJECT_ID", + "VITE_PERF_EFPS_DATASET", + "PERF_EFPS_SANITY_TOKEN" ], "globalDependencies": [ ".npmrc",