Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
203 commits
Select commit Hold shift + click to select a range
7216f25
feat: add video transcriptions (in-browser auto captions)
parse-nip May 13, 2026
1c431fe
fix: improve auto-caption timing and pause handling
parse-nip May 14, 2026
2b7e6f1
fix: address PR review for auto captions and locales
parse-nip May 14, 2026
3a48e54
fix: time-slice single-group phrase captions into lines
parse-nip May 14, 2026
5417581
merge: upstream main into feature/video-transcriptions
parse-nip May 14, 2026
a0bcd9f
feat(editor): polish auto-caption loading toasts
parse-nip May 15, 2026
2ee00a7
fix(captioning): narrow ORT node shim, trim retries, and abort checks
parse-nip May 15, 2026
ea09fdc
test: expose source selector hooks
AjTheSpidey May 21, 2026
2349272
feat: add webcam mirror toggle
AjTheSpidey May 21, 2026
2cf09a8
fix: polish webcam mirror toggle
AjTheSpidey May 22, 2026
205ea55
fix: tidy webcam mirror settings
AjTheSpidey May 25, 2026
7cf78fe
feat: Add projectFolder to user preferences
paulohenriquesg May 29, 2026
68b26c2
Merge upstream/main into feat/remember-last-project-folder
paulohenriquesg May 31, 2026
0ab0859
Merge remote-tracking branch 'origin/main' into feature/video-transcr…
siddharthvaddem May 31, 2026
53be8df
docs: add macOS native cursor capture test pipeline
kaili-yang Jun 1, 2026
b56daae
Merge upstream main into webcam mirror toggle
AjTheSpidey Jun 1, 2026
a18dc39
Merge upstream main into source selector test hooks
AjTheSpidey Jun 1, 2026
a280179
docs: fix three inaccuracies flagged by bot reviews
kaili-yang Jun 1, 2026
c01c88b
Merge upstream/main into feat/remember-last-project-folder
paulohenriquesg Jun 1, 2026
f5ad566
fix: pass getProjectFolder to loadProjectFile in EditorEmptyState
paulohenriquesg Jun 1, 2026
9a83062
Merge pull request #633 from AjTheSpidey/codex/webcam-mirror-toggle
siddharthvaddem Jun 2, 2026
6873b80
Merge pull request #631 from AjTheSpidey/codex/source-selector-test-h…
siddharthvaddem Jun 2, 2026
94a00c0
Merge pull request #673 from kaili-yang/docs/mac-native-cursor
siddharthvaddem Jun 2, 2026
e5462f1
captions: run Whisper in a Web Worker, move auto-captions button to t…
siddharthvaddem Jun 2, 2026
4ce7af4
Merge pull request #589 from parse-nip/feature/video-transcriptions
siddharthvaddem Jun 2, 2026
b8eeef5
fix: resolve cursor-sampler telemetry not working
jodelcioluz Jun 3, 2026
a19771e
Merge pull request #681 from jodelcioluz/fix-cursor-telemetry
siddharthvaddem Jun 3, 2026
970406a
Merge pull request #669 from paulohenriquesg/feat/remember-last-proje…
siddharthvaddem Jun 3, 2026
c35a899
fix vertical layout
siddharthvaddem Jun 3, 2026
4a18361
fix ci check
siddharthvaddem Jun 3, 2026
43b01cf
record permissions prompt
siddharthvaddem Jun 4, 2026
1d7b677
clip tooltio
siddharthvaddem Jun 4, 2026
a371658
global auto toggle
siddharthvaddem Jun 4, 2026
62f4f45
fix variant class
siddharthvaddem Jun 4, 2026
332b6b7
rm blur
siddharthvaddem Jun 4, 2026
0e3bd17
improve waveform
siddharthvaddem Jun 4, 2026
98a3237
fix export compoisted shadow
siddharthvaddem Jun 4, 2026
b088a09
custom cursors
siddharthvaddem Jun 5, 2026
a806e34
reactive zoom
siddharthvaddem Jun 5, 2026
e49cc02
improved cursor smoothing
siddharthvaddem Jun 5, 2026
02b4e1b
cleanup follow effect
siddharthvaddem Jun 5, 2026
358d4af
remove oversmooth
siddharthvaddem Jun 5, 2026
96e178c
fix toggles
siddharthvaddem Jun 5, 2026
c03db08
version bump
siddharthvaddem Jun 5, 2026
c6331ba
fix(build): use sharp prebuilt instead of compiling from source (fixe…
siddharthvaddem Jun 5, 2026
3e3a816
bundle tts
siddharthvaddem Jun 6, 2026
53f9851
fix auto focus export sync
siddharthvaddem Jun 6, 2026
c539e9b
cleanup
siddharthvaddem Jun 6, 2026
5f71979
helper fix
siddharthvaddem Jun 6, 2026
21c637a
fix 2
siddharthvaddem Jun 6, 2026
cc8307f
final readme
siddharthvaddem Jun 6, 2026
ffc41a3
--
siddharthvaddem Jun 6, 2026
d46d37f
chore: bump nix package to v1.5.0
github-actions[bot] Jun 6, 2026
71622a2
Merge pull request #690 from siddharthvaddem/chore/bump-nix-1.5.0
siddharthvaddem Jun 6, 2026
b7ac502
Update repository links for maintained fork
EtienneLescot Jun 15, 2026
1a17f8b
ci: prepare v1.5.0 release pipeline
EtienneLescot Jun 15, 2026
b2d59f4
Add merged PR issue bookkeeping
EtienneLescot Jun 19, 2026
48dfe74
Chain recording after source selection
EtienneLescot Jun 19, 2026
1668cf4
Address merged issue bookkeeping review
EtienneLescot Jun 19, 2026
6d02237
Add official OpenScreen links
EtienneLescot Jun 19, 2026
2cb982b
Refine official download guidance
EtienneLescot Jun 19, 2026
3aace18
Move official links near installation
EtienneLescot Jun 19, 2026
ff092fa
Make Discord PR sync optional
EtienneLescot Jun 19, 2026
5ed545b
Avoid failing PRs on Discord sync errors
EtienneLescot Jun 19, 2026
eb04f4e
Fix issue 3 app launch and UI bugs
EtienneLescot Jun 19, 2026
59cfe26
Guard duplicate HUD creation
EtienneLescot Jun 19, 2026
7790453
Fix duplicate app instances
EtienneLescot Jun 19, 2026
f38c881
Address source selector review feedback
EtienneLescot Jun 19, 2026
4a90d6c
Harden source selector rejection test
EtienneLescot Jun 19, 2026
c2d56ab
Fix video export stall when trim regions cause long decoder gaps
m8i-51 Jun 1, 2026
22db7fa
Add regression test for encoder stall timeout
m8i-51 Jun 19, 2026
8ced98d
Remove redundant stall-timeout test, clarify regression rationale
m8i-51 Jun 19, 2026
b946f07
chore: bootstrap Mavis agent team for the repo
EtienneLescot Jun 22, 2026
3e954cf
chore: address CodeRabbit review feedback on agent bootstrap
EtienneLescot Jun 22, 2026
3dc7aa3
fix(harness): correct native helper and i18n locale paths in agent docs
EtienneLescot Jun 22, 2026
cb97db6
chore(agents): remove Mavis-specific 'Agent team' section from AGENTS.md
EtienneLescot Jun 22, 2026
8246d07
Keep HUD interactive on Linux so the drag handle can receive pointer …
EtienneLescot Jun 22, 2026
93ab891
fix(wgc-capture): pre-flight H.264 MFT check + actionable error
EtienneLescot Jun 22, 2026
dcf9a2a
refactor(wgc-capture): log MFTEnumEx HRESULT and rename mediaType param
EtienneLescot Jun 22, 2026
0819961
fix(wgc-capture): drop pre-flight H.264 MFT check, keep sink-failure …
EtienneLescot Jun 22, 2026
897e45c
docs: add Discord badge, tech/platform badges, and Need help link
Jun 24, 2026
bcb5869
docs: move Need help link into a Community section at the bottom
EtienneLescot Jun 24, 2026
7eb3b60
docs: link GitHub Issues in Community section
EtienneLescot Jun 24, 2026
6625485
docs+ci: add public ROADMAP.md, README link, and #🗺️・roadmap auto-post
EtienneLescot Jun 24, 2026
d0f7254
ci(discord): post roadmap changes via openscreen_etienne bot instead …
EtienneLescot Jun 24, 2026
5feac09
docs(roadmap): link #🗺️・roadmap to the Discord channel
EtienneLescot Jun 24, 2026
cb3ac98
ci(discord): sync ROADMAP.md to a pinned message in #🗺️・roadmap
EtienneLescot Jun 24, 2026
506f7c0
fix(ci): use context.payload.after for push event SHA in roadmap-notify
EtienneLescot Jun 24, 2026
662b429
docs(roadmap): smoke test for discord pinned-message sync
EtienneLescot Jun 24, 2026
3f965bd
fix(ci): account for truncation note length when slicing ROADMAP.md c…
EtienneLescot Jun 24, 2026
42ea07a
docs(roadmap): second smoke test after fixing embed truncation
EtienneLescot Jun 24, 2026
9390e9a
docs(roadmap): third smoke test - verify PATCH path
EtienneLescot Jun 24, 2026
ee451fe
fix(ci): make pin attempt self-healing on every sync (idempotent)
EtienneLescot Jun 24, 2026
89dd64c
chore(ci): drop unused didPost variable
EtienneLescot Jun 24, 2026
6d8765e
refactor(ci): use Discord pins as the persistent state, not a variable
EtienneLescot Jun 24, 2026
eaa972a
docs(roadmap): fourth smoke test - pin-as-state refactor
EtienneLescot Jun 24, 2026
46e8092
docs(roadmap): fifth smoke test - pin permission granted
EtienneLescot Jun 24, 2026
c71767d
docs(roadmap): sixth smoke test - variable deleted, pin-as-state pure
EtienneLescot Jun 24, 2026
ce558b7
docs(roadmap): seventh smoke test via PR merge
EtienneLescot Jun 24, 2026
6c2fdd1
docs(roadmap): add 'Site & documentation' tier; clean smoke-test noise
EtienneLescot Jun 25, 2026
8176f47
feat(docs): scaffold Docusaurus 3 site + GitHub Pages CI
EtienneLescot Jun 25, 2026
09f6348
fix(wgc): capture per-step stop timing, bump timeout, add diagnostic …
EtienneLescot Jun 26, 2026
ea9bd62
fix(diagnostic-tool): address CodeRabbit review
EtienneLescot Jun 26, 2026
a6d8af3
style(website): biome auto-format for cce4524 website scaffold
EtienneLescot Jun 26, 2026
8f1134e
ci(docs): pin actions to commit SHAs in docs.yml
EtienneLescot Jun 26, 2026
ef262d0
Revert "ci(docs): pin actions to commit SHAs in docs.yml"
EtienneLescot Jun 26, 2026
9a07706
Revert "style(website): biome auto-format for cce4524 website scaffold"
EtienneLescot Jun 26, 2026
af06dd2
Revert "feat(docs): scaffold Docusaurus 3 site + GitHub Pages CI"
EtienneLescot Jun 26, 2026
3ef2001
fix(macos): correct cursor offset in single-window capture
giulio333 Jun 23, 2026
1790ccb
refactor(macos): address review — drop captureBounds validator and re…
giulio333 Jun 27, 2026
5293b99
First implementation of copy
davide-dona Jun 25, 2026
33cf5c3
feat(video-editor): region copy/paste and shared text-editing guard
davide-dona Jun 26, 2026
86a08df
Update blur handling
davide-dona Jun 26, 2026
7a6ccd3
fix(video-editor): address CodeRabbit review of region clipboard
EtienneLescot Jun 28, 2026
3a81f55
fix(launch): resize HUD overlay to fit system language prompt
EtienneLescot Jun 26, 2026
0b8230b
fix(launch): backfill prompt observer and make sizing test deterministic
EtienneLescot Jun 26, 2026
b655acf
refactor(launch): drop hardcoded top-8 and trim prompt test bloat
EtienneLescot Jun 28, 2026
17aa4a2
refactor(ci): Global refactoring of pipeline workflows while addressi…
psychosomat Jun 26, 2026
a1535c6
fix(ci): address all 11 CodeRabbit review findings
psychosomat Jun 27, 2026
2a4ab80
fix(security): harden thread channel validation in discord-pr-sync
psychosomat Jun 27, 2026
7216a7e
fix(ci): address maintainer review feedback
psychosomat Jun 27, 2026
8628801
fix(security): add AbortController timeout to Discord thread validation
psychosomat Jun 27, 2026
f6b81e7
fix(ci): install makepkg package, not arch-install-scripts
EtienneLescot Jun 28, 2026
da4e1ca
feat(ci): release candidate pipeline (prerelease + promote workflows)
EtienneLescot Jun 30, 2026
9c93d9d
fix(ci): use OPENSCREEN_RELEASE_TOKEN for git push to bypass repo rul…
EtienneLescot Jun 30, 2026
1ac88ac
fix(ci): add origin argument to git remote set-url
EtienneLescot Jun 30, 2026
1c5b9df
fix(ci): drop git remote set-url workaround now that github-actions[b…
EtienneLescot Jun 30, 2026
b3c42fb
fix(ci): push to release branch and rebase-merge via PR
EtienneLescot Jun 30, 2026
d8a25af
fix(ci): pass token via GH_TOKEN env var, not --token flag
EtienneLescot Jun 30, 2026
c5e2157
fix(ci): delete remote release branch first for idempotent reruns
EtienneLescot Jun 30, 2026
4d8cfa8
fix(ci): use GITHUB_TOKEN for PR create (has pull_requests write), PA…
EtienneLescot Jun 30, 2026
83823fe
fix(ci): grant pull-requests: write to workflow so GITHUB_TOKEN can c…
EtienneLescot Jun 30, 2026
bd1cb9a
fix(ci): use PAT (EtienneLescot) for both PR create and merge
EtienneLescot Jun 30, 2026
35f7480
docs(secrets): update PAT perms and ruleset section
EtienneLescot Jun 30, 2026
ebf8449
fix(ci): --delete-branch-remote is --delete-branch
EtienneLescot Jun 30, 2026
c20c4ee
fix(ci): add --admin flag to gh pr merge to bypass branch protection
EtienneLescot Jun 30, 2026
3d2e4b0
chore(release): bump to 1.5.1-rc.99 [skip ci]
github-actions[bot] Jun 30, 2026
44d05c2
fix(ci): fetch main and tag the merged commit, not the local branch tip
EtienneLescot Jun 30, 2026
55de60d
Revert "chore(release): bump to 1.5.1-rc.99 [skip ci]"
EtienneLescot Jun 30, 2026
5f772b9
chore(release): bump to 1.5.1-rc.99 [skip ci]
github-actions[bot] Jun 30, 2026
660786b
fix(ci): compare package.json to full tag version, not just the stabl…
EtienneLescot Jun 30, 2026
dfcf7dc
fix(ci): use GITHUB_TOKEN for tag push to trigger build.yml downstream
EtienneLescot Jun 30, 2026
6cc5905
Revert "chore(release): bump to 1.5.1-rc.99 [skip ci]"
EtienneLescot Jun 30, 2026
dcd9844
chore(release): bump to 1.5.1-rc.99 [skip ci]
github-actions[bot] Jun 30, 2026
f07ae02
fix(ci): delete remote tag first for idempotent reruns
EtienneLescot Jun 30, 2026
2683729
fix(ci): use Discord webhooks for release announce (works with forum …
EtienneLescot Jun 30, 2026
990cead
fix(ci): use bot API (POST /channels/{forum}/threads) for forum channels
EtienneLescot Jun 30, 2026
36a0807
refactor(ci): use shared discord-bot-api helper for release announce
EtienneLescot Jun 30, 2026
bb14340
Revert "chore(release): bump to 1.5.1-rc.99 [skip ci]"
EtienneLescot Jun 30, 2026
9517757
chore(release): bump to 1.5.1-rc.88 [skip ci]
github-actions[bot] Jun 30, 2026
6cbf62a
fix(ci): trigger build.yml explicitly after tag push
EtienneLescot Jun 30, 2026
4de5728
Revert "chore(release): bump to 1.5.1-rc.88 [skip ci]"
EtienneLescot Jun 30, 2026
56250ed
chore(release): bump to 1.5.1-rc.77 [skip ci]
github-actions[bot] Jun 30, 2026
c4af211
docs(secrets): document Actions and Workflows PAT permissions
EtienneLescot Jun 30, 2026
dd20a05
Revert "chore(release): bump to 1.5.1-rc.77 [skip ci]"
EtienneLescot Jun 30, 2026
996524d
chore(release): bump to 1.5.1-rc.66 [skip ci]
github-actions[bot] Jun 30, 2026
0203c2f
fix(ci): use --notes-start-tag to anchor auto-notes to the SemVer pre…
EtienneLescot Jun 30, 2026
7ef192f
Revert "chore(release): bump to 1.5.1-rc.66 [skip ci]"
EtienneLescot Jun 30, 2026
4ee17bd
chore(release): bump to 1.5.1-rc.55 [skip ci]
github-actions[bot] Jun 30, 2026
e4ff683
Revert "chore(release): bump to 1.5.1-rc.55 [skip ci]"
EtienneLescot Jun 30, 2026
5d7248c
chore(release): bump to 1.6.0-rc.1 [skip ci]
github-actions[bot] Jun 30, 2026
f13191d
refactor(ci): switch Discord automation from webhooks to single bot
EtienneLescot Jun 28, 2026
6f2cf86
fix(ci): restore mention suppression and patch resilience after webho…
EtienneLescot Jun 28, 2026
c103436
test(ci): collapse discord-bot-api tests via describe.each
EtienneLescot Jun 28, 2026
72cb541
fix(ci): address CodeRabbit review on discord-bot-api
EtienneLescot Jun 30, 2026
2fc116b
docs(ai-edition): add HANDOFF.md with implementation summary, file ma…
EtienneLescot Jul 1, 2026
1815a60
Feat: Added lint Button
Itzadetunji Jun 26, 2026
9711a6c
Notes is working, and not showing in the screen recorder
Itzadetunji Jun 26, 2026
ad91389
Refactor: Update URL parameters for notes window
Itzadetunji Jun 26, 2026
33dedd6
Refactor: Update NotesWindow and createNotesWindow for improved UI
Itzadetunji Jun 26, 2026
2819a17
Refactor: Adjust dimensions and local storage handling in NotesWindow
Itzadetunji Jun 26, 2026
ed00131
Refactor: Update window handling and title for NotesWindow
Itzadetunji Jun 28, 2026
48d3a1d
Refactor: Replace useEffect with useLayoutEffect in NotesWindow
Itzadetunji Jun 28, 2026
605e18e
Refactor: Remove unused allowScripts section from package.json
Itzadetunji Jun 28, 2026
b37efc6
Refactor: Update NotesWindow integration and improve accessibility
Itzadetunji Jun 28, 2026
4ed6b35
Refactor: Simplify NotesWindow rendering in App component
Itzadetunji Jun 28, 2026
fcdcecc
Chore: Removed redundant code for the app.tsx resolving coderabbit's …
Itzadetunji Jul 2, 2026
f695b4c
Chore: Upgrade to Tiptap for enhanced note-taking functionality
Itzadetunji Jul 3, 2026
b3470e2
Refactor: Adjust NotesWindow dimensions and update styling
Itzadetunji Jul 3, 2026
8ce5bd0
Enhance NotesToolbar with tooltips and localization support
Itzadetunji Jul 3, 2026
34d687c
Refactor: Clean up NotesWindow content handling
Itzadetunji Jul 3, 2026
2c1e036
fix: restore README screenshots and localize image read errors
Tamsi Jul 1, 2026
0f6c85f
refactor(i18n): drop duplicate annotation.failedImageUpload key
EtienneLescot Jul 4, 2026
f6e1bf8
chore(release): bump to 1.6.0 [skip ci]
github-actions[bot] Jul 5, 2026
33f6eb8
chore: bump nix package to v1.6.0
github-actions[bot] Jul 5, 2026
8e6f949
ci(release): freeze release branches between RC cut and stable promote
EtienneLescot Jul 5, 2026
80cb2b9
docs(release): document frozen release branches and the v1.6.0 incident
EtienneLescot Jul 5, 2026
e685a43
fix(recording): pause/resume webcam recorder on native Windows (#45)
EtienneLescot Jun 30, 2026
a945de1
docs(roadmap): add blur regions restoration to stability tier
EtienneLescot Jul 6, 2026
dfdcbc6
fix: stream >2GB recordings into OPFS so long videos can export
rainyflash Jul 6, 2026
a0c0875
fix: guard OPFS source cache and surface copy progress
rainyflash Jul 6, 2026
53589d0
fix: key OPFS cache refcount by cache entry, not source URL
rainyflash Jul 6, 2026
3f07f3c
fix: lower in-memory source threshold and skip waveform for huge files
rainyflash Jul 6, 2026
a977187
fix: address review — prune race, chunk cap, MIME, streaming waveform
rainyflash Jul 6, 2026
654375b
fix: abortable, deduplicated OPFS copies; harden waveform and caption…
rainyflash Jul 6, 2026
4613c28
feat: implement optimized webm patching and saving state
EtienneLescot Jul 6, 2026
411171c
fix: address CodeRabbit review feedback
EtienneLescot Jul 6, 2026
7f025c8
fix(export): project native cursor onto cropped rect, not mask rect
EtienneLescot Jul 6, 2026
c1320bd
fix(export): address review feedback
EtienneLescot Jul 7, 2026
b31bb71
fix(cursor): normalize sampler position against physical bounds
EtienneLescot Jul 7, 2026
b67811f
fix(cursor): use screen.dipToScreenRect for multi-monitor origin
EtienneLescot Jul 7, 2026
f686b55
chore: retrigger CI after workflow fixes
cursoragent Jul 11, 2026
157277d
chore: merge main and drop legacy discord.yaml in favor of upstream w…
cursoragent Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @siddharthvaddem
* @EtienneLescot
11 changes: 11 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Setup Node.js
description: Install Node 22, restore npm cache, run npm ci
runs:
using: composite
steps:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
shell: bash
75 changes: 34 additions & 41 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,36 @@
# Pull Request Template

## Description
<!-- Briefly describe the purpose of this PR. -->

## Motivation
<!-- Explain why this change is needed. What problem does it solve? -->

## Type of Change
- [ ] New Feature
- [ ] Bug Fix
- [ ] Refactor / Code Cleanup
- [ ] Documentation Update
- [ ] Other (please specify)

## Related Issue(s)
<!-- Link to any related issue(s) (e.g., #123) -->

## Screenshots / Video
<!-- Include screenshots or a short video demonstrating the change. If the change adds a new UI feature, attach an image. If it adds functionality best shown via video, embed a video. -->

**Screenshot** (if applicable):

```markdown
![Screenshot Description](path/to/screenshot.png)
```

**Video** (if applicable):

```html
<video src="path/to/video.mp4" controls width="600"></video>
```
## Summary
<!-- Briefly describe the change. -->

## Related issue
<!-- Use "Fixes #123", "Closes #123", or "Resolves #123" only when this PR fully resolves the issue. -->
<!-- Use "Refs #123", "Related to #123", or "Part of #123" when this PR is partial and should not close the issue. -->

Fixes #

## Type of change
- [ ] Bug fix
- [ ] Feature
- [ ] Enhancement
- [ ] Documentation
- [ ] Refactor / maintenance
- [ ] Performance
- [ ] Security

## Release impact
- [ ] Patch
- [ ] Minor
- [ ] Major / breaking change
- [ ] No release note needed

## Desktop impact
- [ ] Windows
- [ ] macOS
- [ ] Linux
- [ ] Installer / packaging
- [ ] Not platform-specific

## Screenshots / video
<!-- Include screenshots or a short video for UI or visual changes. -->

## Testing
<!-- Describe how reviewers can test the changes. Include steps, commands, or environment setup. -->

## Checklist
- [ ] I have performed a self-review of my code.
- [ ] I have added any necessary screenshots or videos.
- [ ] I have linked related issue(s) and updated the changelog if applicable.

---
*Thank you for contributing!*
<!-- Describe how this was tested. Include commands, steps, or environment setup. -->
51 changes: 51 additions & 0 deletions .github/scripts/discord-bot-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { warning } from "@actions/core";

const API_BASE = "https://discord.com/api/v10";
const DEFAULT_TIMEOUT_MS = 5_000;

async function callDiscord(botToken, method, path, body, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
let res;
try {
res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
Authorization: `Bot ${botToken}`,
"Content-Type": "application/json",
},
body: body !== undefined ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
} finally {
clearTimeout(timeout);
}

if (res.status === 429) {
const txt = await res.text();
warning(`Discord rate-limited (429) on ${method} ${path}: ${txt}`);
throw new Error(`Discord rate-limited (429) on ${method} ${path}`);
}

if (!res.ok) {
const txt = await res.text();
throw new Error(`Discord API ${method} ${path} failed ${res.status}: ${txt}`);
}

if (res.status === 204) return null;
return res.json();
}

export async function createForumThread({ botToken, forumChannelId, payload, timeoutMs }) {
return callDiscord(botToken, "POST", `/channels/${forumChannelId}/threads`, payload, {
timeoutMs,
});
}

export async function postChannelMessage({ botToken, channelId, payload, timeoutMs }) {
return callDiscord(botToken, "POST", `/channels/${channelId}/messages`, payload, { timeoutMs });
}

export async function patchChannel({ botToken, channelId, payload, timeoutMs }) {
return callDiscord(botToken, "PATCH", `/channels/${channelId}`, payload, { timeoutMs });
}
95 changes: 95 additions & 0 deletions .github/scripts/discord-bot-api.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createForumThread, patchChannel, postChannelMessage } from "./discord-bot-api.mjs";

const botToken = "test-token";

beforeEach(() => {
vi.stubGlobal("fetch", vi.fn());
});

afterEach(() => {
vi.unstubAllGlobals();
});

function mockResponse({ status = 200, body = { id: "x" } } = {}) {
vi.mocked(fetch).mockResolvedValue({
ok: status >= 200 && status < 300,
status,
text: vi.fn().mockResolvedValue(JSON.stringify(body)),
json: vi.fn().mockResolvedValue(body),
});
}

const happyCases = [
{
name: "createForumThread",
call: (args) => createForumThread(args),
args: { forumChannelId: "forum-1", payload: { name: "PR #1" } },
expectUrl: "https://discord.com/api/v10/channels/forum-1/threads",
expectMethod: "POST",
expectBody: { name: "PR #1" },
},
{
name: "postChannelMessage",
call: (args) => postChannelMessage(args),
args: { channelId: "thread-1", payload: { content: "hello" } },
expectUrl: "https://discord.com/api/v10/channels/thread-1/messages",
expectMethod: "POST",
expectBody: { content: "hello" },
},
{
name: "patchChannel",
call: (args) => patchChannel(args),
args: { channelId: "thread-1", payload: { archived: true } },
expectUrl: "https://discord.com/api/v10/channels/thread-1",
expectMethod: "PATCH",
expectBody: { archived: true },
},
];

describe.each(happyCases)("$name", ({ call, args, expectUrl, expectMethod, expectBody }) => {
it("calls Discord with the right URL, method, bot auth, and payload", async () => {
mockResponse();

await call({ ...args, botToken });

const [url, init] = vi.mocked(fetch).mock.calls[0];
expect(url).toBe(expectUrl);
expect(init.method).toBe(expectMethod);
expect(init.headers.Authorization).toBe(`Bot ${botToken}`);
expect(JSON.parse(init.body)).toEqual(expectBody);
expect(init.signal).toBeDefined();
});

it("throws on 429 with rate-limit message", async () => {
mockResponse({ status: 429, body: { retry_after: 1 } });
await expect(call({ ...args, botToken })).rejects.toThrow(/rate-limited \(429\)/);
});

it("throws on non-ok responses with status in message", async () => {
mockResponse({ status: 403, body: { message: "Missing Permissions" } });
await expect(call({ ...args, botToken })).rejects.toThrow(/failed 403/);
});
});

describe("callDiscord timeout", () => {
it("aborts the request after timeoutMs when the fetch hangs", async () => {
// fetch that never resolves until aborted
vi.mocked(fetch).mockImplementation(
(_url, init) =>
new Promise((_resolve, reject) => {
init.signal?.addEventListener("abort", () => {
reject(new DOMException("aborted", "AbortError"));
});
}),
);
await expect(
postChannelMessage({
botToken,
channelId: "x",
payload: {},
timeoutMs: 10,
}),
).rejects.toThrow();
});
});
Loading
Loading