4.1 KiB
Calendink Coding Guidelines
These rules apply to all code in this workspace.
Backend — C++ / ESP-IDF
Philosophy: C-with-Utilities
Write C-style code using C++ convenience features. No classes, no methods, no RAII, no exceptions.
Use freely: template, auto, constexpr, enum class, type aliases from types.hpp
Avoid: classes, constructors/destructors, std:: containers, inheritance, virtual functions, RAII wrappers
goto for cleanup/shutdown paths in app_main is acceptable.
Unity Build
All .cpp files are #include-ed into main.cpp as a single translation unit. Do not register new source files in CMakeLists.txt.
- Use
unity.cppaggregators per API group (e.g.,api/tasks/unity.cpp) - Mark all file-scoped symbols with
internal(defined asstaticintypes.hpp) - Use
.hppfor declarations shared across included files only
API Handler Pattern
// METHOD /api/path — What it does
// Body: { "field": value } (if applicable)
internal esp_err_t api_foo_post_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
// 1. Parse request
// 2. Call store function
// 3. Build cJSON response
// Always cJSON_Delete() objects and free() printed strings — no leaks
}
internal const httpd_uri_t api_foo_post_uri = { .uri = "/api/foo", .method = HTTP_POST, .handler = api_foo_post_handler, .user_ctx = NULL };
Store / Handler Separation
Data operations in store.cpp / store.hpp. HTTP parsing in endpoint files. Never mix them.
Logging
Always use ESP_LOGI / ESP_LOGW / ESP_LOGE — never printf().
Since this is a Unity Build (single translation unit), the log tag must be unique per file to avoid redefinition. Name it after the module, not a generic TAG:
// In each file, use a unique local name:
internal const char *kTagHttpServer = "HTTP_SERVER";
internal const char *kTagMain = "MAIN";
internal const char *kTagMDNS = "MDNS";
Type Aliases
Use types.hpp aliases: uint8, uint16, uint32, uint64, int8–int64, internal
Seed Data
seed_users() and seed_tasks() must be guarded:
#ifndef NDEBUG
seed_users();
seed_tasks();
#endif
Data Persistence
All task/user data is currently in-RAM only (intentional — NVS/LittleFS persistence is a future milestone). Do not add persistence without a TDD.
Frontend — Svelte 5 + TailwindCSS v4
Styling: Tailwind Only
Use TailwindCSS exclusively. No <style> blocks in components.
- All design tokens are in
app.cssvia@theme:bg-bg-card,text-text-primary,border-border,text-accent,text-success,text-danger, etc. - If a utility is missing, add a token to
@theme— don't add inline CSS
Reactivity: Svelte 5 Runes Only
| Do ✅ | Don't ❌ |
|---|---|
$state() |
writable(), readable() |
$derived() / $derived.by() |
$: reactive statements |
$effect() |
onMount(), onDestroy(), .subscribe() |
$props() / $bindable() |
export let |
$state() vs Plain let
$state()— values the template reads, or that changes should cause a re-render- Plain
let— script-only internals (mutex flags, interval handles, etc.) the template never reads
$effect() for Initialization
When an effect has no reactive dependencies and runs once on mount, add a comment:
// Load initial data on mount
$effect(() => { fetchData(); });
Shared Utilities
- Date/time helpers, formatters →
lib/utils.js - Cross-component reactive state →
lib/stores.js - API calls →
lib/api.js(always viatrackedFetch())
Never duplicate functions across components.
Component Structure
<script>
// 1. Imports
// 2. $props()
// 3. $state()
// 4. $derived()
// 5. Functions
// 6. $effect()
</script>
<!-- Template — Tailwind classes only -->
General
- Don't commit build artifacts:
dist/,bundles/,temp_*,*.gz— update.gitignoreaccordingly - Version is managed through
version.json, injected as__APP_VERSION__at build time