From a83f9cd37898cee27ea80e0ef6e54b0831b3545e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 31 Jul 2026 18:12:19 +0000 Subject: [PATCH] delete the orphaned build script, and fix the second copy of the memo rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/build/runtime.ts had no caller after the build:editor scripts went. Its own usage text gives away where it came from: --experiments, --tracking, --editor-setup, the same phantom domain as the docs and examples cleaned up earlier. Nothing else references it — the `./runtime` export in src/workspaces/types/package.json points at a different file, which is untouched. helpers.ts stays; dashboard.ts and web.ts import it. APP_CONVENTIONS.md carried its own copy of "React 19's compiler handles memoization. Never use useCallback or useMemo." Correcting only CONVENTIONS.md would have left the two contradicting each other, which is worse than either. Both now say the same thing and one points at the other for the reasoning. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/build/runtime.ts | 212 ------------------- src/workspaces/officerdev/APP_CONVENTIONS.md | 10 +- 2 files changed, 8 insertions(+), 214 deletions(-) delete mode 100755 scripts/build/runtime.ts diff --git a/scripts/build/runtime.ts b/scripts/build/runtime.ts deleted file mode 100755 index 83624b9c..00000000 --- a/scripts/build/runtime.ts +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env bun -import { config as dotenv } from "dotenv"; -import { existsSync } from "fs"; -import { rm } from "fs/promises"; -import path from "path"; -import { fileURLToPath } from "url"; -import { $ } from "bun"; -import { buildConfig } from "./helpers"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const envPath = path.resolve(__dirname, "../../.env"); -dotenv({ path: envPath }); - -if (process.argv.includes("--help") || process.argv.includes("-h")) { - console.log(` -šŸ—ļø Runtime Build Script - -Usage: bun run scripts/build/runtime.ts [options] - -Builds pertento-runtime to src/apps/runtime/dist - -Options: - --help, -h Show this help message - --core Build only runtime-core (default) - --experiments Build only runtime-experiments - --tracking Build only runtime-tracking - --editor-setup Build only editor-setup - --all Build all runtimes - -Example: - bun run scripts/build/runtime.ts - bun run scripts/build/runtime.ts --experiments - bun run scripts/build/runtime.ts --tracking - bun run scripts/build/runtime.ts --editor-setup - bun run scripts/build/runtime.ts --all -`); - process.exit(0); -} - -const formatFileSize = (bytes: number): string => { - const units = ["B", "KB", "MB", "GB"]; - let size = bytes; - let unitIndex = 0; - - while (size >= 1024 && unitIndex < units.length - 1) { - size /= 1024; - unitIndex++; - } - - return `${size.toFixed(2)} ${units[unitIndex]}`; -}; - -interface RuntimeBuildConfig { - name: string; - srcDir: string; - configPath: string; - outFilename: string; - format: "iife" | "esm"; - entry?: string; -} - -const runtimeConfigs: Record = { - core: { - name: "runtime-core", - srcDir: "src/apps/runtime/runtime-core/src", - configPath: "src/apps/runtime/runtime-core/src/config.ts", - outFilename: "pertentoRuntime5.js", - format: "iife", - }, - experiments: { - name: "runtime-experiments", - srcDir: "src/apps/runtime/runtime-experiments/src", - configPath: "src/apps/runtime/runtime-experiments/src/config.ts", - outFilename: "pertentoRuntime5Experiments.js", - format: "esm", - }, - tracking: { - name: "runtime-tracking", - srcDir: "src/apps/runtime/runtime-tracking/src", - configPath: "src/apps/runtime/runtime-tracking/src/config.ts", - outFilename: "pertentoRuntime5Tracking.js", - format: "iife", - }, - editorSetup: { - name: "editor-setup", - srcDir: "src/apps/runtime/runtime-tracking/src", - configPath: "src/apps/runtime/runtime-tracking/src/config.ts", - outFilename: "pertentoEditorSetup.js", - format: "iife", - entry: "setup-for-editor-extension.ts", - }, -}; - -let buildTargets: string[] = ["core"]; -if (process.argv.includes("--experiments")) { - buildTargets = ["experiments"]; -} else if (process.argv.includes("--tracking")) { - buildTargets = ["tracking"]; -} else if (process.argv.includes("--editor-setup")) { - buildTargets = ["editorSetup"]; -} else if (process.argv.includes("--all")) { - buildTargets = ["core", "experiments", "tracking", "editorSetup"]; -} - -console.log("\nšŸš€ Starting runtime build process...\n"); - -const { BUILD_ENV } = process.env; -const isProduction = BUILD_ENV === "production"; -console.log( - `šŸ“‹ Build environment: ${BUILD_ENV || "development"} ${isProduction ? "(minified)" : "(development)"}\n`, -); - -const outdir = "src/apps/runtime/dist"; - -if (existsSync(outdir)) { - console.log(`šŸ—‘ļø Cleaning previous build at ${outdir}`); - await rm(outdir, { recursive: true, force: true }); -} - -const start = performance.now(); - -async function buildRuntime(target: string) { - const config = runtimeConfigs[target]; - if (!config) { - console.error(`Unknown runtime target: ${target}`); - process.exit(1); - } - - // Generate config with environment values - console.log(`šŸ”§ Generating config for ${config.name}...\n`); - buildConfig(config.configPath, target); - - console.log(`šŸ“¦ Building ${config.name}...\n`); - - const entryFile = config.entry || "index.ts"; - const result = await Bun.build({ - entrypoints: [`${config.srcDir}/${entryFile}`], - outdir, - minify: isProduction, - target: "browser", - format: config.format, - naming: config.outFilename, - define: { - "process.env.NODE_ENV": JSON.stringify( - isProduction ? "production" : "development", - ), - }, - }); - - if (!result.success) { - console.error(`Build failed for ${config.name}!`); - process.exit(1); - } - - // Compress the output files using Bun shell - const jsFile = path.join(outdir, config.outFilename); - if (existsSync(jsFile)) { - // Create gzip version - await $`gzip -k -f ${jsFile}`; - - // Create brotli version - await $`brotli -k -f ${jsFile}`; - } - - return result; -} - -// Build all targets -let allOutputs: { path: string; kind: string }[] = []; -for (const target of buildTargets) { - const result = await buildRuntime(target); - allOutputs = allOutputs.concat(result.outputs); -} - -const end = performance.now(); - -// Collect all compressed files -const allCompressed = buildTargets.flatMap((target) => { - const config = runtimeConfigs[target]; - if (!config) return []; - return [ - { path: path.join(outdir, `${config.outFilename}.gz`), kind: "compressed" }, - { path: path.join(outdir, `${config.outFilename}.br`), kind: "compressed" }, - ]; -}); - -const outputTable = allOutputs - .concat(allCompressed) - .filter((output) => existsSync(output.path)) - .map((output) => ({ - File: path.relative(process.cwd(), output.path), - Size: formatFileSize(Bun.file(output.path).size), - })); - -console.table(outputTable); -const buildTime = (end - start).toFixed(2); - -// Copy all output files to runtime-scripts directory -const runtimeScriptsDir = "runtime-scripts"; -if (!existsSync(runtimeScriptsDir)) { - await $`mkdir -p ${runtimeScriptsDir}`; -} - -console.log(`\nšŸ“ Copying files to ${runtimeScriptsDir}...`); -for (const output of allOutputs.concat(allCompressed)) { - if (existsSync(output.path)) { - const filename = path.basename(output.path); - await $`cp ${output.path} ${runtimeScriptsDir}/${filename}`; - } -} - -console.log(`\nāœ… Runtime build completed in ${buildTime}ms\n`); diff --git a/src/workspaces/officerdev/APP_CONVENTIONS.md b/src/workspaces/officerdev/APP_CONVENTIONS.md index b5182fb1..ea0b250e 100644 --- a/src/workspaces/officerdev/APP_CONVENTIONS.md +++ b/src/workspaces/officerdev/APP_CONVENTIONS.md @@ -97,9 +97,15 @@ Within a file, order sections as: 5. Types (props types for sub-components) 6. Helper components / functions -## No useCallback or useMemo (React 19) +## Prefer plain values over memoization -React 19's compiler handles memoization. Never use `useCallback` or `useMemo`. +Default to writing derivations plainly — most are cheap, and the memo costs more than the work. But +`useMemo`/`useCallback` are ordinary tools, not forbidden ones: reach for them when a value or callback +feeds a dependency array, when a computation is genuinely expensive, or when the prop goes to a memoized +child. See `CONVENTIONS.md` for the full rule and why the old "never, React 19 handles it" version was +wrong — the React Compiler is opt-in and is not installed here. + +The examples below are still the right default shape: ```tsx // Plain function — not wrapped in useCallback