The descriptor matrix for posts, categories and tags: Describe per locale
with and without Polish unprefixed, Match exact and inexact per locale,
Records paging and cursors, host patterns, unknown and malformed ids;
journal-tag with tag_pages by default, on and off; translated slugs in
menu items and matching, including a changed Polish slug.
GET /_journal/api/v1/posts/{slug} now looks the slug up in translate's
slug index for the request locale after the default-locale slug and
before the numeric id. TestTranslatedSlug covers the translated slug,
the default slug, the id and an unknown slug.
- journal-post, journal-category and journal-tag carry pact.ContentDescriptor
(controller, morph name, Describe, Records, Match) through signpost
- Match reads the locale prefix and the pattern with signpost.MatchPattern,
finds the record by its slug in that locale, the default slug, another
locale's slug or :id, and is exact only on the record's own URL (D-20c)
- journal-tag (/blog/tag/:slug, P-11) with references and Resolve; host key
golem15.journal.tag_pages false empties tag URLs and stops tag matches
- TestContentDescriptors, TestJournalTagType; README documents both
- POST/PUT/DELETE /_journal/api/v1/posts fire RecordSaved/RecordDeleted
through cabana.EmitRecordSaved/EmitRecordDeleted, so a slug changed over
MCP reaches subscribers like an admin save
- PUT reports the changed columns and each submitted translation that
changed, read before the write (a translated slug per locale)
- TestAPIRecordEvents covers POST, PUT slug change and DELETE
- journal-post registers a pact.ContentDescriptor (posts controller, Post
morph name, Describe with per-locale URL, title, excerpt, image, words)
- descriptors read through cabana.TxFromContext inside a save
- TestRecordSavedDescribesNewURL: an admin slug change reaches a subscriber
after commit and Describe answers the new URL; a failed save fires nothing
pier now gives a backend subject only to a grant whose consented
administrator (pact.MCPGrant.BackendUserID) is the one linked; the fake
verifier's grants name the linked backend user.
draft_post declares Writes, and through a served pier endpoint a read-only
grant's prompts/list omits journal_draft_post while a read and write grant
lists it (C-IN-01).
- the seven tools compile in pier's catalog against the plugin's routes
- every input field is a path segment, a list query key or a create/update
body key the routes read
- the draft prompt renders with and without its optional tone
- journal.Plugin.MCP: list_posts, show_post, list_categories, list_tags,
create_post, update_post, delete_post and the draft_post prompt, guard
backend, listed with golem15.journal.access_posts; update fields are
optional so only sent keys reach the route
- optionalBackendPrincipal honours the MCP dispatch marker (no bearer
short-circuit; anonymous reads unchanged)
- require summercms v0.4.4
- TestJournalMCPSmoke, TestOptionalBackendPrincipalDispatch
Every command gets long help and examples, descriptions in imperative
sentence case, and a warn-only TestCommandsDocumented over bonfire.CheckDocs.
The plugin logs through a diary component logger instead of the process
default logger. Requires SummerCMS v0.4.0; command names and flags unchanged.
TestJournalAuthorProfile covers a backend user with only a subtitle and
metadata that is empty, null, not JSON or not strings: empty strings and a
null avatar, with the key set unchanged.
- GET /_journal/api/v1/authors/{author} adds subtitle and description from
backend_users.metadata and avatar (the admin avatar's public URL or null)
- TestJournalAuthorProfile pins the seven keys and checks the three values
Admin saves purged the site cache; the token API's create, update,
delete and featured-image writes did not, so API-published posts waited
for the site's cache TTL.
- framework release with cooler, site-cache purge and the 19.3 transport surface
- go mod tidy adds go-redis, otter and the AWS SDK modules as indirect requires
- site_cache_keys_test.go: wireSiteCache on a config without a database
- rename the Docker admin test to TestCategoriesAdminFrameworkKeysWin so the
unit test owns TestSiteCacheFrameworkKeysWin (one name per test binary)
- classes.SiteCachePurger, NewSiteCachePurger, PurgeSiteCacheAfterCommit and
the key and limit constants stay as deprecated aliases for one release
- wireSiteCache: framework keys site_cache.* win (Journal keys ignored with
a warning); with only golem15.journal.site_cache.* set it publishes the
purger from them with a deprecation warning, so menu and FAQ saves purge
through it too
- controllers take the purger from cooler.SiteCacheFromApp
- TestSiteCacheFrameworkKeysWin; README documents both key sets
journal-post resolved any post by id, so a menu entry or sitemap row
pointing at a draft or future post published its title and slug on the
anonymous menu API and in sitemap.xml. resolvePost now applies the
published filter; the reference list still offers drafts so editors can
wire a menu before the post goes live.
- TestMenuItemTypesPatterns (Docker-free): the five types, ordered
placeholder fill, date parts only for published posts in UTC,
alternates and the unprefixed locale, translation fallback, reference
parse, category tree, refusals without a booted app
- TestMenuItemTypes (Postgres): host overrides of one record type reach
only its list types, item and list-key overrides, references, nesting
at every depth, order with ties by id, published filters, descendant
categories, alternates in sort_order with ties by id
go test passes -test.short=true, so scanning os.Args for -test.short
never matched and every -short run started a Postgres container. The
three TestMains now parse the flags and ask testing.Short.
- journal-category (category tree references, nesting returns the subtree),
all-journal-categories (by name), all-journal-posts (published, by title)
and category-journal-posts (category and descendants, published, by title)
- list types take the record type's DefaultPattern and set PatternFrom, and
build their items from the layered pattern
- menuitem labels for all five types in en and pl
- README section Menu item types and API reference rows
- TestMenuItemTypesSmoke covers host and item overrides, nesting, ordering,
the published filter and x-default
- Plugin implements pact.HasMenuItemTypes with a lazy app accessor
- journal-post: references by title, resolve fills the layered pattern by
ordered replacement (id, slug, category, year, month, day) and builds the
ordered per-locale alternates with translated slugs (x-default without
enabled locales)
- menuitem.journal_post labels in en and pl
- TestMenuItemTypesSmoke: host override, item override, sort_order alternates
Cover every write route and optional-auth Index/Show across journal,
star, read-only, admin:read, unrelated, revoked and malformed tokens,
plus publish permission under a journal token.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Register journal and journal:read via pact.HasTokenScopes
- Switch API auth to cabana.ResolveBackend with golem15.journal.* cover
- Map ErrTokenScope to PHP Insufficient permissions bodies
- Add TestResolveErrorMapping and TestJournalTokenTracer
Co-authored-by: Cursor <cursoragent@cursor.com>
- TestSiteCachePurgeConfig: every refused configuration names its key and
never the token; https anywhere and http to loopback are accepted
- TestSiteCachePurgeRequest: the contract request, Info and Warn lines,
the 2 s timeout, a cancelled parent context, no token in any log line
- Postgres: category create, update and delete and post create, untouched
update (D-03 bytes kept), failing and unreachable purges and delete each
purge once after the commit; failed saves send none
16.2-03 moved post content to type: mlblogeditor with htmlColumn:
content_html (D-08); the Phase 15 mlmarkdown assertion was stale and kept
the postgres gate red (G-16.2-1).
- posts: FormAfterCreate/Update purge after the content_html translations,
new FormAfterDelete; categories: FormAfterCreate and FormAfterDelete
- NewSiteCachePurger refuses half configuration, non-http(s) URLs, user
info, fragments, http to a non-loopback host and tokens under 32 chars;
Boot logs the endpoint host and path once
- TestPostsAdminPurgesSiteCache smoke test
- README: Configuration section (keys, contract, timing, scope), API rows
- classes.SiteCachePurger posts {kind, slug} with a bearer token to
golem15.journal.site_cache.purge_url, 2 s timeout, logged, never fails a save
- PurgeSiteCacheAfterCommit runs it through lagoon.AfterCommit
- Boot builds and publishes the purger from the two config keys
- categories FormAfterUpdate registers the purge; Postgres test proves
one authorized request after the commit
crypto.randomUUID exists only in a secure context, so an admin served
over plain HTTP from a LAN host threw on every editor upload while the
status line still said uploading. uploadRequestId falls back to a v4
UUID from crypto.getRandomValues, or a time and counter id when no
crypto object exists.
captureAttributes and renderCaptured dropped only on* names, so id,
name, style, srcset, background and the like from stored HTML were
rendered by ProseMirror into the live admin DOM (DOM clobbering of
document.querySelector, fixed-position overlays, remote loads). htmlAttrs
now keeps an allowlist: class, title, alt, width, height, colspan,
rowspan, start, rel, target and data-*; href and src stay with the link
and image nodes. Other attributes are dropped from a block once it is
edited; untouched documents and untouched blocks keep their bytes.
uniqueMediaKey checked Exists and the write followed without a
precondition, so two uploads of the same name (clipboard images are all
image.png) could both pick the free key and the second replaced the
first. StoreMedia now reserves the chosen key in-process while it
writes, and writes with IfNotExist; a key another process stored after
the check moves the upload on to the next free name.
LooksLikeHTML decided by the first character alone, so a markdown post
opening with a hero <img>, a <div> or a comment was served as raw
markdown and its first edit stored ## and ** markers as HTML in both
columns. Load now serves such a value as HTML only when content_html is
empty or missing, or holds that same HTML (the value or SanitizeHTML of
it, white space at the ends ignored); otherwise it serves content_html.
A translation that carries only content_html (an API write through
FormatTranslation) opened as an empty editor, and the first edit in
that locale silently replaced the translated body. Load now serves the
htmlColumn attribute whenever the stored content is blank and that
attribute is not empty; an untouched save echoes it and is skipped, an
edit writes the edited HTML to both columns.
The preview tests put payloads with remote URLs into the preview frame's
srcdoc; happy-dom tried to load them (a DNS lookup of example.test on every
run). Tests must not reach the network.
- schema: every raw HTML tag rule, figure shapes, span mark, unmodelled
attributes, handler attributes dropped, links, tables, no trailing node
- serializer: block reuse after an edit, a deletion and an insertion,
separators and comments kept, captureBlocks fallbacks
- embeds: parseEmbedUrl table, placeholder round trip, isPolicyUrl table,
upload id matching and timeout, labels, disabled and invalid
- Decode: clean values byte for byte, nil, non-string refusal, the 35
unsafe payloads stripped with notices exactly when changed
- notices order, ten-name cut, plural forms in lang/en and lang/pl
- LooksLikeHTML, options, ListValue, Upload sniffing and safe names
- StoreMedia layout, suffixes, no overwrite, size and folder rules
- editor corpus and policy cross-check extended in place: more negatives,
notices only when stripped, stable second pass
- TestPostsEditorHooks: create and update, default and non-default
writes, no writes, no writer
- TestCategoriesAdminBlogEditor (Docker host): untouched saves keep
imported markup and NULL, clean edit, strip and report, blank to NULL,
editor upload
- element.test.ts: an untouched value survives Source tab, tab buttons,
preview and reattach byte for byte with no summer-change
- TestBlogEditorLoad: HTML verbatim, markdown as content_html, FormatHTML
fallback, nil and empty values
- TestPostsAdminResaveKeepsHTML (Docker host): GET then PUT the served
values for an HTML and a markdown post, exact bytes, no notices, then a
clean edited save in both locales
- The preview re-renders 300 ms after the last WYSIWYG edit, Source input or external value, never while off or after detach.
- YouTube placeholders show as img.youtube.com thumbnail cards with no player; the preview copy drops active elements and on* attributes, and its stylesheet uses colours resolved from the host's --c-* values.