Retired — September 2026 ReplyQueue is no longer maintained and no longer accepts issues or pull requests. No further features, bug fixes, or security updates are planned. The extension has been unpublished from the Chrome Web Store. Independent forks are welcome under the MIT license.
These development guidelines are retained for historical reference and independent forks. Issue reporting and pull request instructions below apply only to forks whose maintainers choose to accept contributions.
- Development Environment Setup
- Code Style
- Submitting Issues
- Pull Request Process
- Adding a New Platform Adapter
- Testing Requirements
- Node.js 18 or higher
- pnpm 9 or higher
- Chrome browser (for testing the extension)
- Git
-
Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/replyqueue.git cd replyqueue -
Install dependencies
pnpm install
-
Start the development server
pnpm dev
-
Load the extension in Chrome
- Open
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select the
distfolder
- Open
-
Run tests
pnpm test
replyqueue/
├── src/
│ ├── platforms/ # Platform adapters (LinkedIn, etc.)
│ ├── content/ # Content script (runs on social media)
│ ├── background/ # Service worker
│ ├── sidepanel/ # Vue 3 side panel UI
│ └── shared/ # Shared utilities and types
├── tests/ # Test files (mirrors src/ structure)
└── public/ # Static assets
- Use TypeScript strict mode
- Prefer
interfaceovertypefor object shapes - Use explicit return types for exported functions
- Avoid
any- useunknownif type is truly unknown
// Good
export function extractPost(element: Element): ExtractedPost | null {
// ...
}
// Avoid
export function extractPost(element: any) {
// ...
}- Use Composition API with
<script setup> - Use TypeScript in components
- Keep components focused and single-purpose
- Extract reusable logic into composables
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
</script>| Type | Convention | Example |
|---|---|---|
| Files | kebab-case | post-card.vue, rss-fetcher.ts |
| Components | PascalCase | PostCard.vue, StatusBar.vue |
| Composables | camelCase with use prefix |
useConfig.ts, usePosts.ts |
| Constants | SCREAMING_SNAKE_CASE | MAX_POSTS, API_BASE_URL |
| Functions | camelCase | extractPost(), validateKey() |
| Interfaces | PascalCase | ExtractedPost, PlatformAdapter |
The project uses Prettier for formatting and ESLint for linting:
pnpm lint # Check for lint errors
pnpm lint:fix # Auto-fix lint issues
pnpm format # Format code with PrettierKey formatting rules:
- 2 spaces for indentation
- Single quotes for strings
- No semicolons (unless required)
- Trailing commas in multiline
When reporting a bug, please include:
- Description - Clear description of the issue
- Steps to reproduce - Numbered steps to recreate the bug
- Expected behavior - What should happen
- Actual behavior - What actually happens
- Environment
- Chrome version
- Operating system
- Extension version
- Console errors - Any errors from DevTools console
- Screenshots - If applicable
Template:
## Bug Description
[Clear description]
## Steps to Reproduce
1. Go to...
2. Click on...
3. See error
## Expected Behavior
[What should happen]
## Actual Behavior
[What actually happens]
## Environment
- Chrome: [version]
- OS: [operating system]
- Extension: [version]
## Console Errors[paste errors here]
## Screenshots
[if applicable]
For feature requests, please include:
- Problem statement - What problem does this solve?
- Proposed solution - How should it work?
- Alternatives considered - Other approaches you thought of
- Additional context - Mockups, examples, etc.
- Check existing issues and PRs to avoid duplicates
- For significant changes, open an issue first to discuss
- Fork the repository and create a feature branch
Use descriptive branch names:
feature/twitter-adapter- New featuresfix/rss-parsing-error- Bug fixesdocs/update-readme- Documentationrefactor/storage-layer- Code refactoring
-
Create a branch
git checkout -b feature/your-feature-name
-
Make your changes
- Follow the code style guidelines
- Add tests for new functionality
- Update documentation if needed
-
Run tests
pnpm test -
Lint and format
pnpm lint:fix # Fix lint issues pnpm format # Format with Prettier
-
Build the extension
pnpm build
-
Test manually
- Load the extension in Chrome
- Test your changes in a real scenario
- Push your branch to your fork
- Open a Pull Request against
main - Fill out the PR template
- Link any related issues
PR Template:
## Description
[What does this PR do?]
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation
- [ ] Refactoring
## Testing
- [ ] Unit tests added/updated
- [ ] Manual testing completed
## Checklist
- [ ] Code follows project style
- [ ] Linting passes (`pnpm lint`)
- [ ] Code formatted (`pnpm format`)
- [ ] Tests pass locally (`pnpm test`)
- [ ] Build succeeds (`pnpm build`)
- [ ] Documentation updated
- [ ] No console.log in production code- Maintainers will review your PR
- Address any requested changes
- Once approved, your PR will be merged
Adding support for a new social media platform involves several steps.
cp -r src/platforms/_template src/platforms/twitterDefine CSS selectors for the platform's DOM:
export const platformSelectors: FeedSelectors = {
postItem: '[data-testid="tweet"]',
postContent: '[data-testid="tweetText"]',
authorName: '[data-testid="User-Name"] span',
authorHeadline: '[data-testid="UserName"] + div',
authorProfileLink: '[data-testid="User-Name"] a',
postTimestamp: 'time',
reactionCount: '[data-testid="like"] span',
commentCount: '[data-testid="reply"] span',
repostCount: '[data-testid="retweet"] span',
}Update the class with platform-specific logic:
export class TwitterAdapter implements PlatformAdapter {
readonly platformId = 'twitter'
readonly platformName = 'Twitter'
readonly selectors: FeedSelectors = platformSelectors
isFeedPage(url: string): boolean {
return /twitter\.com\/(home|[^/]+\/status)/.test(url)
}
extractPost(element: Element): ExtractedPost | null {
// Platform-specific extraction logic
}
getPostUrl(postId: string): string {
return `https://twitter.com/i/status/${postId}`
}
scrollToPost(postId: string): boolean {
// Scroll implementation
}
}In src/platforms/index.ts:
import { TwitterAdapter } from './twitter/adapter'
const adapters: PlatformAdapter[] = [
new LinkedInAdapter(),
new TwitterAdapter(), // Add your adapter
]In manifest.json, add:
{
"host_permissions": [
"https://twitter.com/*",
"https://x.com/*"
],
"content_scripts": [
{
"matches": ["https://twitter.com/*", "https://x.com/*"],
"js": ["src/content/index.ts"]
}
]
}Create tests/platforms/twitter-extraction.test.ts:
import { describe, it, expect, beforeEach } from 'vitest'
import { TwitterAdapter } from '../../src/platforms/twitter/adapter'
describe('TwitterAdapter', () => {
let adapter: TwitterAdapter
beforeEach(() => {
adapter = new TwitterAdapter()
})
it('should have correct platform ID', () => {
expect(adapter.platformId).toBe('twitter')
})
it('should identify feed pages', () => {
expect(adapter.isFeedPage('https://twitter.com/home')).toBe(true)
expect(adapter.isFeedPage('https://twitter.com/settings')).toBe(false)
})
// Add more tests for extraction logic
})In src/background/index.ts, add the platform's domain to the ALLOWED_CONTENT_SCRIPT_ORIGINS regex:
const ALLOWED_CONTENT_SCRIPT_ORIGINS = /^https:\/\/(www\.)?(linkedin\.com|twitter\.com|x\.com)\//;This security measure ensures the background script only accepts messages from authorized platform domains.
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run specific test file
pnpm test tests/shared/validation.test.tsTests mirror the source structure:
tests/
├── shared/ # Tests for shared utilities
├── platforms/ # Platform adapter tests
├── content/ # Content script tests
├── background/ # Background worker tests
├── sidepanel/ # Composable tests
└── integration/ # End-to-end flow tests
import { describe, it, expect, beforeEach, vi } from 'vitest'
describe('ComponentName', () => {
beforeEach(() => {
// Setup before each test
})
it('should do something specific', () => {
// Arrange
const input = 'test'
// Act
const result = functionUnderTest(input)
// Assert
expect(result).toBe('expected')
})
})The test setup (tests/setup.ts) provides mocks for Chrome APIs:
// Chrome storage is automatically mocked
await chrome.storage.sync.set({ key: 'value' })
const result = await chrome.storage.sync.get('key')- Unit tests: Individual functions and composables
- Integration tests: Full workflow scenarios
- Edge cases: Error handling, empty states, invalid input
Before submitting a PR:
- All existing tests must pass
- New functionality must have tests
- Test coverage should not decrease significantly
Support is no longer provided for this repository. Existing documentation and closed issues remain available for reference. If you use an independent fork, follow that fork's support and contribution guidelines.