Getting Started
Clone the Repository
git clone https://retoor.molodetz.nl/retoor/snek.git cd snek
Setup Development Environment
make install
This creates a virtual environment and installs all dependencies.
Run Locally
make run
The server starts at http://localhost:8080 by default.
Run Tests
pytest tests/
Project Structure
__main__.py CLI entry point
app.py Application setup, routing
view/ HTTP/WebSocket handlers
index.py Landing page
rpc.py WebSocket RPC
settings/ Settings views
service/ Business logic layer
user.py User service
channel.py Channel service
chat.py Chat service
mapper/ Data persistence
model/ Data models
form/ Form definitions
system/ Core infrastructure
view.py BaseView class
service.py BaseService class
mapper.py BaseMapper class
model.py BaseModel class
static/ Frontend JS and CSS
app.js Application singleton
socket.js WebSocket client
templates/ Jinja2 templates
Code Standards
Author Attribution
Every file must include the author tag at the top:
# retoor <retoor@molodetz.nl>
For JavaScript:
// retoor <retoor@molodetz.nl>
No Comments
Code should be self-documenting. If you need to explain something, refactor the code to be clearer. Comments indicate code that needs improvement.
No JavaScript Frameworks
The frontend uses vanilla ES6 JavaScript only. No React, Vue, Angular, or similar frameworks. Use Custom Elements for component encapsulation.
Explicit Over Implicit
Prefer explicit code paths. No magic imports, no hidden side effects, no implicit type conversions.
Variable Naming
Use clear, descriptive names. Avoid abbreviations except for common conventions (uid, url, etc.). No suffixes like _super or _refactored.
Module Organization
JavaScript: one class per file. Python: related classes can share a file, but keep files focused. Use directories to group related modules.
Adding a New View
Views handle HTTP requests and render responses.
1. Create the View Class
# retoor <retoor@molodetz.nl>
from snek.system.view import BaseView
class MyFeatureView(BaseView):
login_required = True
async def get(self):
data = await self.services.my_service.get_data()
return await self.render_template("my_feature.html", {"data": data})
2. Create the Template
Add a template at templates/my_feature.html:
{% extends "base.html" %}
{% block title %}My Feature{% endblock %}
{% block main %}
<div class="container">
<h1>My Feature</h1>
<p>{{ data }}</p>
</div>
{% endblock %}
3. Register the Route
In app.py setup_router method:
from snek.view.my_feature import MyFeatureView
self.router.add_view("/my-feature.html", MyFeatureView)
Adding a New Service
Services contain business logic and are accessed via app.services.name.
1. Create the Service Class
# retoor <retoor@molodetz.nl>
from snek.system.service import BaseService
class MyService(BaseService):
mapper_class = None
async def process_data(self, data):
return {"processed": data}
2. Register the Service
Services are auto-discovered from the service/ directory. The service name is derived from the filename (e.g., my_service.py becomes app.services.my_service).
Adding RPC Methods
RPC methods are defined in the RPCApi class in view/rpc.py.
Add the Method
async def my_method(self, param1, param2=None):
self._require_login()
self._require_services()
if not param1 or not isinstance(param1, str):
raise ValueError("Invalid param1")
result = await self.services.my_service.process(param1, param2)
return result
Method Guidelines
- Use
self._require_login()for authenticated methods - Use
self._require_services()to ensure services are available - Validate all input parameters
- Return serializable data (dicts, lists, primitives)
- Raise
ValueErrorfor invalid input - Raise
PermissionErrorfor authorization failures
Adding Frontend Components
Frontend components use Custom Elements.
1. Create the Component
// retoor <retoor@molodetz.nl>
class MyComponent extends HTMLElement {
constructor() {
super()
this.data = null
}
connectedCallback() {
this.render()
}
render() {
this.innerHTML = `
<div class="my-component">
<h2>My Component</h2>
</div>
`
}
}
customElements.define("my-component", MyComponent)
export { MyComponent }
2. Create the CSS
Add styles in a separate CSS file (no Shadow DOM):
.my-component {
background: #1a1a1a;
padding: 1rem;
border-radius: 6px;
}
.my-component h2 {
color: #7ef;
}
3. Import in the Template
<script type="module" src="/my-component.js"></script> <link rel="stylesheet" href="/my-component.css">
Testing
Running Tests
pytest tests/ # All tests pytest tests/test_user.py # Single file pytest tests/test_user.py::test_login # Single test pytest -m unit # Unit tests only pytest -m integration # Integration tests
Writing Tests
# retoor <retoor@molodetz.nl>
import pytest
@pytest.mark.unit
async def test_my_feature():
result = await my_function()
assert result is not None
Pull Request Guidelines
- Create a feature branch from
main - Make focused, atomic commits
- Write clear commit messages
- Ensure all tests pass
- Follow the code standards
- Update documentation if needed
- Submit PR with description of changes
Commit Message Format
feat: add user profile editing fix: resolve channel subscription issue docs: update API documentation refactor: simplify message handling