From 4a6f987bf7f9d73e9dd33d225fd2d73e9bf69a88 Mon Sep 17 00:00:00 2001 From: grm Date: Wed, 16 Sep 2026 19:10:13 +0300 Subject: Merge the Layout and Design tabs into one form with a single Save The look and the layout of a blog were split over two tabs that cross-linked each other, and the Layout tab saved every toggle, move and add straight away through a dozen tiny forms. Now one Design tab holds it all, organised by part of the blog (colours & fonts, header, menu, content, side columns, footer, logo & icon) behind a sticky side index, and one Save stores the theme, every module and the menu atomically (BlogStore.SaveDesign). Module settings are edited inline; Discard changes throws the edits away; a validation error shows the form again exactly as it was sent. The image pickers no longer render the whole library four times over: a select of the newest images, and a script-driven panel that fetches the library page by page (GET /files as JSON) with lazy thumbnails. New options: posts per page, site title size, footer alignment. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- AGENTS.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 17 deletions(-) (limited to 'AGENTS.md') diff --git a/AGENTS.md b/AGENTS.md index 1ea5ef9..2560696 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ table and deployment notes. - Changes are live immediately — there is no draft/preview system. The UX is "save, then refresh your blog tab"; keep the "View blog ↗" links. - Theme customisation is a structured form only; **no custom CSS input**. - The one escape hatch is the **custom HTML module** (Layout tab), which the + The one escape hatch is the **custom HTML module** (Design tab), which the owner chose to store and output **unsanitised**. The blast radius is the blogger's own origin: the session cookie is host-only on the base domain and `HttpOnly`, management POSTs need the HMAC `_csrf` token, and the @@ -63,8 +63,9 @@ internal/i18n/ languages, T/Tf (English keys → catalog), FormatDate/Mon el.go is the Greek catalog, el_months.go the month tables internal/slug/ Make/Valid/WithSuffix (Greek transliteration included) internal/web/ server.go (host router, middleware, render helpers) - routes.go (all routes), handlers_*.go (sections = announcements, layout = modules + menu, - files = the upload library), filetype.go (what an upload is, how it may be served) + routes.go (all routes), handlers_*.go (sections = announcements, design = the one + look-and-layout form, files = the upload library), design_form.go (parsing the + design form's module and menu rows), filetype.go (what an upload is, how it may be served) theme.go (colours/fonts/layout switches), layout.go (module kinds, archive grouping) templates/ (embedded; layouts/, partials/, auth/, dashboard/, admin/, blog/) static/ (dashboard.css — lean look; custom properties, flexbox and grid @@ -136,28 +137,57 @@ internal/web/ server.go (host router, middleware, render helpers) Flash messages travel as `?ok=` query param via `redirectOK` (a `#anchor` in the target is kept after the query). Add new templates to `TestAllTemplatesParse`. -- **Theme** (`web/theme.go`): struct stored as jsonb on `blogs.theme`. +- **Design tab** (`handlers_design.go`, `dashboard/design.html`, + `GET|POST /b/{sub}/design`): the whole look and layout of a blog is **one + form with one Save** — theme values, the area switches and column widths, + the modules of every area (with their settings inline, folded in a + `
`) and the menu — stored atomically by `BlogStore.SaveDesign` + (theme + full module list + full menu in one transaction: rows with an id + are updated, without one inserted, missing ones deleted). Sections are cards + with ids (`designSections`) a sticky side index jumps to; the hidden `at` + field says which one was in view so the flash returns there. Module rows are + `mod..{id,area,kind,title,body,count,pos,del}` and menu rows + `menu..{id,page,label,url,pos,del}` (`design_form.go`: `parseModules`, + `parseMenu`); `i` only tells rows apart, `pos` orders them. The inline + script reorders rows in the DOM (renumbering `pos` on submit), clones the + `