ci(benchmark/react): add LoadScript to benchmark#1699
ci(benchmark/react): add LoadScript to benchmark#1699colinaaa merged 1 commit intolynx-family:mainfrom
Conversation
|
📝 WalkthroughWalkthroughAdds a post-load hook API ( Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks (1 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
Poem
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (10)
🚧 Files skipped from review as they are similar to previous changes (10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (11)
benchmark/react/package.json (1)
16-16: Align repeat behavior across perfetto scripts (and avoid potential single-run overwrite).If benchx_cli writes one ptrace per invocation,
--repeat=50may overwrite or aggregate implicitly. For apples-to-apples runs, either propagate--repeat=50to the other perfetto scripts or keep all single-run; also confirm howbenchx_clihandles repeated-ooutput.Proposed alignment:
"perfetto:001-fib": "benchx_cli -o dist/001-fib.ptrace run dist/001-fib.lynx.bundle", -"perfetto:002-hello-reactLynx": "benchx_cli -o dist/002-hello-reactLynx.ptrace run dist/002-hello-reactLynx.lynx.bundle --repeat=50 --wait-for-id=stop-benchmark-true", +"perfetto:002-hello-reactLynx": "benchx_cli -o dist/002-hello-reactLynx.ptrace run dist/002-hello-reactLynx.lynx.bundle --repeat=50 --wait-for-id=stop-benchmark-true", -"perfetto:003-hello-list": "benchx_cli -o dist/003-hello-list.ptrace run dist/003-hello-list.lynx.bundle --wait-for-id=stop-benchmark-true", +"perfetto:003-hello-list": "benchx_cli -o dist/003-hello-list.ptrace run dist/003-hello-list.lynx.bundle --repeat=50 --wait-for-id=stop-benchmark-true", -"perfetto:004-various-update": "benchx_cli -o dist/004-various-update.ptrace run dist/004-various-update.lynx.bundle --wait-for-id=stop-benchmark-true", +"perfetto:004-various-update": "benchx_cli -o dist/004-various-update.ptrace run dist/004-various-update.lynx.bundle --repeat=50 --wait-for-id=stop-benchmark-true",If repeats produce multiple traces, consider suffixing with run index or switching to an aggregated output flag if available.
benchmark/react/rspeedy-env.d.ts (1)
13-20: Tighten docs: grammar + clarify semantics of the callback.Small grammar fix and clarity that the callback runs after the load-time benchmark has stopped.
/** - * This function will be called after the script is loaded and executed. - * Codspeed don't allow stacked benchmark, but sadly we have some cases - * benchmark is executed while loading script, we need to move those cases - * to after script load using this API. - * @param cb The callback to run after script loaded. + * Register a callback to run after the bundle has loaded and executed. + * Codspeed doesn't allow stacked benchmarks; some cases currently start + * during script load. Use this API to defer those to after script load. + * Note: callbacks typically run after the load-time benchmark has stopped. + * @param cb The callback to run after the script has loaded. */benchmark/react/plugins/pluginScriptLoad.mjs (3)
43-46: Consider setting the benchmark name before stopping to ensure attribution.Safer ordering: name → stop → flush.
-Codspeed.stopBenchmark(); -Codspeed.setExecutedBenchmark(\`${caseDir}::\${${JSON.stringify(chunkName)}}_LoadScript\`); +Codspeed.setExecutedBenchmark(\`${caseDir}::\${${JSON.stringify(chunkName)}}_LoadScript\`); +Codspeed.stopBenchmark(); Codspeed.runAfterLoadScript.flush();
21-28: Make runAfterLoadScript idempotent and resilient (drain queue, ignore non-fns, guard double flush).Prevents double-execution, unbounded queue growth, and improves error isolation.
Codspeed.runAfterLoadScript = (() => { - const q = []; - const r = (cb) => { q.push(cb); }; - r.flush = () => { for (const cb of q) { cb(); } }; + /** @type {Array<() => void>} */ + const q = []; + let flushed = false; + const r = (cb) => { + if (typeof cb !== 'function') return; + if (flushed) { + queueMicrotask(cb); + } else { + q.push(cb); + } + }; + r.flush = () => { + if (flushed) return; + flushed = true; + const cbs = q.splice(0, q.length); + for (const cb of cbs) { + try { cb(); } catch (e) { console.error('[Codspeed.runAfterLoadScript]', e); } + } + }; return r; })();
39-40: Avoid hardcodingcaseDir.Derive from a build param or from a compile-time constant to keep this plugin reusable across packages.
I can wire this to accept a
caseDiroption (defaulting to current) and updatelynx.config.jsaccordingly.benchmark/react/lynx.config.js (1)
14-14: Disabling filename hashing risks stale assets behind a CDN.If this is only for local/CI benchmarks, OK. If it’s ever served via
assetPrefix, ensure strong no-cache headers or a versionedassetPrefixto avoid cache poisoning.benchmark/react/cases/004-various-update/index.tsx (1)
230-234: Prefer a safe fallback if Codspeed isn’t injected.Avoid a hard dependency on the plugin for local/dev runs.
Apply this diff:
-Codspeed.runAfterLoadScript(() => { - root.render( - <F />, - ); -}); +if (typeof Codspeed !== 'undefined') { + Codspeed.runAfterLoadScript(() => { + root.render(<F />); + }); +} else { + root.render(<F />); +}benchmark/react/cases/003-hello-list/index.tsx (1)
71-75: Add non-Codspeed fallback for resilience.Same rationale as other cases; keep dev runs working without the banner plugin.
Apply this diff:
-Codspeed.runAfterLoadScript(() => { - root.render( - <BenchmarkList />, - ); -}); +if (typeof Codspeed !== 'undefined') { + Codspeed.runAfterLoadScript(() => { + root.render(<BenchmarkList />); + }); +} else { + root.render(<BenchmarkList />); +}benchmark/react/cases/005-load-script/index.tsx (1)
8-12: Guard against missing Codspeed in non-benchmark environments.Apply this diff:
-Codspeed.runAfterLoadScript(() => { - root.render( - <></>, - ); -}); +if (typeof Codspeed !== 'undefined') { + Codspeed.runAfterLoadScript(() => { + root.render(<></>); + }); +} else { + root.render(<></>); +}benchmark/react/src/dummyRoot.tsx (1)
7-9: Minor: add fallback to keep this entry usable without the plugin.Apply this diff:
-Codspeed.runAfterLoadScript(() => { - root.render(<></>); -}); +if (typeof Codspeed !== 'undefined') { + Codspeed.runAfterLoadScript(() => { + root.render(<></>); + }); +} else { + root.render(<></>); +}benchmark/react/cases/001-fib/index.ts (1)
9-63: Two nits: safer memo check; optional Codspeed fallback wrapper.
- Use explicit
!== undefinedfor memoization checks to avoid truthiness pitfalls.- Optional: guard
Codspeedto allow running this case without the plugin.Apply this diff:
-Codspeed.runAfterLoadScript(() => { +const run = () => { if (__MAIN_THREAD__) { @@ - function fib2(n: number): number { - const memo: number[] = Array.from({ length: n + 1 }); - const dp = (n: number): number => { - if (n <= 1) return n; - if (memo[n]) return memo[n]; - memo[n] = dp(n - 1) + dp(n - 2); - return memo[n]; - }; - return dp(n); - } + function fib2(n: number): number { + const memo: (number | undefined)[] = new Array(n + 1); + const dp = (k: number): number => { + if (k <= 1) return k; + const cached = memo[k]; + if (cached !== undefined) return cached; + const val = dp(k - 1) + dp(k - 2); + memo[k] = val; + return val; + }; + return dp(n); + } @@ -}); +}; + +if (typeof Codspeed !== 'undefined') { + Codspeed.runAfterLoadScript(run); +} else { + run(); +}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
benchmark/react/cases/001-fib/index.ts(1 hunks)benchmark/react/cases/002-hello-reactLynx/index.tsx(1 hunks)benchmark/react/cases/003-hello-list/index.tsx(1 hunks)benchmark/react/cases/004-various-update/index.tsx(1 hunks)benchmark/react/cases/005-load-script/index.tsx(1 hunks)benchmark/react/lynx.config.js(3 hunks)benchmark/react/package.json(1 hunks)benchmark/react/plugins/pluginScriptLoad.mjs(1 hunks)benchmark/react/rspeedy-env.d.ts(1 hunks)benchmark/react/src/dummyRoot.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-12T16:09:32.413Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1497
File: packages/react/transform/tests/__swc_snapshots__/src/swc_plugin_snapshot/mod.rs/basic_full_static.js:9-10
Timestamp: 2025-08-12T16:09:32.413Z
Learning: In the Lynx stack, functions prefixed with `__` that are called in transformed code may be injected globally by the Lynx Engine at runtime rather than exported from the React runtime package. For example, `__CreateFrame` is injected globally by the Lynx Engine, not exported from lynx-js/react.
Applied to files:
benchmark/react/cases/002-hello-reactLynx/index.tsxbenchmark/react/cases/005-load-script/index.tsx
📚 Learning: 2025-08-25T09:16:24.277Z
Learnt from: CR
PR: lynx-family/lynx-stack#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-25T09:16:24.277Z
Learning: Always run a full build with `pnpm turbo build` before running any tests
Applied to files:
benchmark/react/package.json
📚 Learning: 2025-08-27T12:42:01.095Z
Learnt from: upupming
PR: lynx-family/lynx-stack#1616
File: packages/webpack/cache-events-webpack-plugin/test/cases/not-cache-events/lazy-bundle/index.js:3-3
Timestamp: 2025-08-27T12:42:01.095Z
Learning: In webpack, properties like __webpack_require__.lynx_ce are injected during compilation/build time when webpack processes modules and generates bundles, not at runtime when dynamic imports execute. Tests for such properties don't need to wait for dynamic imports to complete.
Applied to files:
benchmark/react/lynx.config.jsbenchmark/react/cases/005-load-script/index.tsx
📚 Learning: 2025-08-06T13:28:57.182Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1453
File: vitest.config.ts:49-61
Timestamp: 2025-08-06T13:28:57.182Z
Learning: In the lynx-family/lynx-stack repository, the file `packages/react/testing-library/src/vitest.config.js` is source code for the testing library that gets exported for users, not a test configuration that should be included in the main vitest projects array.
Applied to files:
benchmark/react/lynx.config.js
🧬 Code graph analysis (2)
benchmark/react/cases/002-hello-reactLynx/index.tsx (1)
benchmark/react/src/RunBenchmarkUntil.tsx (1)
RunBenchmarkUntilHydrate(7-9)
benchmark/react/lynx.config.js (1)
benchmark/react/plugins/pluginScriptLoad.mjs (2)
pluginScriptLoad(8-52)pluginScriptLoad(8-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build / Build (Ubuntu)
- GitHub Check: build / Build (Windows)
- GitHub Check: test-rust / clippy
- GitHub Check: test-rust / Test (Ubuntu)
🔇 Additional comments (3)
benchmark/react/lynx.config.js (2)
10-10: Import looks correct; ensure plugin file is included in build graph.No issues; just confirm
./plugins/pluginScriptLoad.mjsis part of the package published/consumed by CI, otherwise config will fail at runtime.
30-30: TSX entry is supported—no changes needed.benchmark/react/tsconfig.jsonsets"jsx": "react-jsx"and includes"src", so.tsxfiles (e.g../src/dummyRoot.tsx) are picked up and compiled.benchmark/react/cases/002-hello-reactLynx/index.tsx (1)
10-17: LGTM: render deferral via Codspeed wrapper is consistent with the suite.
Web Explorer#4999 Bundle Size — 366.49KiB (0%).6ddfd5e(current) vs de8a1c7 main#4998(baseline) Bundle metrics
Bundle size by type
|
| Current #4999 |
Baseline #4998 |
|
|---|---|---|
235.59KiB |
235.59KiB |
|
99.06KiB |
99.06KiB |
|
31.84KiB |
31.84KiB |
Bundle analysis report Branch hzy:p/hzy/bench_10 Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#5006 Bundle Size — 238.2KiB (0%).6ddfd5e(current) vs de8a1c7 main#5005(baseline) Bundle metrics
|
| Current #5006 |
Baseline #5005 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
163 |
163 |
|
67 |
67 |
|
46.88% |
46.88% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #5006 |
Baseline #5005 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
92.45KiB |
92.45KiB |
Bundle analysis report Branch hzy:p/hzy/bench_10 Project dashboard
Generated by RelativeCI Documentation Report issue
CodSpeed Performance ReportMerging #1699 will degrade performances by 11.64%Comparing 🎉 Hooray!
|
| Benchmark | BASE |
HEAD |
Change | |
|---|---|---|---|---|
| 🆕 | 001-fib/background.js_LoadScript |
N/A | 546.8 µs | N/A |
| 🆕 | 001-fib/main-thread.js_LoadScript |
N/A | 1.1 ms | N/A |
| ❌ | 002-hello-reactLynx-destroyBackground |
625 µs | 707.3 µs | -11.64% |
| 🆕 | 002-hello-reactLynx/background.js_LoadScript |
N/A | 475.6 µs | N/A |
| 🆕 | 002-hello-reactLynx/main-thread.js_LoadScript |
N/A | 388.8 µs | N/A |
| 🆕 | 003-hello-list/background.js_LoadScript |
N/A | 1.5 ms | N/A |
| 🆕 | 003-hello-list/main-thread.js_LoadScript |
N/A | 1.5 ms | N/A |
| 🆕 | 004-various-update/background.js_LoadScript |
N/A | 383.3 µs | N/A |
| 🆕 | 004-various-update/main-thread.js_LoadScript |
N/A | 643.4 µs | N/A |
| 🆕 | 005-load-script/background.js_LoadScript |
N/A | 1.2 ms | N/A |
| 🆕 | 005-load-script/main-thread.js_LoadScript |
N/A | 1.1 ms | N/A |
| ❌ | basic-performance-nest-level-100 |
5.9 ms | 6.6 ms | -11.1% |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
benchmark/react/lynx.config.js (1)
57-57: Guard against Codspeed absence and make flush idempotent (duplicate of prior feedback).As previously noted,
pluginScriptLoad()injects unguardedCodspeed.*calls, a non-idempotentflush(), and a buggy regex forchunkName. These can crash dev builds and re-run callbacks across assets. Please adopt the earlier fix inbenchmark/react/plugins/pluginScriptLoad.mjs, or gate this plugin to CI-only in this config.Optionally gate the plugin here to avoid local/dev crashes:
plugins: [ pluginRepoFilePath(), pluginReactLynx({ enableParallelElement: false, pipelineSchedulerConfig: 0, debugInfoOutside: false, }), - pluginScriptLoad(), + ...(process.env.CODSPEED_BENCH === '1' ? [pluginScriptLoad()] : []), pluginQRCode({}), ],
🧹 Nitpick comments (1)
benchmark/react/lynx.config.js (1)
14-14: Disabling filename hashing: confirm cache-busting strategy.With
filenameHash: falseand a staticassetPrefix, CDN/browser caches may serve stale bundles across runs. Either ensure no-store headers on the CDN or scope the prefix by commit/CI run.Apply this to make the prefix versioned by CI/commit (fallback to dev):
- assetPrefix: 'https://example.com/benchmark/react', + assetPrefix: `${process.env.BENCH_ASSET_PREFIX ?? 'https://example.com/benchmark/react'}/${process.env.GIT_SHA ?? 'dev'}`,
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
benchmark/react/cases/001-fib/index.ts(1 hunks)benchmark/react/cases/002-hello-reactLynx/index.tsx(1 hunks)benchmark/react/cases/003-hello-list/index.tsx(1 hunks)benchmark/react/cases/004-various-update/index.tsx(1 hunks)benchmark/react/cases/005-load-script/index.tsx(1 hunks)benchmark/react/lynx.config.js(4 hunks)benchmark/react/package.json(1 hunks)benchmark/react/plugins/pluginScriptLoad.mjs(1 hunks)benchmark/react/rspeedy-env.d.ts(1 hunks)benchmark/react/src/dummyRoot.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- benchmark/react/cases/002-hello-reactLynx/index.tsx
- benchmark/react/cases/003-hello-list/index.tsx
- benchmark/react/rspeedy-env.d.ts
- benchmark/react/package.json
- benchmark/react/src/dummyRoot.tsx
- benchmark/react/cases/005-load-script/index.tsx
- benchmark/react/plugins/pluginScriptLoad.mjs
- benchmark/react/cases/001-fib/index.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-27T12:42:01.095Z
Learnt from: upupming
PR: lynx-family/lynx-stack#1616
File: packages/webpack/cache-events-webpack-plugin/test/cases/not-cache-events/lazy-bundle/index.js:3-3
Timestamp: 2025-08-27T12:42:01.095Z
Learning: In webpack, properties like __webpack_require__.lynx_ce are injected during compilation/build time when webpack processes modules and generates bundles, not at runtime when dynamic imports execute. Tests for such properties don't need to wait for dynamic imports to complete.
Applied to files:
benchmark/react/lynx.config.js
📚 Learning: 2025-08-06T13:28:57.182Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1453
File: vitest.config.ts:49-61
Timestamp: 2025-08-06T13:28:57.182Z
Learning: In the lynx-family/lynx-stack repository, the file `packages/react/testing-library/src/vitest.config.js` is source code for the testing library that gets exported for users, not a test configuration that should be included in the main vitest projects array.
Applied to files:
benchmark/react/lynx.config.js
🧬 Code graph analysis (1)
benchmark/react/lynx.config.js (1)
benchmark/react/plugins/pluginScriptLoad.mjs (2)
pluginScriptLoad(8-52)pluginScriptLoad(8-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: build / Build (Windows)
- GitHub Check: build / Build (Ubuntu)
- GitHub Check: test-rust / Test (Ubuntu)
🔇 Additional comments (3)
benchmark/react/lynx.config.js (3)
10-10: Import path and symbol look correct.The local import resolves to the expected plugin module and matches the exported name.
45-47: 005-load-script case validated: usesCodspeed.runAfterLoadScriptpost-load hook and contains no dynamic imports (single JS chunk).
30-30: Resolved: TSX pipeline and ambient types are correctly configured
The local tsconfig.json in benchmark/react declares"jsx": "react-jsx"with the appropriatejsxImportSource, and the ambient declarations in benchmark/react/rspeedy-env.d.ts are included by the default"include": ["**/*"]behavior—no further changes needed.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
benchmark/react/cases/001-fib/index.ts (4)
16-25: Memoization: make presence checks explicit and avoid param shadowingTruthiness on memo[n] works here but is brittle. Seed base cases, check for undefined explicitly, avoid reusing the name n in dp, and avoid any[] from Array.from.
- function fib2(n: number): number { - const memo: number[] = Array.from({ length: n + 1 }); - const dp = (n: number): number => { - if (n <= 1) return n; - if (memo[n]) return memo[n]; - memo[n] = dp(n - 1) + dp(n - 2); - return memo[n]; - }; - return dp(n); - } + function fib2(n: number): number { + const memo: (number | undefined)[] = new Array(n + 1); + memo[0] = 0; + memo[1] = 1; + const dp = (k: number): number => { + const v = memo[k]; + if (v !== undefined) return v; + const res = dp(k - 1) + dp(k - 2); + memo[k] = res; + return res; + }; + return dp(n); + }
27-36: Nit: collapse base casesSlightly tighter and consistent with fib().
- function fib3(n: number): number { - if (n === 0) return 0; - if (n === 1) return 1; + function fib3(n: number): number { + if (n < 2) return n; let prev = 0, curr = 1; for (let i = 2; i <= n; i++) { const next = prev + curr; prev = curr; curr = next; } return curr; }
39-45: tinybench time is milliseconds—make intent explicitIf you meant 5 seconds, set 5_000 and consider letting time govern instead of mixing iterations; tinybench treats time in ms.
- new Bench({ - name: 'simple benchmark', - time: 5, - iterations: 20, - }), + new Bench({ + name: 'simple benchmark', + // time is in milliseconds; use 5s + time: 5_000, + iterations: 20, + }),
58-63: Ensure tinybench version supports runSync; otherwise switch to async runSome versions only expose run(). This tweak keeps the surrounding code non-async.
- bench.runSync(); - - console.log(bench.name); - console.log(JSON.stringify(bench.table(), undefined, 2)); + void (async () => { + await bench.run(); + console.log(bench.name); + console.log(JSON.stringify(bench.table(), undefined, 2)); + })();
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
benchmark/react/cases/001-fib/index.ts(1 hunks)benchmark/react/cases/002-hello-reactLynx/index.tsx(1 hunks)benchmark/react/cases/003-hello-list/index.tsx(1 hunks)benchmark/react/cases/004-various-update/index.tsx(1 hunks)benchmark/react/cases/005-load-script/index.tsx(1 hunks)benchmark/react/lynx.config.js(4 hunks)benchmark/react/package.json(1 hunks)benchmark/react/plugins/pluginScriptLoad.mjs(1 hunks)benchmark/react/rspeedy-env.d.ts(1 hunks)benchmark/react/src/dummyRoot.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- benchmark/react/cases/004-various-update/index.tsx
- benchmark/react/src/dummyRoot.tsx
- benchmark/react/cases/002-hello-reactLynx/index.tsx
- benchmark/react/cases/003-hello-list/index.tsx
- benchmark/react/cases/005-load-script/index.tsx
- benchmark/react/lynx.config.js
- benchmark/react/rspeedy-env.d.ts
- benchmark/react/plugins/pluginScriptLoad.mjs
- benchmark/react/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build / Build (Windows)
- GitHub Check: build / Build (Ubuntu)
- GitHub Check: test-rust / clippy
- GitHub Check: test-rust / Test (Ubuntu)
🔇 Additional comments (1)
benchmark/react/cases/001-fib/index.ts (1)
9-15: Load-after-script deferral looks correctKeeping the MAIN_THREAD guard inside the runAfterLoadScript callback is the right placement for avoiding worker-side execution. Please confirm pluginScriptLoad flush order guarantees the callback runs exactly once after injection.
Summary by CodeRabbit
New Features
Chores
Notes
Checklist