docs: improve README with badges, enhanced sections, and better organization
Co-authored-by: gabriel20xx <21219769+gabriel20xx@users.noreply.github.com>
This commit is contained in:
co-authored by
gabriel20xx
parent
90eb409501
commit
5568470549
@@ -1,5 +1,9 @@
|
||||
# CollabTable
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://android.com)
|
||||
[](https://nodejs.org)
|
||||
|
||||
A collaborative list management system with Android app and server for creating and managing shared lists with custom fields and items.
|
||||
|
||||
## Overview
|
||||
@@ -12,59 +16,117 @@ CollabTable allows multiple users to collaboratively create and manage lists wit
|
||||
|
||||
## Features
|
||||
|
||||
### Android
|
||||
- ✨ Beautiful Material 3 design with dynamic colors
|
||||
- 📱 Native Android app with Jetpack Compose
|
||||
- 💾 Offline-first with Room database
|
||||
- 🔄 Automatic synchronization with server
|
||||
- 🎨 Custom fields (name, link, price, category, etc.)
|
||||
- ✏️ Real-time editing of items
|
||||
- 🗑️ Soft delete support
|
||||
- ⚙️ Configurable server URL in settings
|
||||
### Android App
|
||||
- ✨ **Beautiful Material 3 design** with dynamic colors
|
||||
- 📱 **Native Android app** built with Jetpack Compose
|
||||
- 💾 **Offline-first** with Room database
|
||||
- 🔄 **Real-time synchronization** via WebSocket (with HTTP fallback)
|
||||
- 🎨 **Custom fields** (name, link, price, category, etc.)
|
||||
- ✏️ **Inline editing** of items with auto-save
|
||||
- 📤 **Export functionality** (CSV format)
|
||||
- 🗑️ **Soft delete support** for data recovery
|
||||
- ⚙️ **Configurable server** URL and authentication
|
||||
- 📊 **Optimized performance** for large tables with thousands of rows
|
||||
- 🎯 **Column alignment** options (left, center, right)
|
||||
|
||||
### Server
|
||||
- 🚀 Express.js REST API with TypeScript
|
||||
- 🐳 Fully containerized with Docker
|
||||
- 💾 SQLite database with persistent storage
|
||||
- 🔄 Sync protocol for conflict resolution
|
||||
- 📊 Complete CRUD operations
|
||||
- 🚀 **Express.js REST API** with TypeScript
|
||||
- 🐳 **Fully containerized** with Docker
|
||||
- 💾 **Flexible database** support: SQLite or PostgreSQL
|
||||
- 🔄 **Advanced sync protocol** for conflict resolution
|
||||
- 🔌 **WebSocket support** for real-time updates
|
||||
- 🔐 **Optional authentication** with shared password
|
||||
- 📊 **Complete CRUD operations** for all entities
|
||||
- 💽 **Persistent storage** with Docker volumes
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Server
|
||||
- **Docker** and **Docker Compose** (recommended)
|
||||
- Or **Node.js 20+** for local development
|
||||
|
||||
### Android App
|
||||
- **Android Studio** Hedgehog or later
|
||||
- **Android SDK 26+** (Android 8.0 or higher)
|
||||
- **JDK 21**
|
||||
- Android emulator or physical device
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Server (Docker)
|
||||
|
||||
```bash
|
||||
cd CollabTableServer
|
||||
docker-compose up -d
|
||||
```
|
||||
1. **Navigate to server directory:**
|
||||
```bash
|
||||
cd CollabTableServer
|
||||
```
|
||||
|
||||
2. **Copy environment file (optional):**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
3. **Configure authentication (optional):**
|
||||
Edit `.env` and set `SERVER_PASSWORD` for API protection:
|
||||
```env
|
||||
SERVER_PASSWORD=your_secure_password
|
||||
```
|
||||
|
||||
4. **Start the server:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Server runs on `http://localhost:3000`
|
||||
|
||||
**Database Options:**
|
||||
- SQLite (default): Data persists in Docker volume `sqlite_data`
|
||||
- PostgreSQL: Configure in `.env` with `DB_CLIENT=postgres`
|
||||
|
||||
### Android App
|
||||
|
||||
1. Open `CollabTableAndroid` in Android Studio
|
||||
2. Wait for Gradle sync
|
||||
3. Run on emulator or device
|
||||
4. Go to Settings (gear icon) to configure server URL
|
||||
- For emulator: `http://10.0.2.2:3000/api/`
|
||||
- For physical device: `http://YOUR_COMPUTER_IP:3000/api/`
|
||||
1. **Open project in Android Studio:**
|
||||
- Open `CollabTableAndroid` folder in Android Studio
|
||||
- Wait for Gradle sync to complete
|
||||
|
||||
2. **Run the app:**
|
||||
- Run on emulator or physical device
|
||||
|
||||
3. **Configure server connection:**
|
||||
- Tap the Settings icon (⚙️) in the top bar
|
||||
- Enter your server URL:
|
||||
- **For emulator:** `http://10.0.2.2:3000/api/`
|
||||
- **For physical device:** `http://YOUR_COMPUTER_IP:3000/api/`
|
||||
- If server has authentication enabled, enter the password
|
||||
- Tap "Save"
|
||||
|
||||
**Note:** Make sure to include the trailing `/api/` in the URL.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
CollabTable/
|
||||
├── CollabTableAndroid/ # Android app
|
||||
├── CollabTableAndroid/ # Android app
|
||||
│ ├── app/
|
||||
│ │ ├── src/main/java/com/collabtable/app/
|
||||
│ │ │ ├── data/ # Database, API, repositories
|
||||
│ │ │ └── ui/ # Compose UI, screens, theme
|
||||
│ │ │ ├── data/ # Database, API, repositories
|
||||
│ │ │ │ ├── api/ # Retrofit interfaces
|
||||
│ │ │ │ ├── dao/ # Room DAOs
|
||||
│ │ │ │ ├── database/ # Room database setup
|
||||
│ │ │ │ ├── model/ # Data models
|
||||
│ │ │ │ └── repository/ # Repository pattern
|
||||
│ │ │ └── ui/ # Compose UI
|
||||
│ │ │ ├── navigation/ # Navigation setup
|
||||
│ │ │ ├── screens/ # UI screens & ViewModels
|
||||
│ │ │ └── theme/ # Material 3 theme
|
||||
│ │ └── build.gradle
|
||||
│ └── README.md
|
||||
│
|
||||
└── CollabTableServer/ # Node.js server
|
||||
└── CollabTableServer/ # Node.js server
|
||||
├── src/
|
||||
│ ├── models/ # MongoDB models
|
||||
│ └── routes/ # API endpoints
|
||||
│ ├── models/ # Data models & database logic
|
||||
│ ├── routes/ # API endpoints
|
||||
│ └── index.ts # Server entry point
|
||||
├── data/ # SQLite database storage
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
└── README.md
|
||||
@@ -73,71 +135,458 @@ CollabTable/
|
||||
## Technology Stack
|
||||
|
||||
### Android
|
||||
- Kotlin
|
||||
- Jetpack Compose
|
||||
- Material 3
|
||||
- Room Database
|
||||
- Retrofit
|
||||
- Coroutines & Flow
|
||||
- **Language:** Kotlin
|
||||
- **UI Framework:** Jetpack Compose with Material 3
|
||||
- **Database:** Room (SQLite)
|
||||
- **Networking:** Retrofit (HTTP), OkHttp WebSocket
|
||||
- **Async:** Coroutines & Flow
|
||||
- **Architecture:** MVVM with Repository pattern
|
||||
- **Build System:** Gradle with Kotlin DSL
|
||||
|
||||
### Server
|
||||
- Node.js
|
||||
- Express
|
||||
- TypeScript
|
||||
- SQLite (Sequelize ORM)
|
||||
- Docker
|
||||
- **Runtime:** Node.js 20+
|
||||
- **Framework:** Express.js
|
||||
- **Language:** TypeScript
|
||||
- **Database:** SQLite (better-sqlite3) or PostgreSQL (pg)
|
||||
- **Containerization:** Docker & Docker Compose
|
||||
- **Real-time:** WebSocket support
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET/POST /api/lists` - Manage lists
|
||||
- `GET/POST /api/fields` - Manage fields
|
||||
- `GET/POST /api/items` - Manage items
|
||||
- `POST /api/sync` - Synchronize data
|
||||
- `GET /health` - Health check
|
||||
All endpoints are prefixed with `/api/` and can be protected with optional authentication.
|
||||
|
||||
### Authentication
|
||||
If `SERVER_PASSWORD` is set in the server's `.env` file, include this header in all requests:
|
||||
```
|
||||
Authorization: Bearer your_password_here
|
||||
```
|
||||
|
||||
### Lists
|
||||
- `GET /api/lists` - Get all lists
|
||||
- `GET /api/lists/:id` - Get a specific list
|
||||
- `POST /api/lists` - Create a new list
|
||||
- `PUT /api/lists/:id` - Update a list
|
||||
- `DELETE /api/lists/:id` - Delete a list (soft delete)
|
||||
|
||||
### Fields
|
||||
- `GET /api/fields/list/:listId` - Get all fields for a list
|
||||
- `POST /api/fields` - Create a new field
|
||||
- `PUT /api/fields/:id` - Update a field (including alignment)
|
||||
- `DELETE /api/fields/:id` - Delete a field (soft delete)
|
||||
|
||||
### Items
|
||||
- `GET /api/items/list/:listId` - Get all items for a list
|
||||
- `GET /api/items/:itemId/values` - Get all values for an item
|
||||
- `POST /api/items` - Create a new item
|
||||
- `POST /api/items/values` - Create/update item values
|
||||
- `PUT /api/items/:id` - Update an item
|
||||
- `DELETE /api/items/:id` - Delete an item (soft delete)
|
||||
|
||||
### Sync
|
||||
- `POST /api/sync` - Synchronize data (HTTP fallback)
|
||||
- `GET /api/ws` - WebSocket endpoint for real-time sync
|
||||
|
||||
### Health
|
||||
- `GET /health` - Server health check
|
||||
|
||||
See [Server README](CollabTableServer/README.md) for detailed API documentation.
|
||||
|
||||
## Development
|
||||
|
||||
### Android Development
|
||||
### Server Development
|
||||
|
||||
**Local development without Docker:**
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
cd CollabTableServer
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Create `.env` file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
3. Start development server with hot reload:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
4. Build for production:
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
**Useful commands:**
|
||||
```bash
|
||||
cd CollabTableAndroid
|
||||
# Open in Android Studio
|
||||
npm run lint # Check code style
|
||||
npm run format # Auto-format code
|
||||
```
|
||||
|
||||
### Server Development
|
||||
```bash
|
||||
cd CollabTableServer
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
### Android Development
|
||||
|
||||
1. Open `CollabTableAndroid` in Android Studio
|
||||
|
||||
2. Build the project:
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
3. Run linters:
|
||||
```bash
|
||||
./gradlew ktlintCheck # Check Kotlin code style
|
||||
./gradlew detekt # Run static analysis
|
||||
```
|
||||
|
||||
4. Format code:
|
||||
```bash
|
||||
./gradlew ktlintFormat
|
||||
```
|
||||
|
||||
## Data Model
|
||||
|
||||
### List
|
||||
Contains multiple fields and items
|
||||
### Database Schema
|
||||
|
||||
### Field
|
||||
Defines a column/attribute (name, price, category, etc.)
|
||||
#### List
|
||||
Represents a table/list container:
|
||||
```typescript
|
||||
{
|
||||
id: string; // Unique identifier
|
||||
name: string; // List name
|
||||
createdAt: number; // Creation timestamp
|
||||
updatedAt: number; // Last update timestamp
|
||||
isDeleted: boolean; // Soft delete flag
|
||||
}
|
||||
```
|
||||
|
||||
### Item
|
||||
A row in the list with values for each field
|
||||
#### Field
|
||||
Defines a column/attribute in a list:
|
||||
```typescript
|
||||
{
|
||||
id: string; // Unique identifier
|
||||
listId: string; // Parent list ID
|
||||
name: string; // Field name (e.g., "Price", "Category")
|
||||
order: number; // Display order
|
||||
alignment: string; // Content alignment: "left", "center", "right"
|
||||
createdAt: number; // Creation timestamp
|
||||
updatedAt: number; // Last update timestamp
|
||||
isDeleted: boolean; // Soft delete flag
|
||||
}
|
||||
```
|
||||
|
||||
### ItemValue
|
||||
The actual value for a specific field of an item
|
||||
#### Item
|
||||
Represents a row in a list:
|
||||
```typescript
|
||||
{
|
||||
id: string; // Unique identifier
|
||||
listId: string; // Parent list ID
|
||||
createdAt: number; // Creation timestamp
|
||||
updatedAt: number; // Last update timestamp
|
||||
isDeleted: boolean; // Soft delete flag
|
||||
}
|
||||
```
|
||||
|
||||
#### ItemValue
|
||||
The actual value for a specific field of an item:
|
||||
```typescript
|
||||
{
|
||||
id: string; // Unique identifier
|
||||
itemId: string; // Parent item ID
|
||||
fieldId: string; // Associated field ID
|
||||
value: string; // The actual value
|
||||
updatedAt: number; // Last update timestamp
|
||||
}
|
||||
```
|
||||
|
||||
### Relationships
|
||||
- One **List** has many **Fields**
|
||||
- One **List** has many **Items**
|
||||
- One **Item** has many **ItemValues** (one per Field)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Android App
|
||||
|
||||
**Cannot connect to server**
|
||||
- Verify server is running: visit `http://localhost:3000/health` in browser
|
||||
- Check server URL in app settings:
|
||||
- Emulator: Use `http://10.0.2.2:3000/api/`
|
||||
- Physical device: Use your computer's IP address
|
||||
- Ensure trailing `/api/` is included in URL
|
||||
- If server has authentication, verify password is correct
|
||||
|
||||
**Gradle sync failed**
|
||||
- Update Android Studio to latest version
|
||||
- Ensure JDK 21 is installed and selected
|
||||
- Try: File → Invalidate Caches → Invalidate and Restart
|
||||
|
||||
**Build errors with Room/Kotlin**
|
||||
- Clean and rebuild: Build → Clean Project → Rebuild Project
|
||||
- Check that you're using compatible versions
|
||||
|
||||
**Large tables are slow**
|
||||
- Performance is optimized for thousands of rows
|
||||
- For tens of thousands of rows, consider server-side filtering
|
||||
- Check that debouncing is enabled (default)
|
||||
|
||||
#### Server
|
||||
|
||||
**Port 3000 already in use**
|
||||
```bash
|
||||
# Find and kill process using port 3000
|
||||
lsof -ti:3000 | xargs kill -9
|
||||
# Or change port in docker-compose.yml or .env
|
||||
```
|
||||
|
||||
**Docker container won't start**
|
||||
```bash
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Rebuild container
|
||||
docker-compose down
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
**Database connection error (PostgreSQL)**
|
||||
- Verify PostgreSQL is running
|
||||
- Check connection details in `.env`
|
||||
- Ensure database exists
|
||||
|
||||
**Data not persisting after container restart**
|
||||
- Verify Docker volume exists: `docker volume ls`
|
||||
- Check volume mount in `docker-compose.yml`
|
||||
- For SQLite, data is in `sqlite_data` volume
|
||||
|
||||
### Getting Help
|
||||
|
||||
- Check the [Android README](CollabTableAndroid/README.md) for app-specific details
|
||||
- Check the [Server README](CollabTableServer/README.md) for server-specific details
|
||||
- Review Docker logs: `docker-compose logs -f`
|
||||
- Open an issue on GitHub with detailed error messages
|
||||
|
||||
## Synchronization
|
||||
|
||||
The sync protocol uses timestamps to determine which changes to apply:
|
||||
1. Client sends all local changes since last sync
|
||||
2. Server merges changes and returns server-side updates
|
||||
3. Client applies server updates locally
|
||||
4. Latest timestamp wins in conflicts
|
||||
CollabTable uses a sophisticated sync protocol to keep data consistent across clients:
|
||||
|
||||
### Sync Strategy
|
||||
- **Primary:** WebSocket (`/api/ws`) for real-time, low-latency sync
|
||||
- **Fallback:** HTTP POST (`/api/sync`) when WebSocket fails
|
||||
- **Conflict Resolution:** Latest timestamp wins
|
||||
|
||||
### How It Works
|
||||
1. **Client → Server:** Client sends all local changes since last sync timestamp
|
||||
2. **Server Processing:** Server saves client changes and retrieves server changes
|
||||
3. **Server → Client:** Server returns changes made since client's last sync
|
||||
4. **Client Update:** Client applies server updates locally
|
||||
5. **Timestamp Update:** Client stores new server timestamp for next sync
|
||||
|
||||
### Sync Flow
|
||||
```
|
||||
Client Server
|
||||
| |
|
||||
|-- Sync Request (local changes) ---->|
|
||||
| (lists, fields, items, values) |
|
||||
| |
|
||||
|<-- Sync Response (server changes) --|
|
||||
| (updates + new serverTimestamp) |
|
||||
| |
|
||||
```
|
||||
|
||||
### WebSocket Protocol
|
||||
Send JSON messages to `/api/ws`:
|
||||
```json
|
||||
{
|
||||
"type": "sync",
|
||||
"id": "unique-request-id",
|
||||
"payload": {
|
||||
"lastSyncTimestamp": 1234567890,
|
||||
"lists": [...],
|
||||
"fields": [...],
|
||||
"items": [...],
|
||||
"itemValues": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"type": "syncResponse",
|
||||
"id": "unique-request-id",
|
||||
"payload": {
|
||||
"lists": [...],
|
||||
"fields": [...],
|
||||
"items": [...],
|
||||
"itemValues": [...],
|
||||
"serverTimestamp": 1234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
- **Automatic Sync:** Triggered on app start and after local changes
|
||||
- **Soft Deletes:** Deleted items can be recovered on server
|
||||
- **Optimistic Updates:** UI updates immediately, syncs in background
|
||||
- **Performance:** Debounced updates for large table operations
|
||||
|
||||
## Docker & Deployment
|
||||
|
||||
### Data Persistence
|
||||
|
||||
**SQLite (Default)**
|
||||
- Database stored in Docker volume: `sqlite_data`
|
||||
- Persists across container restarts
|
||||
- Backup database:
|
||||
```bash
|
||||
docker cp collabtable-server:/data/collabtable.db ./backup.db
|
||||
```
|
||||
- Restore database:
|
||||
```bash
|
||||
docker cp ./backup.db collabtable-server:/data/collabtable.db
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
**PostgreSQL**
|
||||
- Configure in `.env`: `DB_CLIENT=postgres`
|
||||
- Use standard PostgreSQL backup tools (`pg_dump`, `pg_restore`)
|
||||
- Docker volume manages persistence automatically
|
||||
|
||||
### Docker Commands
|
||||
|
||||
**Start services:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**Stop services:**
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
**View logs:**
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
**Rebuild and restart:**
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
**Check running containers:**
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
**Environment Variables**
|
||||
Configure these in `.env` or docker-compose.yml:
|
||||
```env
|
||||
PORT=3000 # Server port
|
||||
NODE_ENV=production # Environment
|
||||
SERVER_PASSWORD=your_secure_pass # API authentication (recommended)
|
||||
DB_CLIENT=sqlite # Database: sqlite or postgres
|
||||
DB_PATH=/data/collabtable.db # SQLite path (in container)
|
||||
```
|
||||
|
||||
**Security Recommendations**
|
||||
1. Set `SERVER_PASSWORD` for production
|
||||
2. Use HTTPS with a reverse proxy (nginx, Caddy)
|
||||
3. Regular database backups
|
||||
4. Keep Docker images updated
|
||||
5. Restrict network access to server ports
|
||||
|
||||
**Reverse Proxy Example (nginx)**
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name yourdomain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Here's how you can help:
|
||||
|
||||
### Getting Started
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature/your-feature-name`
|
||||
3. Make your changes
|
||||
4. Test your changes thoroughly
|
||||
5. Commit with clear messages: `git commit -m "feat: add new feature"`
|
||||
6. Push to your fork: `git push origin feature/your-feature-name`
|
||||
7. Open a Pull Request
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
**Code Style**
|
||||
- **Android:** Follow Kotlin conventions, use ktlint for formatting
|
||||
- **Server:** Follow TypeScript/JavaScript conventions, use Prettier
|
||||
- Keep functions small and focused
|
||||
- Write meaningful variable and function names
|
||||
- Add comments for complex logic
|
||||
|
||||
**Testing**
|
||||
- Test your changes on both emulator and physical devices (Android)
|
||||
- Test with both SQLite and PostgreSQL backends (Server)
|
||||
- Verify sync works correctly with multiple clients
|
||||
- Test with large datasets to ensure performance
|
||||
|
||||
**Commit Messages**
|
||||
Follow conventional commits:
|
||||
- `feat:` for new features
|
||||
- `fix:` for bug fixes
|
||||
- `docs:` for documentation changes
|
||||
- `style:` for formatting changes
|
||||
- `refactor:` for code refactoring
|
||||
- `test:` for adding tests
|
||||
- `chore:` for maintenance tasks
|
||||
|
||||
### Areas for Contribution
|
||||
|
||||
**Features**
|
||||
- Additional export formats (JSON, Excel)
|
||||
- Search and filter functionality
|
||||
- Batch operations
|
||||
- User authentication and permissions
|
||||
- Drag-and-drop row reordering
|
||||
- Rich text field support
|
||||
|
||||
**Improvements**
|
||||
- Performance optimization for very large tables
|
||||
- Better offline support
|
||||
- Enhanced error handling
|
||||
- Accessibility improvements
|
||||
- Internationalization (i18n)
|
||||
|
||||
**Documentation**
|
||||
- API documentation improvements
|
||||
- Tutorial videos or blog posts
|
||||
- Translation to other languages
|
||||
- More code examples
|
||||
|
||||
### Pull Request Guidelines
|
||||
- Keep PRs focused on a single feature or fix
|
||||
- Update documentation if needed
|
||||
- Ensure all tests pass
|
||||
- Add screenshots for UI changes
|
||||
- Reference any related issues
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
Reference in New Issue
Block a user