Contribute

How to contribute to the Snek platform

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

src/snek/
  __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

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

  1. Create a feature branch from main
  2. Make focused, atomic commits
  3. Write clear commit messages
  4. Ensure all tests pass
  5. Follow the code standards
  6. Update documentation if needed
  7. 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