Thanks for considering a contribution. This guide covers the development environment, code conventions, and the submission process — everything you need to open a PR.
- Getting Started
- Development Workflow
- Code Style
- Submitting Changes
- After You Submit
- Reference
- Questions
- Git
- Bun v1.2.12+
- At least one supported AI engine:
- Claude Code by Anthropic
- OpenCode by Anomaly
- Codex by OpenAI
- GitHub Copilot CLI by GitHub
- Qwen Code by Alibaba Qwen
Engines can be installed manually or via Settings → System Tools after first launch.
Clopen is a Bun-only project — Node.js and Deno are not supported. Use bun for all package management and scripts.
Fork the repository via the GitHub UI, then:
git clone https://github.com/YOUR_USERNAME/clopen.git
cd clopen
git remote add upstream https://github.com/myrialabs/clopen.git
bun install
bun run checkbun run check should pass cleanly on a fresh clone. If it doesn't, see Troubleshooting.
Before starting any new branch, sync with upstream:
git fetch upstream
git checkout main
git merge upstream/main
git push origin main # optional: also sync your fork's main on GitHubWhen running bun run dev, Clopen stores data in ~/.clopen-dev instead of ~/.clopen. This keeps development data separate from any production instance — especially important since Clopen can be used to develop itself.
Standard flow from branch creation to PR:
# 1. Sync
git checkout main && git pull upstream main
# 2. Branch
git checkout -b feature/your-feature
# 3. Develop & verify locally
bun run check && bun run lint && bun run build
# 4. Commit
git commit -m "feat(scope): description"
# 5. Sync with upstream main again (resolve conflicts locally, not in the GitHub UI)
git fetch upstream && git merge upstream/main
bun run check && bun run lint && bun run build # re-verify after merge
# 6. Push & open PR
git push origin feature/your-featureOpen the PR targeting main on GitHub. See Submitting Changes for the conventions used at each step.
- Use
constby default; useletonly when reassignment is needed. anyis acceptable for Elysia/WS patterns where strict typing creates friction.
Use the runes system, not legacy stores. let for $state and $bindable; const for everything else ($derived, $props, functions).
<script lang="ts">
let count = $state(0);
const doubled = $derived(count * 2);
const handleClick = () => count++;
</script>camelCase— variables, functionsPascalCase— classes, typesUPPER_SNAKE_CASE— constantskebab-case— file names
Use the project's debug module instead of console.*. It respects log levels and namespaces.
import { debug } from './utils/debug';
debug.log('namespace', 'message', { data });Tabs, single quotes, semicolons. No Prettier enforcement — manual consistency.
Add a *.test.ts file when the change introduces non-trivial logic where a regression would be silent or costly — validators, parsers, security checks, path resolution, anything with branching that isn't obvious by inspection. Place the test alongside the source: foo.ts → foo.test.ts. Use bun:test.
Skip tests for changes that are inherently observable (UI tweaks, log strings, dependency bumps), trivial one-liners, or thin forwards of an already-tested function. If breaking the code would take more than a glance to notice, add a test.
For security fixes, tests are expected — at minimum a case that fails before the patch and passes after. Cover the boundary explicitly (the exact value that should be rejected) and at least one happy path.
bun test path/to/file.test.ts # single file
bun test # full suiteTests must pass before opening the PR.
All written content on the repository — branch names, commit messages, PR titles, PR descriptions, and PR comments — must be in English, regardless of the language you and the maintainers use elsewhere. This keeps the contribution trail readable across the project's audience.
Format: <type>/<description> — lowercase, kebab-case, concise. Exactly one /: the type, then the description. Nested paths are not allowed.
| Type | Use |
|---|---|
feature/ |
New feature |
fix/ |
Bug fix |
docs/ |
Documentation |
chore/ |
Build, refactor, dependencies, miscellaneous |
Examples: feature/database-management, fix/websocket-connection, docs/maintainers-guide-restructure.
Don't:
| Wrong | Why | Fix |
|---|---|---|
docs/maintainers/review-examples |
Two slashes — nested path | docs/maintainers-review-examples |
Fix/Websocket-Connection |
Uppercase | fix/websocket-connection |
fix/fix-the-websocket-connection-bug-that-happens-on-reconnect |
Verbose | fix/websocket-reconnect |
feature/auth.middleware |
Non-kebab punctuation | feature/auth-middleware |
Format: <type>(<scope>): <subject> — imperative mood, lowercase, no period, max 72 characters.
| Type | Use |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation |
chore |
Refactor, build, perf, dependencies |
release |
Version release |
Examples:
feat(chat): add message export
fix(terminal): resolve memory leak
docs(maintainers): document pr reshape workflow
chore: update dependencies
Keep the subject focused on why the change exists, not what files moved. If the change needs a longer explanation, put it in the PR description, not the commit body — repo convention is subject-only.
Before pushing each commit:
-
bun run checkpasses -
bun run lintpasses -
bun run buildworks -
bun testpasses if any test file was added or modified - New non-trivial logic has a
*.test.ts(see Tests) - Commit message follows the format above
- No
console.*(use thedebugmodule) - No sensitive data (tokens, credentials, internal URLs)
If a pre-commit hook fails, fix the underlying issue rather than skipping with --no-verify. Hooks exist for the same reasons CI does.
Same format as commit messages: <type>(<scope>): <subject> (lowercase, imperative, no period, ≤72 chars). GitHub uses this as the squash-commit subject on main.
## Summary
One or two sentences: what this PR does.
## Why
The motivation — bug it fixes, behavior it changes, constraint it addresses.
## Changes
- bullet list of concrete changes (files, modules, behaviors)
## Notes (optional)
Anything reviewers should know: trade-offs, follow-ups, areas needing extra eyes.Add only when relevant — empty headers add noise:
| Section | When to use |
|---|---|
## Test plan |
Any non-trivial change. Bulleted checklist of how the change was verified (bun run check, manual UI test, regression test, etc.). |
## Security impact |
Any change touching auth, authorization, input validation, file/path handling, or external execution. State the threat model in one paragraph: who is the attacker, what they could reach before, what is closed now. |
## Breaking changes |
Public API, WebSocket schema, DB schema, or config keys changed. Include migration path. |
## Migration |
Steps existing installs need to follow (DB migration, config update, manual cleanup). |
## Screenshots |
UI changes — before/after images or a short clip. |
## Follow-ups |
Known TODOs deferred to a separate PR (with link/issue if one exists). |
## Related |
Links to related issues, PRs, or external discussions. |
Match the comment's shape to its substance. Most contributor comments are short replies and read best as prose; a few situations warrant lists or topic sections so reviewers can scan.
The examples below are illustrative templates, not literal copy-paste — angle-bracket slots like <the specific reasoning> and concrete-looking names should be replaced with what's actually in your PR. Structural pieces (the opening "Thanks for…", the closing commitment, ## Header shapes) are the lesson; keep those.
Prose — the default. Short replies, single-topic responses, agreements, clarifications. Open with one or two sentences that name something specific you took from the review (the catch, the file:line reference, the reasoning) — generic "thanks" is filler. Then raise your reply, concern, or counter in prose, anchored to file:line inline. Warmth and brevity are not opposites.
Thanks for the careful breakdown. I re-validated the flow and I agree with your assessment: `<the specific point the maintainer made, in your own words>`. So `<the practical consequence — why the original fix shape doesn't apply, or what changes in your understanding>`.
I'm closing this PR to avoid carrying the wrong abstraction. If `<the condition that would make the original direction relevant>` changes in the future, I'll open a separate PR with `<the alternative approach — the layer it belongs at, the test that would pin it>`.Validation-result list. When responding to a maintainer's "please verify end-to-end" request, a bulleted list of what you ran is appropriate — it's concrete evidence, not framing. Keep the items factual; one line each. Only include items you actually ran; if the maintainer didn't ask for a manual UI walkthrough, the universal commands alone are fine.
Thanks for the guidance. I re-tested this PR locally after installing Bun.
Validation performed:
- `bun run check` passes with 0 errors / 0 warnings.
- `bun run lint` passes.
- Started the app locally with `bun run dev` and exercised <the specific flow the maintainer asked about>.
<Optional one-sentence summary tying the validation back to the maintainer's concern, if it isn't obvious from the items above.>If the maintainer asked you to verify multiple distinct scenarios (e.g. a positive case and a negative case for an authorization change), add one bullet per scenario — each describing what you did and what you observed, not just "verified X". The bullets mirror what was actually asked; they aren't a fixed checklist.
Topic sections (## <Topic Name>) — use when:
- You pushed commits or expanded scope and the additions span multiple files/areas. Mirror the PR-description shape (
## Summary / ## Why / ## Changes / ## Notes) so reviewers can scan what's new. - Your reply addresses multiple distinct concerns the maintainer raised (e.g. a blocker + a process question + a follow-up commitment). One
##per class, prose inside.
For everything else — single reply, agreement with the audit, clarification of one detail — prose is shorter and clearer.
Either way:
- File paths and identifiers in backticks.
@usernamefor mentions.- Dates as plain English (
May 25, 2026), not ISO format. - One issue per paragraph. No restated context.
- Avoid numbered audit-verdict lists with bolded leads (
**1. Issue:** ...) — they read as a checklist handed back, not a conversation.
A maintainer will read the full diff and audit it before responding. You can expect one of these responses:
| Response | What it means |
|---|---|
| Approve and merge | Audit was clean. |
| Commits added to your branch | Maintainer extended your fix with adjacent changes and posted a summary. Pull before pushing anything new. |
| Comment requesting discussion | Maintainer has concerns or wants to split scope. Default response window is 1 week — silence past the deadline closes the PR as auto-stale, and you can reopen at any time. |
| Close with reshape | Approach needs to change. You'll be credited in the replacement PR — closure is administrative, not rejection. |
If you disagree with feedback, push back with a concrete scenario, file/line reference, or counterargument — reviewers expect this and will update their position when warranted.
bun run dev # Dev server
bun run check # Type check
bun run lint # Lint
bun run lint:fix # Auto-fix lint
bun run build # Build# Type errors
rm -rf node_modules && bun install
# Lint errors
bun run lint:fix
# Git conflicts
git fetch upstream && git rebase upstream/main