Files
Calendink/Provider/.agents/rules/coding-guidelines.md

4.1 KiB
Raw Blame History

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.cpp aggregators per API group (e.g., api/tasks/unity.cpp)
  • Mark all file-scoped symbols with internal (defined as static in types.hpp)
  • Use .hpp for 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, int8int64, 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.css via @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 via trackedFetch())

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 .gitignore accordingly
  • Version is managed through version.json, injected as __APP_VERSION__ at build time