<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docs on Lesstruct</title><link>https://lesstruct.dev/docs/</link><description>Recent content in Docs on Lesstruct</description><generator>Hugo</generator><language>en</language><copyright>Released under the [MIT License](https://github.com/aristorinjuang/lesstruct/blob/main/LICENSE).</copyright><lastBuildDate>Tue, 16 Jun 2026 19:07:39 +0700</lastBuildDate><atom:link href="https://lesstruct.dev/docs/index.xml" rel="self" type="application/rss+xml"/><item><title/><link>https://lesstruct.dev/docs/project-context/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://lesstruct.dev/docs/project-context/</guid><description>&lt;h1 id="project-context-for-ai-agents"&gt;Project Context for AI Agents&lt;a class="anchor" href="#project-context-for-ai-agents"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;This file contains critical rules and patterns that AI agents must follow when implementing code in this project. Focus on unobvious details that agents might otherwise miss.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="technology-stack--versions"&gt;Technology Stack &amp;amp; Versions&lt;a class="anchor" href="#technology-stack--versions"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="backend-go"&gt;Backend (Go)&lt;a class="anchor" href="#backend-go"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt; 1.26 — module: &lt;code&gt;github.com/aristorinjuang/lesstruct&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chi&lt;/strong&gt; 5.2.5 — HTTP router; &lt;strong&gt;httprate&lt;/strong&gt; 0.15.0 — per-route rate limiting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Databases&lt;/strong&gt; (driver selected via &lt;code&gt;DB_DRIVER&lt;/code&gt; env: &lt;code&gt;sqlite&lt;/code&gt; | &lt;code&gt;postgres&lt;/code&gt; | &lt;code&gt;mysql&lt;/code&gt;):
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt; (modernc.org/sqlite v1.50.0) — default, embedded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; (jackc/pgx/v5 v5.10.0)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt; (go-sql-driver/mysql v1.9.2) — DSN MUST contain &lt;code&gt;parseTime=true&lt;/code&gt; AND &lt;code&gt;multiStatements=true&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;golang-migrate&lt;/strong&gt; 4.19.1 — DB migrations via &lt;code&gt;iofs&lt;/code&gt; embedded filesystem, per-driver subdirs under &lt;code&gt;internal/database/migrations/{sqlite,postgresql,mysql}/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BurntSushi/toml&lt;/strong&gt; 1.6.0 — &lt;code&gt;config.toml&lt;/code&gt; parsing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;joho/godotenv&lt;/strong&gt; 1.5.1 — &lt;code&gt;.env&lt;/code&gt; loader (called from &lt;code&gt;config.Load()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fsnotify&lt;/strong&gt; 1.10.0 — config file hot-reload&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;golang-jwt&lt;/strong&gt; 5.3.1 — JWT auth (browser admin realm)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bluemonday&lt;/strong&gt; 1.0.27 — HTML sanitization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;goldmark&lt;/strong&gt; 1.8.2 — Markdown parser (Markdown → TipTap JSON converter in &lt;code&gt;internal/content/markdown/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wazero&lt;/strong&gt; 1.11.0 — WebAssembly runtime (plugin system)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;google.golang.org/genai&lt;/strong&gt; 1.59.0 — Google Imagen image generation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;openai/openai-go&lt;/strong&gt; 1.12.0 — text generation (OpenAI-compatible APIs via &lt;code&gt;AI_TEXT_GENERATION_BASE_URL&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deepteams/webp&lt;/strong&gt; 1.2.1 + &lt;strong&gt;golang.org/x/image&lt;/strong&gt; 0.39.0 — image transcoding for media uploads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spf13/cobra&lt;/strong&gt; 1.10.2 — CLI framework (&lt;code&gt;cmd/lesstruct-cli&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;golang.org/x/crypto&lt;/strong&gt; 0.51.0, &lt;strong&gt;golang.org/x/net&lt;/strong&gt; 0.55.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stretchr/testify&lt;/strong&gt; 1.11.1 — test assertions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mockery&lt;/strong&gt; — mock generation (&lt;code&gt;make mock&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;govulncheck&lt;/strong&gt; — vulnerability scanning (&lt;code&gt;make vulncheck&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;golangci-lint&lt;/strong&gt; v2.11.4 — linter (&lt;code&gt;make lint&lt;/code&gt;); config in &lt;code&gt;.golangci.yml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cli-cmdlesstruct-cli"&gt;CLI (&lt;code&gt;cmd/lesstruct-cli&lt;/code&gt;)&lt;a class="anchor" href="#cli-cmdlesstruct-cli"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Thin Cobra-based client over &lt;code&gt;/api/v1&lt;/code&gt;; imports &lt;strong&gt;no server internals&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Subcommands: &lt;code&gt;content&lt;/code&gt; (create/get/list/update/delete/publish/unpublish), &lt;code&gt;media&lt;/code&gt; (upload/get/list), &lt;code&gt;config&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Auth via &lt;code&gt;--api-key&lt;/code&gt; flag, &lt;code&gt;LESSTRUCT_API_KEY&lt;/code&gt; env, or config file (precedence in that order)&lt;/li&gt;
&lt;li&gt;Output mode: &lt;code&gt;--output text|json&lt;/code&gt; (default &lt;code&gt;text&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Built via &lt;code&gt;make build-cli&lt;/code&gt; → &lt;code&gt;bin/lesstruct-cli&lt;/code&gt;; integration tests via &lt;code&gt;make test-cli&lt;/code&gt; (tag &lt;code&gt;integration&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="admin-panel-frontend--webadmin"&gt;Admin Panel (Frontend) — &lt;code&gt;web/admin/&lt;/code&gt;&lt;a class="anchor" href="#admin-panel-frontend--webadmin"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vue 3&lt;/strong&gt; 3.5.31 — Composition API + &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; only&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; 6.0 — strict mode&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vite&lt;/strong&gt; 8 — build tool; base: &lt;code&gt;/admin/&lt;/code&gt;, output to &lt;code&gt;internal/api/static/admin/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pinia&lt;/strong&gt; 3.0.4 — state management&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vue Router&lt;/strong&gt; 5.0.4&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TipTap&lt;/strong&gt; 3.22+ (Vue 3) — rich text editor (starter-kit + code-block-lowlight, emoji, image, link, mathematics, placeholder, table, table-cell, table-header, table-row, text-align, underline)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headless UI&lt;/strong&gt; 1.7.23 — accessible primitives&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KaTeX&lt;/strong&gt; 0.16.47 — math rendering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lowlight&lt;/strong&gt; 3.3.0 — syntax highlighting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vitest&lt;/strong&gt; 4.1.2 + jsdom 29 — unit tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prettier&lt;/strong&gt; 3.8.1 + ESLint 10 + Oxlint ~1.57 — linting/formatting&lt;/li&gt;
&lt;li&gt;Node engine: &lt;code&gt;^20.19.0 || &amp;gt;=22.12.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="content-theme"&gt;Content Theme&lt;a class="anchor" href="#content-theme"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go &lt;code&gt;html/template&lt;/code&gt; — server-rendered content site via &lt;code&gt;internal/api/template/&lt;/code&gt; (layouts/pages) and &lt;code&gt;internal/api/contentpage/&lt;/code&gt; (data assembly)&lt;/li&gt;
&lt;li&gt;Theme overrides via &lt;code&gt;THEME_DIR&lt;/code&gt; env var or theme plugin architecture&lt;/li&gt;
&lt;li&gt;Default theme CSS minified via &lt;code&gt;make css&lt;/code&gt; (tdewolff/minify)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="architecture"&gt;Architecture&lt;a class="anchor" href="#architecture"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain-Driven Design&lt;/strong&gt;: &lt;code&gt;internal/domain/&amp;lt;name&amp;gt;/&lt;/code&gt; holds business logic, types, sentinel errors, interfaces. Current domains: &lt;code&gt;apikey&lt;/code&gt;, &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;customfield&lt;/code&gt;, &lt;code&gt;dashboard&lt;/code&gt;, &lt;code&gt;media&lt;/code&gt;, &lt;code&gt;plugin&lt;/code&gt;, &lt;code&gt;posttype&lt;/code&gt;, &lt;code&gt;profilepicture&lt;/code&gt;, &lt;code&gt;sanitize&lt;/code&gt;, &lt;code&gt;seo&lt;/code&gt;, &lt;code&gt;textgen&lt;/code&gt;, &lt;code&gt;thumbnail&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository pattern&lt;/strong&gt;: interfaces in domain, per-driver implementations in &lt;code&gt;internal/repository/{sqlite,mysql,postgresql}/&lt;/code&gt;. Shared cross-driver helpers (e.g., &lt;code&gt;soft_delete.go&lt;/code&gt;, &lt;code&gt;user.go&lt;/code&gt;) live directly in &lt;code&gt;internal/repository/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP handlers&lt;/strong&gt;: &lt;code&gt;internal/api/handlers/&lt;/code&gt; (browser admin realm) and &lt;code&gt;internal/api/handlers/agent/&lt;/code&gt; (Bearer API-key realm, &lt;code&gt;/api/v1&lt;/code&gt;); routes registered in &lt;code&gt;internal/api/routes/routes.go&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth realms&lt;/strong&gt;: two co-exist on shared paths and are dispatched by &lt;code&gt;dispatchByAuth()&lt;/code&gt; based on the &lt;code&gt;Authorization&lt;/code&gt; header prefix (&lt;code&gt;Bearer lesstruct_…&lt;/code&gt; = agent realm, JWT cookie or other Bearer = browser realm). Each chain carries its own auth middleware&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Middleware&lt;/strong&gt; (&lt;code&gt;internal/api/middleware/&lt;/code&gt;): &lt;code&gt;auth&lt;/code&gt; (JWT), &lt;code&gt;apikey&lt;/code&gt; (Bearer API key), &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;commentator&lt;/code&gt;, &lt;code&gt;cors&lt;/code&gt;, &lt;code&gt;csrf&lt;/code&gt;, &lt;code&gt;nocookie&lt;/code&gt;, &lt;code&gt;ratelimit&lt;/code&gt; (via httprate)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response envelope&lt;/strong&gt; (&lt;code&gt;internal/api/response/&lt;/code&gt;): &lt;code&gt;{&amp;quot;data&amp;quot;: ..., &amp;quot;error&amp;quot;: {...}, &amp;quot;meta&amp;quot;: {...}}&lt;/code&gt;. Lists use &lt;code&gt;SuccessList()&lt;/code&gt; which uses a dedicated &lt;code&gt;listResponse&lt;/code&gt; type WITHOUT &lt;code&gt;omitempty&lt;/code&gt; on &lt;code&gt;data&lt;/code&gt; so empty lists serialize as &lt;code&gt;&amp;quot;data&amp;quot;:[]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugin system&lt;/strong&gt;: wazero WASM runtime in &lt;code&gt;internal/plugin/&lt;/code&gt; with hook execution (&lt;code&gt;before_save&lt;/code&gt;, &lt;code&gt;after_save&lt;/code&gt;, etc.). Subpackages: &lt;code&gt;bootstrap&lt;/code&gt;, &lt;code&gt;capability&lt;/code&gt;, &lt;code&gt;devmode&lt;/code&gt;, &lt;code&gt;hostfunctions&lt;/code&gt;, &lt;code&gt;loader&lt;/code&gt;, &lt;code&gt;registry&lt;/code&gt;, &lt;code&gt;runtime&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content pipeline&lt;/strong&gt;: &lt;code&gt;internal/content/&lt;/code&gt; holds format converters — &lt;code&gt;tiptap/&lt;/code&gt; (canonical), &lt;code&gt;markdown/&lt;/code&gt; (Markdown→TipTap via goldmark), &lt;code&gt;wordpress/&lt;/code&gt; (WordPress importer)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Config&lt;/strong&gt;: &lt;code&gt;.env&lt;/code&gt; + env vars loaded via &lt;code&gt;internal/config/config.go&lt;/code&gt; (&lt;code&gt;Config&lt;/code&gt; struct, &lt;code&gt;Load()&lt;/code&gt;); user-facing &lt;code&gt;config.toml&lt;/code&gt; in project root loaded at startup with hot-reload via fsnotify; post types/languages/thumbnails schemas in &lt;code&gt;internal/config/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migrations&lt;/strong&gt;: numbered &lt;code&gt;.up.sql&lt;/code&gt;/&lt;code&gt;.down.sql&lt;/code&gt; pairs in &lt;code&gt;internal/database/migrations/{driver}/&lt;/code&gt;, embedded via &lt;code&gt;embed.go&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="critical-implementation-rules"&gt;Critical Implementation Rules&lt;a class="anchor" href="#critical-implementation-rules"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="language-specific-rules"&gt;Language-Specific Rules&lt;a class="anchor" href="#language-specific-rules"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id="go"&gt;Go&lt;a class="anchor" href="#go"&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;any&lt;/code&gt;, never &lt;code&gt;interface{}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Never use &lt;code&gt;panic()&lt;/code&gt; — use &lt;code&gt;log.Fatalf()&lt;/code&gt;/&lt;code&gt;log.Panicf()&lt;/code&gt; only in &lt;code&gt;main.go&lt;/code&gt; (and only in &lt;code&gt;cmd/lesstruct-cli/main.go&lt;/code&gt; for the CLI)&lt;/li&gt;
&lt;li&gt;Private structs/functions before public ones in every file&lt;/li&gt;
&lt;li&gt;Constructors (&lt;code&gt;New*&lt;/code&gt;) go AFTER all methods on the struct&lt;/li&gt;
&lt;li&gt;Multi-line function arguments when ≥3 params (one arg per line)&lt;/li&gt;
&lt;li&gt;Always use constants for HTTP methods: &lt;code&gt;http.MethodDelete&lt;/code&gt;, not &lt;code&gt;&amp;quot;DELETE&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;internal/config/&lt;/code&gt; holds env-based config; &lt;code&gt;config.toml&lt;/code&gt; holds user-facing config&lt;/li&gt;
&lt;li&gt;Domain errors are sentinel errors (&lt;code&gt;var ErrSomething = errors.New(...)&lt;/code&gt;) in the domain package&lt;/li&gt;
&lt;li&gt;Handlers map domain errors to HTTP responses via &lt;code&gt;handleContentError()&lt;/code&gt; pattern (browser realm) or the agent error mapper in &lt;code&gt;internal/api/handlers/agent/errors.go&lt;/code&gt; (agent realm)&lt;/li&gt;
&lt;li&gt;JSON responses use the envelope from &lt;code&gt;internal/api/response/&lt;/code&gt; — call &lt;code&gt;Success&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt;, or &lt;code&gt;SuccessList&lt;/code&gt;; never hand-roll the envelope&lt;/li&gt;
&lt;li&gt;Cross-driver repository code must work for SQLite, PostgreSQL, AND MySQL — beware driver-specific SQL (placeholders, &lt;code&gt;RETURNING&lt;/code&gt;, time handling). Use the per-driver subpackage when behavior must diverge&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="typescriptvue"&gt;TypeScript/Vue&lt;a class="anchor" href="#typescriptvue"&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;&lt;/code&gt; exclusively&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;defineProps&amp;lt;T&amp;gt;()&lt;/code&gt;, &lt;code&gt;defineEmits&amp;lt;T&amp;gt;()&lt;/code&gt; typed interfaces&lt;/li&gt;
&lt;li&gt;&lt;code&gt;composables/&lt;/code&gt; for reusable stateful logic (e.g., &lt;code&gt;useAuth&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stores/&lt;/code&gt; for Pinia stores, organized by domain under &lt;code&gt;stores/domain/&lt;/code&gt; and UI under &lt;code&gt;stores/ui/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;types/&lt;/code&gt; for shared TypeScript interfaces&lt;/li&gt;
&lt;li&gt;TipTap content is always a JSON string (&lt;code&gt;&amp;quot;{\&amp;quot;type\&amp;quot;:\&amp;quot;doc\&amp;quot;,\&amp;quot;content\&amp;quot;:[...]}&amp;quot;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="framework-specific-rules"&gt;Framework-Specific Rules&lt;a class="anchor" href="#framework-specific-rules"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id="backend-chi--domain-driven-design"&gt;Backend (Chi + Domain-Driven Design)&lt;a class="anchor" href="#backend-chi--domain-driven-design"&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No framework&lt;/strong&gt;: Chi is a lightweight router, not a framework — handlers receive &lt;code&gt;http.ResponseWriter, *http.Request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Routes registered in &lt;code&gt;internal/api/routes/routes.go&lt;/code&gt;, grouped by resource and by auth realm&lt;/li&gt;
&lt;li&gt;Two auth realms co-own some &lt;code&gt;/api/v1/media&lt;/code&gt; paths — when adding routes that may collide, register via &lt;code&gt;dispatchByAuth(agentChain, browserChain)&lt;/code&gt; rather than duplicating the path&lt;/li&gt;
&lt;li&gt;Agent realm (&lt;code&gt;/api/v1/*&lt;/code&gt;) requires Bearer &lt;code&gt;lesstruct_&amp;lt;keyID&amp;gt;_&amp;lt;secret&amp;gt;&lt;/code&gt; tokens verified by &lt;code&gt;APIKeyAuthMiddleware&lt;/code&gt;; identity is injected into context using the SAME context keys (&lt;code&gt;UserIDKey&lt;/code&gt;, &lt;code&gt;UsernameKey&lt;/code&gt;, &lt;code&gt;RoleKey&lt;/code&gt;) as the JWT middleware so downstream code is auth-agnostic&lt;/li&gt;
&lt;li&gt;Content services require a &lt;code&gt;HookExecutor&lt;/code&gt; — always pass plugin hooks through, don&amp;rsquo;t bypass&lt;/li&gt;
&lt;li&gt;Custom field validation flows through &lt;code&gt;content.Service.validateCustomFields()&lt;/code&gt; — never call &lt;code&gt;validateFieldValue()&lt;/code&gt; directly from handlers&lt;/li&gt;
&lt;li&gt;Post types loaded from &lt;code&gt;config.toml&lt;/code&gt; at startup via &lt;code&gt;internal/config/posttypes.go&lt;/code&gt;; reloaded on file change via fsnotify&lt;/li&gt;
&lt;li&gt;SEO auto-extraction: &lt;code&gt;ExtractPlainText()&lt;/code&gt; and &lt;code&gt;ExtractImageURL()&lt;/code&gt; consume TipTap JSON from content&lt;/li&gt;
&lt;li&gt;Markdown bodies on the agent create surface are converted to canonical TipTap JSON via &lt;code&gt;internal/content/markdown&lt;/code&gt; — raw Markdown is NEVER persisted&lt;/li&gt;
&lt;li&gt;Rate limits configurable per realm via &lt;code&gt;RATE_LIMIT_{AUTH,API,PUBLIC}_PER_MINUTE&lt;/code&gt;; toggle via &lt;code&gt;RATE_LIMIT_ENABLED&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="frontend-vue-3--pinia"&gt;Frontend (Vue 3 + Pinia)&lt;a class="anchor" href="#frontend-vue-3--pinia"&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atomic design&lt;/strong&gt;: &lt;code&gt;atoms/&lt;/code&gt; → &lt;code&gt;molecules/&lt;/code&gt; → &lt;code&gt;organisms/&lt;/code&gt; → &lt;code&gt;views/&lt;/code&gt; under &lt;code&gt;web/admin/src/components/&lt;/code&gt; and &lt;code&gt;web/admin/src/views/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Content editor: &lt;code&gt;ContentEditor.vue&lt;/code&gt; is the single organism for create + edit (shared component, not separate views)&lt;/li&gt;
&lt;li&gt;Custom field rendering: &lt;code&gt;CustomFieldRenderer.vue&lt;/code&gt; in molecules handles all field types&lt;/li&gt;
&lt;li&gt;Media upload: &lt;code&gt;MediaPanel.vue&lt;/code&gt; organism, opened as a slideover from &lt;code&gt;ContentEditor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SEO settings are collapsible within &lt;code&gt;ContentEditor&lt;/code&gt; (&lt;code&gt;isSEOSettingsOpen&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Store actions (e.g., &lt;code&gt;contentStore.create()&lt;/code&gt;) make API calls; components only call store actions&lt;/li&gt;
&lt;li&gt;Toast notifications via &lt;code&gt;Toast.vue&lt;/code&gt; molecule with &lt;code&gt;displayToast(message, type)&lt;/code&gt; pattern&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title/><link>https://lesstruct.dev/docs/api-reference/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lesstruct.dev/docs/api-reference/</guid><description>&lt;h1 id="lesstruct-api-reference-apiv1"&gt;Lesstruct API Reference (&lt;code&gt;/api/v1&lt;/code&gt;)&lt;a class="anchor" href="#lesstruct-api-reference-apiv1"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Lesstruct exposes a versioned, API-key-authenticated REST API at &lt;code&gt;/api/v1&lt;/code&gt; for creating, reading, updating, and deleting &lt;strong&gt;Content&lt;/strong&gt; and &lt;strong&gt;Media&lt;/strong&gt;. It is designed for programmatic consumers — the &lt;code&gt;lesstruct-cli&lt;/code&gt;, MCP servers, AI agents (Claude Code, OpenCode, Hermes, …), and human integrators — and accepts Markdown as a first-class authoring format.&lt;/p&gt;
&lt;p&gt;This reference documents the &lt;strong&gt;implemented&lt;/strong&gt; surface. For the design intent, see &lt;code&gt;_bmad-output/planning-artifacts/architecture-ai-cli.md&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="overview"&gt;Overview&lt;a class="anchor" href="#overview"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base URL.&lt;/strong&gt; The API is served from the same origin as your Lesstruct server, under the &lt;code&gt;/api/v1&lt;/code&gt; prefix. Example: &lt;code&gt;https://your-lesstruct.example/api/v1/content&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transport.&lt;/strong&gt; HTTPS in production. All request and response bodies are &lt;code&gt;application/json&lt;/code&gt;, except media upload which is &lt;code&gt;multipart/form-data&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication.&lt;/strong&gt; Every &lt;code&gt;/api/v1&lt;/code&gt; request carries an API key as a Bearer token (see &lt;a href="#authentication"&gt;Authentication&lt;/a&gt;). &lt;code&gt;/api/v1&lt;/code&gt; is &lt;strong&gt;Bearer-only&lt;/strong&gt; — there is no cookie/JWT fallback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioning.&lt;/strong&gt; The &lt;code&gt;v1&lt;/code&gt; URL segment pins the contract. Breaking changes ship under a new version segment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON conventions.&lt;/strong&gt; Keys are &lt;code&gt;camelCase&lt;/code&gt;. Strings are UTF-8. Timestamps are ISO 8601 strings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="authentication"&gt;Authentication&lt;a class="anchor" href="#authentication"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Requests authenticate with a personal API key in the &lt;code&gt;Authorization&lt;/code&gt; header:&lt;/p&gt;</description></item><item><title/><link>https://lesstruct.dev/docs/configuration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lesstruct.dev/docs/configuration/</guid><description>&lt;h1 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Lesstruct has two layers of configuration, each for a different concern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;config.toml&lt;/code&gt;&lt;/strong&gt; — your site&amp;rsquo;s &lt;em&gt;content schema&lt;/em&gt;: languages, custom post types, user profile fields, thumbnail variants. Edited by hand, version-controlled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment variables&lt;/strong&gt; (in &lt;code&gt;.env&lt;/code&gt; or the process env) — your &lt;em&gt;deployment configuration&lt;/em&gt;: host, port, database, secrets, SMTP, AI integrations. Treated as deployment state, not committed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This document covers both.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="anchor" href="#table-of-contents"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#where-the-files-live"&gt;Where the Files Live&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#environment-variables"&gt;Environment Variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configtoml-reference"&gt;config.toml Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#validation-rules"&gt;Validation Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#worked-examples"&gt;Worked Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#what-is-not-configurable-from-configtoml"&gt;What is NOT Configurable from config.toml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#upgrading-lesstruct"&gt;Upgrading Lesstruct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#troubleshooting"&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#quick-reference"&gt;Quick Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="where-the-files-live"&gt;Where the Files Live&lt;a class="anchor" href="#where-the-files-live"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Concern&lt;/th&gt;
					&lt;th&gt;Default location&lt;/th&gt;
					&lt;th&gt;Override via env&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Content schema&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;./config.toml&lt;/code&gt; in the working directory&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;CONFIG_DIR&lt;/code&gt; (directory), &lt;code&gt;CONFIG_FILE&lt;/code&gt; (filename)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Deployment config&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;.env&lt;/code&gt; in the working directory&lt;/td&gt;
					&lt;td&gt;process env wins over &lt;code&gt;.env&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Custom theme&lt;/td&gt;
					&lt;td&gt;empty (uses embedded theme)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;THEME_DIR=themes/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Plugins&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;plugins/&lt;/code&gt; in the working directory&lt;/td&gt;
					&lt;td&gt;not configurable (hard-coded)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Database&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;data/lesstruct.db&lt;/code&gt; (SQLite)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DB_DRIVER&lt;/code&gt;, &lt;code&gt;DB_PATH&lt;/code&gt;, &lt;code&gt;DB_DSN&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;config.toml&lt;/code&gt; is loaded once at startup from &lt;code&gt;CONFIG_DIR/CONFIG_FILE&lt;/code&gt;. The runtime does &lt;strong&gt;not&lt;/strong&gt; auto-merge or auto-generate anything; the file is read as-is. If the file is missing, the runtime falls back to defaults (one language: English, default &lt;code&gt;post&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; post types, default thumbnail at 370 px). Validation errors at startup are reported with a clear message; the server does not start with an invalid &lt;code&gt;config.toml&lt;/code&gt;.&lt;/p&gt;</description></item><item><title/><link>https://lesstruct.dev/docs/plugin-capabilities/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lesstruct.dev/docs/plugin-capabilities/</guid><description>&lt;h1 id="plugin-capabilities"&gt;Plugin Capabilities&lt;a class="anchor" href="#plugin-capabilities"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;Audience.&lt;/strong&gt; This is the &lt;strong&gt;developer-facing&lt;/strong&gt; reference for the Lesstruct
plugin capability manifest and host functions. It references source-tree
paths (e.g. &lt;code&gt;internal/plugin/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If you are a Lesstruct user who has installed the binary and wants to add
host function calls to a plugin, use the &lt;strong&gt;user-facing&lt;/strong&gt; snapshot bundled
with the &lt;code&gt;lesstruct-plugin-development&lt;/code&gt; skill at
&lt;code&gt;skills/lesstruct-plugin-development/references/plugin-capabilities.md&lt;/code&gt;. It
covers the same contract (manifest schema, host functions, security
model) but with no source-tree references.&lt;/p&gt;</description></item><item><title/><link>https://lesstruct.dev/docs/plugin-development/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lesstruct.dev/docs/plugin-development/</guid><description>&lt;h1 id="plugin-development-guide"&gt;Plugin Development Guide&lt;a class="anchor" href="#plugin-development-guide"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;Audience.&lt;/strong&gt; This is the &lt;strong&gt;developer-facing&lt;/strong&gt; reference for Lesstruct
plugin development. It references source-tree paths (e.g. &lt;code&gt;internal/plugin/&lt;/code&gt;,
&lt;code&gt;pkg/sdk/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If you are a Lesstruct user who has installed the binary and wants to write
a plugin against your installation, use the &lt;strong&gt;user-facing&lt;/strong&gt; snapshot bundled
with the &lt;code&gt;lesstruct-plugin-development&lt;/code&gt; skill at
&lt;code&gt;skills/lesstruct-plugin-development/references/plugin-development.md&lt;/code&gt;. It
covers the same contract (hooks, memory protocol, host functions, build
instructions) but with no source-tree references.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Lesstruct supports WebAssembly (WASM) plugins that extend functionality through hooks. Plugins are compiled from Go, Rust, C/C++, or any language that targets WASI.&lt;/p&gt;</description></item><item><title/><link>https://lesstruct.dev/docs/theme-development/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lesstruct.dev/docs/theme-development/</guid><description>&lt;h1 id="theme-development-guide"&gt;Theme Development Guide&lt;a class="anchor" href="#theme-development-guide"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;Audience.&lt;/strong&gt; This is the &lt;strong&gt;developer-facing&lt;/strong&gt; reference for Lesstruct theme
development. It references source-tree paths (e.g. &lt;code&gt;internal/api/template/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If you are an end user of Lesstruct — i.e. you have installed the binary and
want to customise the public site via &lt;code&gt;themes/&amp;lt;name&amp;gt;/&lt;/code&gt; — use the
&lt;strong&gt;user-facing&lt;/strong&gt; snapshot bundled with the &lt;code&gt;lesstruct-theme-development&lt;/code&gt; skill
at &lt;code&gt;skills/lesstruct-theme-development/references/theme-development.md&lt;/code&gt;. It
covers the same contract (CSS variables, template blocks, JS DOM contract,
CDN assets) but with no source-tree references.&lt;/p&gt;</description></item></channel></rss>