aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
Commit message (Collapse)AuthorAgeFilesLines
* Make the post form's tag input a box with a folded list of chipsgrm2026-09-151-4/+6
| | | | | | | | | | | A checkbox per existing tag stops working once a blog has dozens of them. The form is now one comma-separated box, prefilled with the post's tags, and the blog's tags sit folded under it as chips; a small script toggles a clicked chip in the box, and without it the chips are a plain list to copy from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add post tags, with a tag page and two side-column modulesgrm2026-09-151-8/+31
| | | | | | | | | | | | | | | | | | | | Posts can now carry tags, set on the post form as a checklist of the blog's existing tags plus a comma-separated box for new ones (no JS). A tag is a name and a unique slug, so "Go" and "go" are one tag and Greek tags get readable URLs; tags no post uses any more are deleted. On the blog, tags appear under the post date and link to /tag/<slug>, which lists the published posts from every page, paginated like a page. Two new layout modules show them: a Tags list (with counts, by use) and a Tag cloud (alphabetical, sized by use). Both are only fetched when a visible module needs them. The article loop and pager move to a shared postlist partial; while there, the pager stops adding a trailing slash — /news/?p=2 was a 404 because a {page} wildcard never matches one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Give the editor a Markdown toolbar and preview, restyle the dashboardgrm2026-09-151-8/+16
| | | | | | | | | | | | | | | | | | | | The post/page/announcement editor was a bare textarea, so bloggers had to know the Markdown syntax by heart. It now has a toolbar (bold, italic, strike, heading cycle, quote, code, lists, rule, link box, insert file), Ctrl+B/I/K, list continuation on Enter and a Write/Preview toggle that renders the text through the same goldmark + bluemonday pipeline a save uses (POST /b/{sub}/preview; nothing is stored). Every edit goes through execCommand("insertText") so browser undo keeps working. The toolbar stays hidden without JavaScript, leaving the old form untouched. The dashboard moves from the "paper & ink" look to a lean one: neutral surfaces, 1px borders, one blue accent, system sans, with custom properties and flexbox/grid now allowed there (blog.css is unchanged). Class names were kept so the templates barely change; a global [hidden] rule keeps flex containers from overriding the hidden attribute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Turn the image library into a file library, with a per-blog upload limitgrm2026-09-141-22/+58
| | | | | | | | | | | | | | | | | | | | | | | | | | Bloggers want to attach PDFs, archives, audio and other files to posts, not only images. The Images tab becomes Files: any type is accepted, listed by kind with search, paging, rename and multi-file upload, and the editor's paste/drop/"Insert file" takes anything (images are shown, everything else becomes a link). The default limit goes from 5 to 10 MB and the superadmin can override it per blog from /admin/. Files stay in Postgres so one pg_dump is still the whole blog. The bytea column is STORAGE EXTERNAL and /media streams it in substring() slices, so serving never holds a whole file in memory whatever limit a blog gets. Serving any type on the root domain, which carries the session cookie, needs a policy: uploads are typed by sniffing (the extension may only refine a generic sniff to an allowlisted type) and only images, PDF, plain text, audio and video render inline; HTML, SVG, XML, scripts, archives and binaries always go out as application/octet-stream with Content-Disposition: attachment. The body cap moves out of requireAuth into guardPOST, which runs after withBlog has resolved the blog and so knows its limit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Let the blogger set a post's date, when writing it and afterwardsgrm2026-09-141-0/+7
| | | | | | | | | | | The post date is posts.created_at: it already drives ordering, the archive, the feed and the displayed dates, so making it editable is a form field rather than a new column. The datetime-local input degrades to a text box on old browsers, so the server also accepts the value typed by hand; blank keeps the current date. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add languages: English and Greek, chosen per blog on the Settings tabgrm2026-09-141-2/+31
| | | | | | | | | | | | | | | | | | | | | | | Every blog and its dashboard were hard-wired to English. A blogger can now pick the language of their blog; it switches the whole dashboard and the blog's fixed text — post dates, archive months, the RSS link, the pager, the 404 page — while what the blogger wrote is left alone. The new internal/i18n package keys translations by the English string, so an untranslated key renders as English rather than blank, and TestGreekCatalogComplete scans the templates and handlers to fail when the Greek catalog misses a key or keeps a stale one. Templates are compiled once per language with t/tf/date/postdate/month closed over the language, so they need no data plumbing. The language lives in settings.language (blog migration 00002), not in the theme, so "Reset design" does not touch it. Public pages use the blog's language; management pages use the logged-in user's own blog's, so a superadmin editing someone else's blog keeps theirs; the login page follows Accept-Language. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Drop the single-database split and re-baseline the control migrationsgrm2026-09-141-5/+6
| | | | | | | | | | | | | | | | | Every deployment has been through the per-blog split, so the code that performed it (split.go, control migrations 00002–00007) is dead weight, and a fresh install replaying six migrations only to drop the tables again was silly. The control chain is now a single 00001_init.sql with the final users and blogs shape, matching the blog chain. Goose ignores versions recorded in the database that no longer exist in the source, but refuses a future migration numbered below the database's highest version. Databases that went through the split therefore need `DELETE FROM goose_db_version WHERE version_id > 1` once; README and AGENTS.md say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Give every blog its own Postgres databasegrm2026-09-131-10/+45
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | A blog is now a database of its own (blog_<sub>) on the same server: one pg_dump is a complete backup of a blog, one psql restores it, and nothing a blog's queries do can reach another blog's rows. The control database (DATABASE_URL) keeps only users and the blog registry (id, owner, subdomain, db_name); title, tagline and theme move into a one-row settings table next to the content so the dump really is everything. db.Cluster holds the control pool plus small, lazily opened per-blog pools. store.Store (control) hands out a store.BlogStore per blog; every blog_id parameter and column is gone, the database is the scope. Handlers reach it through blogStore(r), which resolveBlog puts in the context next to the blog. Existing data is moved in place by control migration 00006, a Go migration that runs inside the control transaction: it creates and migrates each blog database, copies the rows preserving ids, and marks the registry; 00007 then drops the old tables. Either every blog is moved or the control database is untouched. /media/{id} now serves the host's blog only, so dashboard previews on the root domain use /b/{sub}/media/{id}. Subdomains are capped at 58 chars so "blog_" + name fits a Postgres identifier. Deleting a user drops their database. Store.Open resets a blog's pool and retries once so a database restored under a running app (dropdb --force, createdb, psql) just works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Drop the content width option: the page is always full widthgrm2026-09-131-2/+4
| | | | | | | | | | With the column grid, a centred max-width box meant the main content did not fill the page when no side columns were shown. The column percentages now divide the whole page; a blogger who wants a narrower, centred main column keeps the columns' space instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add the Layout tab: five areas with modules, columns and a menu editorgrm2026-09-131-20/+56
| | | | | | | | | | | | | | | | | | | A blog page is now header, left column, main content, right column and footer, each holding an ordered list of modules (blog title, logo, menu, archive by year/month, recent posts, custom HTML, footer text, RSS link, site map). Side columns have percentage widths, can be hidden, and can keep their space when empty; on phones they stack under the posts. The menu becomes its own ordered list mixing pages and outside links. Announcements are placed per column at its top or bottom. Modules and menu items get tables; the migration derives them from each blog's old theme (nav position, footer text, show-in-nav pages) so existing blogs look the same. Custom HTML is deliberately stored and served as-is (owner's decision; see AGENTS.md for why the blast radius is the blogger's own origin). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add a favicon and rework the Design pagegrm2026-09-121-2/+20
| | | | | | | | | | | | | | | | | | | | | Blogs had no icon at all, and browsers probing /favicon.ico ended up rendering the 404 page. Ship a default paper & ink icon (svg + png) and let each blog replace it from the Design page; /favicon.ico now serves the default or redirects to the blog's own image. ICO uploads accepted. The Design page gains colour schemes, reset to defaults, thumbnail image pickers, jump links and a sticky save bar, plus new options: heading font, link underline/hover, content box style and padding, header image height, post dates and format, footer RSS toggle, menu alignment/style/hover and sidebar width. The Menu card picks the position with illustrated tiles and shows only the options that apply to it, using CSS :checked rules rather than JavaScript. All new defaults reproduce the previous look, so existing blogs are unchanged until edited. Theme is jsonb, so no migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add announcements: blog-wide notices on every page and postgrm2026-09-121-5/+22
| | | | | | | | | | | A blog can have any number of announcements ("sections" in the schema): a book club's next meeting, this month's pick, a holiday closure. Each has a placement (above or below the posts, or under the menu when the menu is a left sidebar), a look (highlighted, warning, plain), an order, and an on/off switch so recurring notices can be kept around while hidden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Rename /login to /webadmin and reach it from every bloggrm2026-09-121-2/+4
| | | | | | | | | | The login URL is less guessable, bloggers can type /webadmin on their own blog and get bounced to the root login page (and back to their dashboard after logging in), and the public root blog no longer advertises the admin entry point in its footer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* AGENTS.md: JS is to be kept light, not forbiddengrm2026-09-121-6/+8
| | | | | Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add AGENTS.md for AI coding sessionsgrm2026-09-121-0/+124
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A