feat: add Windows native capture and cursor pipeline#217
feat: add Windows native capture and cursor pipeline#217EtienneLescot wants to merge 38 commits intosiddharthvaddem:mainfrom
Conversation
b94ffca to
a5d52d8
Compare
36081e4 to
e8e6a95
Compare
|
Just wanted to thank you for working on this and helping contribute to the project 🙏 I know this is a draft, but wanted to reiterate that I was never against Native Capture. We can have the cursor related settings hidden for Linux and Windows. If that helps. Let me know if there's any further clarification needed. Sorry if this feels like going back and forth Had a couple of folks on X reach out and say they really wanted this for macOS. No rush on this at all. Don't feel obliged. This is a pretty large change which would require a lot of testing to avoid bugs. This is what usually helps me (might have missed something): Capture + Launch
Audio
Editor Load + Playback
Timeline
Project Persistence
Other
|
|
@siddharthvaddem |
take your time 🙏 no rush. want to make sure it is implemented correctly even if it takes time. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a typed native bridge and Windows-native recording pipeline: bridge contracts, state store, services, IPC/preload wiring, renderer client and hooks, cursor recording sessions/adapters, a native wgc-capture helper (C++), audio/video/webcam capture + encoder, exporter and cursor-rendering integration, build/test scripts, and documentation. ChangesNative bridge + Windows-native recording (single DAG)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
|
ff8645e to
bc200cd
Compare
|
@EtienneLescot please close the PR if there are no plans on continuing work here. Appreciate all the work and help put in 🙏 |
@siddharthvaddem my goodness, time goes by so quickly! |
bc200cd to
ac41399
Compare
…and playback - Implement native bridge for Windows cursor capture via PowerShell/C# - Add cursor-free capture using getDisplayMedia with setDisplayMediaRequestHandler - Update video player and exporters to support native cursor telemetry - Enable system audio capture on Windows via WASAPI loopback - Add interpolation for smoother cursor movement in playback and export - Improve cursor scaling and visibility handling in editor and playback
00b6e83 to
e48dd9c
Compare
|
@siddharthvaddem I am working on it today ;-) |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/video-editor/VideoEditor.tsx (1)
1934-1954:⚠️ Potential issue | 🟠 Major | ⚡ Quick winpreview is ignoring the saved cursor highlight settings now.
VideoPlaybackstill falls back toDEFAULT_CURSOR_HIGHLIGHTand[]when the parent omitscursorHighlight/cursorClickTimestamps. Since this call site stopped passing both props, preview no longer reflects the editor state even though export still useseffectiveCursorHighlight.small wiring fix
cursorRecordingData={cursorRecordingData} trimRegions={trimRegions} speedRegions={speedRegions} annotationRegions={annotationOnlyRegions} @@ onBlurDataChange={handleBlurDataPreviewChange} onBlurDataCommit={commitState} cursorTelemetry={cursorTelemetry} + cursorHighlight={effectiveCursorHighlight} + cursorClickTimestamps={cursorClickTimestamps} showCursor={showCursor} cursorSize={cursorSize} cursorSmoothing={cursorSmoothing}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/video-editor/VideoEditor.tsx` around lines 1934 - 1954, The preview stopped reflecting saved cursor highlight because VideoPlayback is no longer being passed the cursorHighlight and cursorClickTimestamps props; update the VideoPlayback call (the component receiving props like cursorRecordingData, trimRegions, etc.) to pass cursorHighlight={effectiveCursorHighlight} and cursorClickTimestamps={cursorClickTimestamps} (or the corresponding state/prop names used in this file) so the preview uses the editor's effective settings rather than falling back to DEFAULT_CURSOR_HIGHLIGHT/[].
♻️ Duplicate comments (1)
electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts (1)
106-107:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReadiness failure can still orphan the helper process.
lowkey risky:
start()awaits readiness, but timeout/rejection only rejects the promise and doesn’t guarantee teardown. If caller drops the session on error, the helper can keep running.Suggested minimal fix
async start(): Promise<void> { @@ - await this.waitUntilReady(); + try { + await this.waitUntilReady(); + } catch (error) { + const normalizedError = + error instanceof Error ? error : new Error(String(error)); + this.failHelper(normalizedError); + throw normalizedError; + } } @@ private waitUntilReady() { return new Promise<void>((resolve, reject) => { this.readyResolve = resolve; this.readyReject = reject; this.readyTimer = setTimeout(() => { - this.rejectReady(new Error("Timed out waiting for Windows cursor helper readiness")); + this.failHelper( + new Error("Timed out waiting for Windows cursor helper readiness"), + ); }, READY_TIMEOUT_MS); }); }Also applies to: 249-255
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts` around lines 106 - 107, start() currently awaits waitUntilReady() but if that promise times out or rejects the helper process can be orphaned; change start() (and the other call site that awaits waitUntilReady around lines 249-255) to ensure cleanup on failure by wrapping the await in try/catch/finally (or using Promise.race/AbortController) and calling the session teardown method (e.g., this.stop(), this.dispose(), or the helper-kill routine) in the catch/finally path so the helper process is always terminated when readiness fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 154-159: cursorClickTimestamps has been hardcoded to an empty
array which disables click-driven emphasis on macOS; change it to derive
timestamps conditionally instead: keep the same inputs (videoSourcePath,
useCursorTelemetry, useCursorRecordingData) and compute cursorClickTimestamps
based on available data (prefer cursorRecordingData if present, otherwise
extract click events from cursorTelemetry), and only fall back to an empty array
when neither source provides click timestamps; update the derivation referenced
by effectiveCursorHighlight and preview/export paths so mac projects get the
platform-specific fallback rather than a hardcoded [] (use the symbols
cursorClickTimestamps, useCursorTelemetry, useCursorRecordingData,
cursorRecordingData, cursorTelemetry, videoSourcePath/fromFileUrl, and
effectiveCursorHighlight to locate and change the logic).
In `@src/components/video-editor/VideoPlayback.tsx`:
- Around line 382-390: The fallback that treats any positive video.currentTime
as the full duration in resolveCurrentDuration is unsafe (forceResolveDuration
sets currentTime to 0.1); change the check so we only accept currentTime as the
duration when playback has actually reached the end: i.e., require video.ended
=== true OR that video.duration is finite and video.currentTime >=
video.duration - EPSILON (or matches seekable end), then call onDurationChange;
otherwise ignore small positive currentTime values. Update
resolveCurrentDuration (and related logic that relies on
lastResolvedDurationRef/onDurationChange) to use this stricter check.
In `@src/lib/cursor/nativeCursor.ts`:
- Around line 291-315: getNativeCursorClickBounceProgress (and the similar
helper at lines 429-448) currently reverse-scans recordingData.samples on every
frame which causes O(samples × frames) perf; replace the per-frame linear tail
scan with a binary-search helper that finds the last sample with sample.timeMs
<= timeMs (or maintain a monotonic cursor index when iterating sequential
frames) and use that index to compute ageMs and the click-bounce value; update
resolveInterpolatedNativeCursorFrame and getNativeCursorClickBounceProgress to
call the new binary-search (or use the monotonic index) so semantics (handling
null/undefined recordingData, NATIVE_CURSOR_CLICK_ANIMATION_MS threshold, and
interactionType === "click") remain the same while avoiding repeated full
reverse scans called by drawNativeCursor and the VideoPlayback ticker.
In `@src/lib/exporter/frameRenderer.ts`:
- Around line 599-606: When projectNativeCursorToLocal(...) returns null we only
reset motion blur; also reset the cursor smoothing state so exports don't ease
in from stale off-screen positions. In the same branch where
resetNativeCursorMotionBlurState(this.nativeCursorMotionBlurState) is called,
also clear the smoothing state by calling the smoothing reset used elsewhere
(e.g. resetNativeCursorSmoothingState(this.nativeCursorSmoothingState) or
otherwise clear the smoothing buffer/last-position stored on
this.nativeCursorSmoothingState) so both motion blur and smoothing are reset
when the cursor falls outside the crop.
---
Outside diff comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 1934-1954: The preview stopped reflecting saved cursor highlight
because VideoPlayback is no longer being passed the cursorHighlight and
cursorClickTimestamps props; update the VideoPlayback call (the component
receiving props like cursorRecordingData, trimRegions, etc.) to pass
cursorHighlight={effectiveCursorHighlight} and
cursorClickTimestamps={cursorClickTimestamps} (or the corresponding state/prop
names used in this file) so the preview uses the editor's effective settings
rather than falling back to DEFAULT_CURSOR_HIGHLIGHT/[].
---
Duplicate comments:
In `@electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts`:
- Around line 106-107: start() currently awaits waitUntilReady() but if that
promise times out or rejects the helper process can be orphaned; change start()
(and the other call site that awaits waitUntilReady around lines 249-255) to
ensure cleanup on failure by wrapping the await in try/catch/finally (or using
Promise.race/AbortController) and calling the session teardown method (e.g.,
this.stop(), this.dispose(), or the helper-kill routine) in the catch/finally
path so the helper process is always terminated when readiness fails.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 99cef220-8320-4f1c-a307-ab49414c1eee
📒 Files selected for processing (11)
electron/native-bridge/cursor/recording/windowsNativeRecordingSession.script.tselectron/native-bridge/cursor/recording/windowsNativeRecordingSession.tselectron/native-bridge/cursor/recording/windowsNativeRecordingSession.types.tsscripts/test-windows-native-cursor.mjssrc/components/video-editor/VideoEditor.tsxsrc/components/video-editor/VideoPlayback.tsxsrc/lib/cursor/nativeCursor.tssrc/lib/exporter/frameRenderer.tssrc/lib/exporter/gifExporter.tssrc/lib/exporter/videoExporter.tssrc/native/contracts.ts
✅ Files skipped from review due to trivial changes (1)
- src/native/contracts.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/lib/exporter/videoExporter.ts
- scripts/test-windows-native-cursor.mjs
- electron/native-bridge/cursor/recording/windowsNativeRecordingSession.script.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
scripts/test-windows-native-cursor.mjs (2)
6-12: 💤 Low value
Number(process.env.X ?? default)will happily produceNaNand bake it into PowerShell.If anyone sets e.g.
CURSOR_TEST_SAMPLE_INTERVAL_MS=fastin CI, you getNaN, and thenStart-Sleep -Milliseconds NaN(line 322/469) fails inside PS, which throws under$ErrorActionPreference = 'Stop'and the sampler dies immediately with a not-super-obvious error. A tiny clamp/validate at the top would save someone a 2am debugging session.diff
-const SAMPLE_INTERVAL_MS = Number(process.env.CURSOR_TEST_SAMPLE_INTERVAL_MS ?? 25); -const DURATION_MS = Number(process.env.CURSOR_TEST_DURATION_MS ?? 1800); -const SCREEN_FRAME_INTERVAL_MS = Number(process.env.CURSOR_TEST_SCREEN_FRAME_INTERVAL_MS ?? 100); +function readPositiveIntEnv(name, fallback) { + const raw = process.env[name]; + if (raw === undefined) return fallback; + const parsed = Number(raw); + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error(`Invalid ${name}=${raw}; expected positive number`); + } + return Math.floor(parsed); +} + +const SAMPLE_INTERVAL_MS = readPositiveIntEnv("CURSOR_TEST_SAMPLE_INTERVAL_MS", 25); +const DURATION_MS = readPositiveIntEnv("CURSOR_TEST_DURATION_MS", 1800); +const SCREEN_FRAME_INTERVAL_MS = readPositiveIntEnv("CURSOR_TEST_SCREEN_FRAME_INTERVAL_MS", 100);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/test-windows-native-cursor.mjs` around lines 6 - 12, The numeric environment parsing for SAMPLE_INTERVAL_MS, DURATION_MS, SCREEN_FRAME_INTERVAL_MS (and READY_TIMEOUT_MS if desired) can yield NaN when env vars are non-numeric; validate and clamp each parsed value immediately after creation (e.g., check Number.isFinite(value) and fall back to the intended default or a safe minimum/maximum) and emit a warning if an env value was invalid so downstream PowerShell calls (Start-Sleep) never receive NaN; update the initialization around SAMPLE_INTERVAL_MS, DURATION_MS, SCREEN_FRAME_INTERVAL_MS to perform these checks and fallback logic and ensure OUTPUT_DIR handling remains unchanged.
476-492: 💤 Low value
waitForReadypolls instead of resolving from the stream; nit: cleaner.The current 25ms
setIntervalover a sharedeventsarray works, but it's a polling loop tied to a buffer that's mutated elsewhere. A small "ready" promise resolved directly fromonStdoutwould remove the timer/race-y interplay and make the timeout the only wall-clock concern. Totally optional — current code is correct, just kinda cursed at 2am.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/test-windows-native-cursor.mjs` around lines 476 - 492, Replace the polling loop in waitForReady with a promise that is resolved directly when the stdout handler pushes a "ready" event: change waitForReady to return a new Promise that sets a single timeout (setTimeout) to reject after READY_TIMEOUT_MS and exposes a local resolve function; remove setInterval. In the stdout handler (the function that currently mutates events, e.g., onStdout or wherever events.push(...) happens) call that resolve when you detect an event.type === "ready" so the promise resolves immediately; ensure the timeout is cleared on resolve to avoid leaks. Keep the existing rejection message and behavior but switch polling to this direct-resolution pattern.electron/native-bridge/cursor/recording/windowsNativeRecordingSession.types.ts (1)
4-21: 💤 Low valuenit:
assetshould beWindowsCursorAssetPayload | nullfor consistency.The PowerShell sampler emits
asset = $nullon every sample where no asset is captured (seescripts/test-windows-native-cursor.mjsline 332/360), soJSON.parsewill produceasset: nullrather than absent.boundson line 19 already correctly allows| null, butasseton line 20 is just optional. Consumers happen to use optional chaining (payload.asset?.id) so it works, but the type lowkey lies about what comes off the wire.diff
- asset?: WindowsCursorAssetPayload; + asset?: WindowsCursorAssetPayload | null;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@electron/native-bridge/cursor/recording/windowsNativeRecordingSession.types.ts` around lines 4 - 21, The WindowsCursorSampleEvent type's asset field is declared optional but should explicitly allow null; update the WindowsCursorSampleEvent interface to change the asset property from optional (asset?) to a non-optional field typed as WindowsCursorAssetPayload | null (i.e., asset: WindowsCursorAssetPayload | null) so it matches incoming JSON from the PowerShell sampler, and then grep for uses of WindowsCursorSampleEvent.payload.asset to ensure callers handle null (or adjust code to use optional chaining if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/test-windows-native-cursor.mjs`:
- Around line 1025-1044: The current findPlaywrightChromiumExecutable uses
lexicographic sort on chromium-* directory names which misorders numeric
revisions; change the candidate selection to extract the numeric revision from
each entry.name (the suffix after "chromium-"), convert to integer (handle
non-numeric safely), sort candidates by that numeric revision descending, then
pick the highest numeric candidate (falling back to defaultPath). Update the
mapping/filtration logic around candidates in findPlaywrightChromiumExecutable
so it pairs each path with its parsed revision number, sorts by that number (not
by string), and returns the highest-version path or defaultPath if none valid.
- Around line 1100-1118: The handler that parses stdout lines calls
JSON.parse(trimmed) directly which can throw and crash the sampler; wrap that
parse in a try/catch (or otherwise validate the trimmed string is valid JSON)
inside the stdout data handler so malformed or non-JSON lines are skipped (and
optionally logged with the `[cursor-native-test]` prefix), and only push to
events/update assets when parsing succeeds — reference the JSON.parse(trimmed)
call, the events array, and the assets Map in the stdout handler and mirror the
defensive filtering used in onStderr for non-JSON/CLIXML lines.
---
Nitpick comments:
In
`@electron/native-bridge/cursor/recording/windowsNativeRecordingSession.types.ts`:
- Around line 4-21: The WindowsCursorSampleEvent type's asset field is declared
optional but should explicitly allow null; update the WindowsCursorSampleEvent
interface to change the asset property from optional (asset?) to a non-optional
field typed as WindowsCursorAssetPayload | null (i.e., asset:
WindowsCursorAssetPayload | null) so it matches incoming JSON from the
PowerShell sampler, and then grep for uses of
WindowsCursorSampleEvent.payload.asset to ensure callers handle null (or adjust
code to use optional chaining if needed).
In `@scripts/test-windows-native-cursor.mjs`:
- Around line 6-12: The numeric environment parsing for SAMPLE_INTERVAL_MS,
DURATION_MS, SCREEN_FRAME_INTERVAL_MS (and READY_TIMEOUT_MS if desired) can
yield NaN when env vars are non-numeric; validate and clamp each parsed value
immediately after creation (e.g., check Number.isFinite(value) and fall back to
the intended default or a safe minimum/maximum) and emit a warning if an env
value was invalid so downstream PowerShell calls (Start-Sleep) never receive
NaN; update the initialization around SAMPLE_INTERVAL_MS, DURATION_MS,
SCREEN_FRAME_INTERVAL_MS to perform these checks and fallback logic and ensure
OUTPUT_DIR handling remains unchanged.
- Around line 476-492: Replace the polling loop in waitForReady with a promise
that is resolved directly when the stdout handler pushes a "ready" event: change
waitForReady to return a new Promise that sets a single timeout (setTimeout) to
reject after READY_TIMEOUT_MS and exposes a local resolve function; remove
setInterval. In the stdout handler (the function that currently mutates events,
e.g., onStdout or wherever events.push(...) happens) call that resolve when you
detect an event.type === "ready" so the promise resolves immediately; ensure the
timeout is cleared on resolve to avoid leaks. Keep the existing rejection
message and behavior but switch polling to this direct-resolution pattern.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 42c3641f-1ebe-441e-a7cf-1ec0a2f42a1e
📒 Files selected for processing (4)
electron/native-bridge/cursor/recording/windowsNativeRecordingSession.script.tselectron/native-bridge/cursor/recording/windowsNativeRecordingSession.tselectron/native-bridge/cursor/recording/windowsNativeRecordingSession.types.tsscripts/test-windows-native-cursor.mjs
✅ Files skipped from review due to trivial changes (1)
- electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- electron/native-bridge/cursor/recording/windowsNativeRecordingSession.script.ts
|
Addressed the non-inline nitpicks from the latest CodeRabbit summary in
Validation run:
|
|
Addressed the two latest non-inline CodeRabbit notes in
Validation run:
|
|
Addressed the older unresolved CodeRabbit thread group in
One thread was verified as not applicable: the E2E native bridge mock for Validation run:
|
|
@siddharthvaddem all tests ok on my side on windows. |
| Developer notes: | ||
| - [Windows native cursor test pipeline](docs/testing/windows-native-cursor.md) | ||
|
|
|
madlad 🐐 taking a look |
|
@EtienneLescot few regressions I noticed that affect macos and Linux I also see you replaced
It adds an additional cursor on macOS - should this not be gated to just Windows? |
|
can we also restore the stream-copy fast path when |
|
noticed you also switched audio codec from opus to aac with no fallback |
|
can we avoid native bridge also intializing on macos/linux unconditionally if this is only going to be used by windows we also dont want the electron builder to ship windows native binaries into all packages |
|
|
@siddharthvaddem Addressed the stream-copy fast path as well:
Validated with targeted Biome checks, the new EDIT: It breaks the cursor feature, so I will take a deeper look. |
|
@siddharthvaddem I restored the source-copy fast path for no-op MP4 exports. To make this compatible with the Windows native cursor work, I added a pre-recording cursor mode toggle in the launch HUD. This button is currently Windows-only. The reason is that the fast path copies the original MP4 without re-encoding, so it cannot be used when we need to inject the editable/magnified cursor overlay during export. On Windows, users can now choose before recording:
When system cursor mode is used, and the project remains eligible for source-copy export, for example full-screen/source-size export, no padding, no crop, no webcam overlay, no trims/zoom/speed/annotations/effects, the export can use the fast path again. |

Description
This PR adds a Windows-only native recording path for OpenScreen. The goal is to avoid the preview/export cursor drift caused by splitting capture, cursor telemetry, audio, webcam, and muxing across Electron/browser APIs.
On Windows, recordings are routed through a bundled native Windows Graphics Capture helper. macOS and Linux behavior is intentionally unchanged in this PR.
What changed
electron/native/bin/win32-x64/wgc-capture.exe.asarUnpack.Windows-only scope
This PR does not add native capture for macOS or Linux. Windows-only cursor/capture controls are hidden outside Windows where applicable.
Linux remains on the existing Electron capture path. macOS behavior is not changed by this PR.
Validation run
Passed locally on Windows:
npm run build-vitenpm run build:winnpm run test:e2e:windows-native-checklistnpm run test:cursor-native:winnpm run test:wgc-helper:winnpm run test:wgc-window:winnpm run test:wgc-audio:winnpm run test:wgc-mic:winnpm run test:wgc-mixed-audio:winnpm run test:wgc-webcam:winOPENSCREEN_WGC_TEST_WEBCAM_DEVICE_NAME="NVIDIA Broadcast" npm run test:wgc-webcam:winOPENSCREEN_WGC_TEST_WEBCAM_DEVICE_NAME="NVIDIA Broadcast" npm run test:wgc-full:winnpx vitest --run src/lib/cursorTelemetryBuffer.test.tsKnown repo-level checks still needing separate cleanup:
npm run lintcurrently reports broad existing Biome formatting/line-ending issues outside the native-capture work.npm testhas existing failures in blur effects, tutorial translations, and browser exporter fixture/canvas handling.npm run test:e2ecurrently times out intests/e2e/gif-export.spec.ts.Maintainer test checklist
Capture + Launch
Audio
Editor Load + Playback
Timeline
Project Persistence
Other
Notes for reviewers
The checked items above are the paths covered by local Windows automation and manual iteration on this branch. The unchecked items are still useful manual QA targets before considering the PR ready to merge.
Summary by CodeRabbit
New Features
Documentation
Tests
Chores