aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
diff options
context:
space:
mode:
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md76
1 files changed, 56 insertions, 20 deletions
diff --git a/AGENTS.md b/AGENTS.md
index b552615..132e9c2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -29,6 +29,11 @@ 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
+ 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
+ base-domain blog is only editable by the superadmin.
- App runs plain HTTP behind a reverse proxy; auth is a JWT cookie.
- Stdlib `net/http` ServeMux with method+pattern routes. No router library.
@@ -38,14 +43,16 @@ table and deployment notes.
cmd/blogspace/ main.go (serve|seed|migrate, superadmin + root blog bootstrap), seed.go
internal/config/ env → Config; RootSubdomain = "www"
internal/db/ pgxpool + goose; migrations/*.sql embedded
-internal/store/ models + hand-written SQL (users, blogs, pages, posts, images, sections)
+internal/store/ models + hand-written SQL (users, blogs, pages, posts, images, sections, modules, menu)
internal/auth/ bcrypt, JWT issue/parse, cookie, HMAC CSRF
internal/markdown/ Render(md) → sanitized HTML
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), theme.go
+ routes.go (all routes), handlers_*.go (sections = announcements, layout = modules + menu)
+ theme.go (colours/fonts/layout switches), layout.go (module kinds, archive grouping)
templates/ (embedded; layouts/, partials/, auth/, dashboard/, admin/, blog/)
- static/ (dashboard.css "paper & ink" look — plain CSS, no variables/flex; blog.css)
+ static/ (dashboard.css "paper & ink" look — plain CSS, no variables/flex;
+ blog.css — floats except the three-column `.cols`, which is flexbox)
```
## Key mechanics
@@ -79,27 +86,53 @@ internal/web/ server.go (host router, middleware, render helpers)
Handlers call `s.render(w, r, "dir/file.html", map[string]any{...})`;
page data is under `.Data.<key>`, common fields (`.User`, `.Blog`,
`.CSRF`, `.Flash`, `.Error`, `.BlogURL`, `.RootURL`) are top level.
- Flash messages travel as `?ok=` query param via `redirectOK`. Add new
+ 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`.
`ParseTheme` merges over `DefaultTheme()` and `normalize()` clamps every
- value to an allowlist (hex colours, enum strings, uuid image ids) — this
- is what makes inlining the generated CSS safe. Add new options in the
- struct, `normalize`, `ThemeFromForm`, the CSS template and
- `dashboard/design.html`; defaults must reproduce the look blogs had
+ value to an allowlist (hex colours, enum strings, uuid image ids, column
+ percentages) — this is what makes inlining the generated CSS safe. Add
+ new options in the struct, `normalize`, `ThemeFromForm`, the CSS template
+ and `dashboard/design.html`; defaults must reproduce the look blogs had
before the option existed. Non-CSS options (`ShowDates`, `DateFormat` via
- `FormatDate`, `FooterShowRSS`, `Favicon`) are read by the blog templates.
+ `FormatDate`, `Favicon`, `Logo`) are read by the blog templates.
Optional colours (`LinkHover`, `NavHover`) are `""` = inherit and come
- from a colour input paired with a `<name>_custom` checkbox. Image fields
+ from a colour input paired with a `<name>_custom` checkbox, shown/hidden
+ with CSS only (`.hoverpick > input:not(:checked) ~ …`). Image fields
use the `imagepick` partial's radios: `none` clears, a uuid selects, no
value keeps (`pickImage`); an upload in `<name>_file` wins.
- Options that only apply to one menu layout (`NavAlign` for horizontal
- menus, `NavSidebarWidth` for the sidebar) are shown/hidden in
- `design.html` with CSS only (`#np-*:checked ~ .only-*` in dashboard.css,
- same trick for the `_custom` hover checkboxes) — no JS, and browsers
- without `:checked` just show everything.
`Presets()`/`WithPreset` are the colour schemes (`POST /b/{sub}/design/preset`,
- colours only); `POST /b/{sub}/design/reset` stores `DefaultTheme()`.
+ colours only); `POST /b/{sub}/design/reset` stores `DefaultTheme()` and
+ calls `ResetModules`.
+ The layout switches (`HeaderOn/LeftOn/RightOn/FooterOn`, `LeftWidth`,
+ `RightWidth`, `KeepColumns`) also live in the theme but are edited from the Layout tab
+ (`LayoutFromForm`, `SetAreaOn`); `ThemeFromForm` never touches them.
+- **Layout** (`modules` table, `store/modules.go`, `web/layout.go`,
+ `handlers_layout.go`, `/b/{sub}/layout…`): a blog page is six areas —
+ `header`, `left`, `above`, `below` (the two custom-HTML slots inside the
+ main column, one module at most), `right`, `footer` — each an ordered
+ list of modules (`kind`: title, logo, menu, archive, recent, html, rss,
+ text, sitemap; `moduleKinds` says which kinds an area accepts). Modules
+ have an optional `title` (heading), `body` (raw HTML or footer text) and
+ `count` (recent posts). `blogView` builds a `Layout` (`buildLayout`,
+ areas that are switched off are dropped) and only fetches the archive
+ index / recent posts when a visible module needs them. A side column is
+ laid out only when it has modules, or always with `theme.keep_columns`
+ (`HasLeft/HasRight` → `body.has-*`
+ classes, which also lift the `.wrap` max-width so the page goes
+ full-width; column widths in the theme CSS under `@media (min-width: 701px)`);
+ below 700px `.cols` becomes block flow so the order is main → left →
+ right → footer. Everything is rendered by the `module` template in
+ `layouts/blog.html`. New blogs get `insertDefaultModules` (title + menu
+ in the header, RSS in the footer) — keep it in step with the layout
+ migration's defaults.
+- **Menu** (`menu_items` table, `store/menu.go`): the single ordered list
+ the Menu module shows — page entries (`page_id`, unique per page, cascade
+ on delete) and custom links (`label` + `url`, validated by
+ `validLinkURL`: http(s), mailto or a `/path`). `Page.ShowInNav` is
+ derived (`EXISTS` on `menu_items`); `CreatePage`/`UpdatePage` add or
+ remove the entry so the page form's checkbox and the Layout tab agree.
- **Favicon**: `static/favicon.svg` + `favicon.png` are the defaults, linked
from both layouts. A blog can set `theme.favicon` (an image id) which the
blog layout links instead. `GET /favicon.ico` on every host
@@ -116,9 +149,11 @@ internal/web/ server.go (host router, middleware, render helpers)
`s.readUpload(r, "inline_image")` + `appendImageMD` (the no-JS path).
- **Announcements** (`sections` table, `store/sections.go`,
`handlers_sections.go`, `/b/{sub}/announcements…`): per-blog notices with
- `placement` (above|below|sidebar), `style` (plain|note|warning), `enabled`
- and `sort_order`. `blogView` loads the enabled ones and `splitSections`
- routes `sidebar` to the left-sidebar layout only (otherwise above).
+ `placement` (`<column>-<position>`: left|main|right × top|bottom, split by
+ `Section.Column()/Position()`), `style` (plain|note|warning), `enabled`
+ and `sort_order`. `blogView` loads the enabled ones and `placeNotices`
+ groups them by placement, moving ones for a side column that is not laid
+ out into the main column.
Rendered by the `notices` template in `layouts/blog.html`; CSS classes
`notice notice-<style>` use translucent colours so they fit any theme.
- **Dashboard nav**: `dashnav.html` marks the current tab with `hasPrefix
@@ -160,4 +195,5 @@ superadmin password to `admin`. Production refuses both.
## Things deliberately not built (ask before adding)
Drafts/preview, custom CSS, comments, multiple blogs per user, email,
-user self-registration, custom domains per blog, image resizing, search.
+user self-registration, custom domains per blog, image resizing, search,
+sanitising the custom HTML module (owner's decision, see above).