Releases: BaryoDev/barakoPress
Release list
0.8.0
-
The look check takes
maxDiffPixels, a count of pixels a pair may differ by beside its ratio,
for a page tall enough that any ratio lets a whole section through. Unset, a pair is judged as it
was. The summary prints the pixel count beside the percentage. -
The docs search box works in a Next app. Its attribute names came from a
"use client"module,
which a server component receives as references rather than strings, so the box rendered without
them and its client code wired nothing. They now live insrc/blocks/search-attrs.ts. -
A tree's items link where the collection's
hreffield says they are read, when it maps one and
it is a path on this site: the sidebar, previous and next, the in-page index and the sitemap,
through the exportedtreeItemHref. An item with no such field, or an absolute address in it, is
linked at its route and slug as before. The sitemap followshreffor a tree only, without its
fragment or query, and lists a URL once.TreeAside
andTreeSidebartake asummary({ line, title }) that the closed phone control says in place
of the product, the section and the page, for a page the tree does not hold. -
A collection whose index is off frees its one-segment route for a page at exactly that path
wherever it was configured, the post collection at/blogincluded. Before, only a collection a
tenant's settings brought was freed, since the build config reserves its own routes. Only a slug
held by a collection's route and nothing else is freed: what the engine serves at the root, a slug
the build config'sreservedSlugsor a tenant'sReservedSlugsnames, and the blog's/authors
and/categoriesstay reserved (the newheldSlugslists them). Anything below the route is still
the collection's. -
A
sourcewithmode: "all"reads every row of a collection, fifty to a request and up to 500,
as one of the page's reads, for a page that is a whole collection such as a changelog. Arepeat
draws every row its source read unlesslimitnames fewer, and the blocks its rows draw spend a
budget of their own (10,000) rather than the page's 400, and so do a grouped source's groups. The
requests past each source's first come at once, out of 16 a page'sallsources share. A source
that could not read everything it matched, a repeat or a grouped source cut short by the budget,
says so in the server log, and{{count}}stays what the collection holds. A sum and a distinct
count are worked out once per source.MAX_ALL_ROWS,MAX_ALL_REQUESTSandMAX_ROW_BLOCKSare
exported. A source inoneorlistmode reads what it did. -
{{distinct.<Field>}}inside asourceis how many different values a field holds among the
rows read, each entry of a list counting on its own and an empty field adding nothing: the number
of repositories a list of issues spans, beside{{count}}of the issues. A source that read part
of what it matched has no distinct counts, so the fallback renders rather than a count of one page. -
The element a primitive draws, for a design rebuilt from primitives and recipes
(arnelirobles/barakocms-site#46). A block wearing a recipe is its own cell: its list wrapper is
display: contents, so the recipe's element is the flex or grid item its parent lays out, an
inline one included: in a column list a link wearingdisplay: inline-blockis stretched to the
column's width unless its recipe setsalign-self: flex-startorwidth: fit-content. Such a
wrapper carriesdata-bp-contents, and a hue flow turns the element inside it.text
takestag(p,span,code,strong,em,h1toh4),decorative(hidden from a
screen reader) andtitle.stackandpaneltakehref, which makes the whole container a
link (an address off the site withrel="noopener noreferrer"), dropped when what the container
holds renders a link or a control of its own, andsection,stackandpaneltakeanchor,
drawn as the element'sid. AfilterBar
takesrecipe,buttonRecipeandpressedRecipefor its row and its buttons. A site may keep
400 recipes, up from 100. A block that uses none of this renders as it did. -
The tree screens take a site's styling (#130). Every colour, gap, padding, radius and font size in the sidebar,
the switcher, the search box, the pager and the edit link is read as
var(--t-tree-<name>, <today's value>), so a tenant'sTokensrestyle one part at a time and a
site that names none draws the same pixels as before. Each part carries abp-tree-*class. A
tree'svariantpicks the layouts a token cannot:switchertabsorlist,sidebarplain
orboxed,railfor an "on this page" column of the item's headings,pagerwideorhalves;
each component takes the same as avariantprop. A product takes anote.searchIndex: true
draws the tree's titles and headings into the page and filters them as the reader types.
treeSearchIndex(tree)builds that index once per tree read, anditemHeadingstokenises a body
once however many pages list it. New exports:TreeRail,TreeAside,treeVariant,
treeSearchIndex,itemHeadings,markdownHeadings. New label:onThisPage.
Two more variants:search: "compact", one well with a magnifier, the input named for a screen
reader and a "/" key hint, its results floating; anddisclosure: "closed", the sidebar closed on a
phone under a control naming the page, with no script. A tree'siconsmay point both glyphs at a
site's own sprite. Every label paragraph carriesbp-label,searchEmptymay say{query}, and
there is acloseContentslabel. The index keeps every page's title past its 2000 entry limit and
drops headings instead, saying so once; an entry is a plain link styled from one stylesheet; the
results are put away when focus or a press goes elsewhere; and a box with nosearchPathhas no
form, so Enter never reloads the page. -
A
filterBarblock inside asource(#129) draws one button per distinct value of a field among
the source's rows, in the order first seen or asordersays, after an "all" button. The buttons
are toggles witharia-pressed, not a tablist. The rows stay server-rendered: the binder marks
each with the bar's id and its values, and a click writes one rule that hides the rows without the
value, escaped as a CSS string and winning over inlinedisplay.separatorsplits a text field
holding several values, andhideEmptyGroupshides a group of a grouped source once none of its
rows is left. A bar offers at most a hundred values, and a row of a nested source belongs to both
bars. A site needs no hide rule of its own. A source with no bar renders as it did. -
Plugin packages (#25). A plugin is an npm package whose default export is
definePlugin({ name, blocks }), its blocks written withdefineBlockand checked at compile time
the same as the built-ins. It reaches a deployment through a derived image:npm packtarballs in a
directory passed to the Dockerfile as thepluginsbuild context, built from the engine's source at
a pinned release tag (examples/derived-image/compose.yml). One image carries every plugin, and a
tenant renders a plugin's blocks only when itsPluginssite setting names it; a build-time site
setspluginsin its config. Until then the blocks are not in/api/blocks, a page holding one
renders without it, and a preset drawing one is left out./api/blocksaddsplugins, each
installed plugin with whether the tenant enabled it, andpluginon each block a plugin added.
createBlockRegistrytakesplugins, and refuses a plugin block whose name is already taken. A
plugin block is passed its props, slots and theme and not the config, but its code runs in the
server with full access, so installing a plugin is trusting it with the deployment. Every plugin's
code is loaded for every tenant the container serves, so tenants that must not share plugins need
separate deployments. A plugin's dependencies must be bundled in its tarball, since the install
runs offline, and a plugin named like a package the engine has is refused. An image built with no plugins renders exactly as before. -
Style recipes, and inline marks in text (#131).
StyleRecipesin the site settings (or
theme.recipes) holds named looks, each{ class, style }: classes put on the element, and CSS
properties to values, where{name}in a value is a token and{colors.accent},{space.lg}and
the like are the theme's own. Every primitive takesrecipe, and a recipe it names replaces its
own inline look on its outer element, keeping only what the block needs to work. Properties are
held to a list (box, spacing, typography, colour, border, radius, shadow, grid, flex and the
engine's own custom properties), and a value to letters, digits, a few symbols, quoted family
names and a short list of functions, sourl(),expression(),;, braces and!importantare
refused. A bad property or value is dropped and the rest kept.texttakesformat: "inline",
which reads code, emphasis, strong, links and==an accent==through the safe renderer
(renderInlineMarkdown), up to 2000 characters, past which the value is plain text. A block that
names no recipe, and a text block leftplain, render as they did. -
A markdown link or a
urlfield starting//or/\is refused. A browser reads both as another
site, and they were let through as paths. -
The tenant-aware
/api/blockshandler takes its request as required, not optional. Next's route
type check refuses a handler whose request may be absent, so a consumer built with webpack failed
its type check onapp/api/blocks/route.ts. -
Counts, sums and groups over a source (#128).
{{count.<collection>}}is how many published
entries a collection has, read as the delivery API'stotalItemsfor a ...
0.7.0
-
Four things the block library v4 look check said it could not draw yet, measured against the
baryo.dev fixture (#83, #91). AcardGridcard can carry a colour of its own:{{item.Color}},
the same toneOptionStyle(#52) already resolves for an option, now reaches a badge behind the
card's icon instead of only a word in the theme's accent, andcardGridcan also drawcards
typed in place, with nocollection, for a page author's own package list or feed.sectionand
every block that takes atonegainedwash, a radial gradient from the theme's own page and
accent tint colours, declared rather than hand-written.codeTabsdraws a real strip of tabs
now,tabGroupandtabPanel, a radio and its label standing in for the tab and a:checked
selector standing in for the script, since a first version built on<details>broke past two
tabs (see the pull request for what and why) and this one is measured at four.disclosurestays
whattabsandfaquse. Measured against the fixture: 48.863% different at 1280px and 59.434%
at 390px before, 30.595% and 35.741% after. The mascot, the bio panel, the sticky translucent
header and the richer "Four products" cards are not blocks yet; the gap that is left is written
down in the pull request rather than closed by guessing at a shape nobody asked for. -
A page over its block budget says so, once, instead of rendering short with nothing said (#90).
Raising the limit from a hundred to four hundred (#83) bought room; it did not make going over it
visible, which is how baryo.dev's eighth band went missing with a 200 and no error.resolveBlocks
still renders everything that fits, so a static export still builds, and warns once through the
samesayOnceshape the sitemap and the font allow list already use. -
The blocks barakocms.com adds.
comparisonTabledraws a real table from rows typed as lines with
|between the cells, headings on both the columns and the rows, so a cell is announced with the
option it belongs to.progressBarisrole="progressbar"with the three values that role needs,
which is what a roadmap needs to be read and not only seen, and it reads its figure through a new
progressfield role so the tenant says which of its own fields holds the number.stickyBaris
the announcement band, andannouncementis the preset over it.codeTabsdraws the snippet a
quickstart is for and puts the other ways of running it incodeTabs sharing a group (a tab strip
as of #91; disclosures until then).progressListandchangelogListread a collection, and a
changelog is grouped by kind withfilterFieldandfilterValue, one band per kind, because a
block that grouped by itself would
have to know which field holds the kind and that is the tenant's field name.faqandfaqItem
are questions that all open at once, which is the whole difference fromtabs. A card grid's new
optionprop marks each card with the glyph and the word the site declared for that entry's
option, which with a category filter is the module grid; it is off unless a grid asks for it, so
every existing card grid draws what it drew. (#24) -
A site's approved design is a fixture now, and the look check compares against that rather than
against whatever the live site deployed this morning.npm run look:capturereads a page with the
same determinism the check uses and writes one self-contained file: stylesheets inlined, fonts and
images as data URIs, every script and fetch hint taken out.npm run look:siteserves the same
page assembled from blocks, through the real renderer against a stand-in CMS holding the site's
settings, and runs the pair list over the two. baryo.dev is the first site through it. Its capture
matches the live site at 0.000% at both widths, and running the rebuild against it found a four
column band that scrolled a phone sideways and a page that lost its last band without a word. Both
are fixed below. What is left is a rebuild that differs by 48.9% at 1280px and 59.4% at 390px,
which is the real state of it: the block library has no answer yet for the mascot, the package
family grid, the feed or the terminal's tab strip. (#83) -
A flow wraps on a phone instead of scrolling it sideways. Its track list was fixed at the column
count with the column floor under each track, and four floors and three gaps do not fit in 390px,
so a four column stat band made a page 1144px wide. The tracks are a share of the row now, with
the floor still under them, and as many fit as the row can hold: the column count on a desktop,
fewer on a phone. (#83) -
A sticky bar sticks.
BlockListwraps every block in a div that is exactly as tall as what it
holds, and aposition: stickyelement can only move inside its own containing block, so the band
scrolled away with the page: its top went from 0 to -400 after a 400px scroll, measured. A block
definition may now ask for that wrapper to bedisplay: contents, andstickyBaris the one that
does. Nothing else in the library asks, so nothing else moves. (#24) -
A flow asked for no gap keeps its columns.
space.noneis "0", which is a number and not a
length, so100% - 2 * 0was a type error that took the whole track list with it and left every
cell in one column with nothing said. (#83) -
A page may hold four hundred blocks rather than a hundred. A band from the library is a preset
that expands into eight or ten, and the binder spends the budget on everything it walks through as
well as everything that comes out, so a page of eight bands ran out on the seventh and rendered as
though that were the page. The reads a page may make and the length of a binding are bounded
separately, so this buys more substitution over short strings and nothing else. Going over is still
silent. (#83) -
Docs are a configured collection with a tree, not a Next app of their own. A collection's
tree
names four fields on the tenant's own type: the section a page sits under, where it comes in the
order, the page it hangs under, and the product it documents. The section order and the products
the switcher offers are settings beside them, so what used to be a hand-kept manifest in a site
repository is content an editor changes in barakoBrew. An item page in such a collection draws the
sidebar with the page being read marked, the product switcher, a search box, previous and next from
the tree's reading order, and a link to wherever the page is written, built fromtree.editBaseand
the item's own source path. The same three are blocks as well,docsSidebar,docsSwitcherand
search, over one implementation, because a landing page needs them with no item and an item page
needs them with no blocks. Search goes through barakoCMS's own/api/public/{type}/search, which
matches only over the fields a type publishes, so a draft or a field held back from public delivery
can never surface as a hit;createCollectionIndex(config, key, { search: true })answers?q=the
same way. The box is drawn wheretree.searchPathnames a route that reads the query and nowhere
else, since only the site knows which of its routes does and one pointing elsewhere is a control
that looks like it works. The box is a form and the results are links, so a reader with no script
gets a working search, and the keyboard handling on top of it is the package's first client component. Old paths
move through the redirects feature the catch-all already asks. (#23) -
The generic item view renders an article, and the blog's post exports are wrappers over it. A
reading column, a byline, a read time and a band of neighbours are what long-form content wants
rather than what a blog wants, so a collection asks for them withlayout: "article"and a law
firm's briefings get the page the blog has always had.PostViewproduces every byte of that markup
through the shared layout, andtest/blog-wrappers.golden.jsonstill passes byte for byte, which is
the whole claim.Post,PostView,createArchive,listPostsByandlistRelatedare
@deprecatednaming what to use instead and are removed no earlier than 1.0.0, since they shipped
one release ago. What changed underneath is thattoPost,listPosts,getPost,getPostPreview,
listPostsBy,getTermandlistTermsread the post collection's own field map, so a tenant that
replacedpostin its settings gets its own names where it used to get the image's. What stays
blog-shaped on purpose is the three composition slots onPostView, which exist for a consumer
assembling its own post page. (#75) -
A block that reads the site is rebound to the config a request resolved as itself. Every one of them
used to be replaced with a freshly builtcollectionblock, whatever it was, so a page holding a
docs sidebar would have rendered a list of documents where the sidebar belonged. (#23) -
A collection with more entries than the API hands back in one page is in the sitemap whole. The
route asked forpageSizes.sitemapentries, a thousand by default, and barakoCMS answers a public
list with at most a hundred and no error, so everything past the hundredth was missing from a file
that was valid XML served with a 200 and nothing anywhere said so. The collection is read a page at
a time now, at the size the API answered with rather than one this side guessed, and
pageSizes.sitemapreads as what it says: the most entries one collection puts in the file. The
file itself stops at 50,000 URLs, which is the sitemap standard's limit for one file, because past
that the standard's own answer is several files behind an index and building those belongs to the
consumer's route file with Next'sgenerateSitemaps. Both the clamp and the stop are said once in
the log rather than absorbed, since a count a tenant set and a count the API permits disagreei...
0.4.0
- Two build-time keys are a tenant's to set.
PageSizesgives a site its own index, feed, sitemap
and archive counts, so a bakery listing 50 products and an agency listing 9 case studies run the
same image instead of one of them needing a build; a key the entry leaves out keeps the configured
count, and a collection's ownpageSizestill wins.ReservedSlugsadds to the reserved first
segments, which is how a tenant keeps a path its proxy answers out of the menu and the sitemap. It
only ever adds: the configured list is the app's own routes, and a page freed onto one of those
would sit behind a route file and render nowhere. The pages mount stays operator-only, because
Next resolves the catch-all by where its file sits and no setting can move a file. A tenant that
sets neither renders exactly as it did. (#53) - A request-time site no longer renders every page for every visitor. Resolving the tenant meant
reading the request host, reading a header makes a route dynamic, and Next never keeps a dynamic
route, so one container serving many domains re-rendered every page on every view with every read
under it already cached. Aproxy.tsresolves the tenant and the share session once and rewrites
to/_press/<tenant>~<gate>~<host>/<path>; the pages read all three out of that segment and read
nothing from the request. The tenant is in the path and the path is the whole of Next's key, so two
tenants cannot share an entry and a request carrying a share session cannot be answered from one
made without it. A path that arrives under/_pressfrom outside is a 404.revalidateTagdrops a
tenant's renders with its reads, so the webhook that purged one now purges both. Measured on the
reference app against the stand-in CMS: 19ms a view before, 9ms after, with no CMS read either way.
A request-time site moves its page routes underapp/%5Fpress/[site]/and addsproxy.ts; see
the README. A build-time site changes nothing. Routes that read?preview=or bind{{query.X}}
stay dynamic on purpose, and which routes are kept is the consumer's call in the consumer's route
file, the same asrevalidate. (#55) createPagetakes{ query: true }for a page that binds{{query.X}}. On a route Next keeps,
the query is not handed to the blocks at all, so the binding is reported as an unbound scope and
renders as nothing rather than failing the route. (#55)registryForbinds the blocks that read the site to the config the request resolved, and takes
{ holding: true }for a holding page. Thecollectionblock used to resolve the request itself,
once per block per view, which kept every page holding one out of the render cache. A site that
registered its owncollectionkeeps its own. A consumer renderingPageViewwith a registry that
never went throughregistryFornow gets a loud error on a request-time site instead of a read
that resolved itself. (#55)- Fonts from somewhere other than Google Fonts. A
Fontsentry may name the stylesheet that loads a
face,{ "family": "Zilla Slab", "url": "https://type.school.example/zilla.css" }, which is how a
school with a licensed face on its own host or a tenant that must not send visitor addresses to a
third party gets its type. The URL is a tenant's setting on its way into a<link>in every
visitor's page, so the deployment decides which origins may appear there:PRESS_FONT_ORIGINS,
read per request,https://fonts.googleapis.comalone when unset. Anything else is refused, the
role falls back to its family name, and the log says so once. An operator who leaves Google Fonts
out of the list stops every link to it, the built-in one and its preconnects included, and no
setting can put one back. A family name on its own renders the Google Fonts link it always did.
(#54) - An asset can be used exactly as supplied.
AssetsAsSuppliedlists the marks a site must not
restyle,LogoAsSuppliedsays it of the logo and the footer logo, and each carries a minimum clear
space from the spacing scale. A marked asset is drawn from the file with that space held around it,
and no tint, border, corner, shadow, filter or crop reaches it: a block that asked for a frame draws
the mark alone. It holds in the header, the footer, the holding page, a region, a page block, a
preset, a post cover, a collection item and an image inside markdown, because every image the engine
draws now goes through one component and a test walks all of them. Animageblock can mark its own
file withasSuppliedandclearSpace. A site that marks nothing renders exactly as it did. (#29) - One reader for every environment value,
readEnvinsrc/env.ts, called where the value is used.
CMS_URLandCMS_TENANTwere read insidedefineConfig, which runs when a site's
press.config.tsis first imported, whileCMS_DEFAULT_TENANT,CMS_RENDERER_KEY,
PRESS_CONSOLE_ORIGINSand the secrets were read per request. When a variable was read depended on
which variable it was. Nothing outsidesrc/env.tsnamesprocess.envnow, and a test fails if
that changes. Variable names, defaults and precedence are unchanged. (#51) - Redeeming a share link is bounded by
cmsTimeoutMs, like every other call to the CMS, instead of a
fixed five seconds. An operator who lowers the timeout for a site on a slow network meant that call
too. The default is still 5000ms, so a site that leavescmsTimeoutMsalone behaves as it did.
(#51) - A block library every site gets:
hero,band,statBand,cardGrid,peopleGrid,timeline,
steps,tiers,keyValueTable,tabs,map, and the parts that go in their slots (stat,
timelineEntry,step,tier,person,keyValueRow). Every one is a preset compiled from the
primitives rather than code, so a tenant that wants one to look different saves its own under that
name and that one wins. Three new primitives carry them:flowlays a single list out as cells
instead of stacking it, which is what arepeatand a preset's slot produce,panelis a card
with a tone and a frame, anddisclosureis a labelled section that opens. (#21) - A band's tone reaches the blocks inside it. A
sectionor apanelpublishes its ink, accent and
hairline, andtext,icon,list,buttonandlinkread those instead of the page's, so a
heading on an inverse band is no longer dark ink on a dark panel. (#21, toward #49) - A page may nest blocks eight lists deep instead of four. The hundred-block budget is what bounds
the work, and four was not enough for a card grid: a band, a source, a flow, a repeat, a card and
the stack inside it is six before any content. (#21) - The header and the footer are block regions.
HeaderPathandFooterPathname a page whose blocks
are drawn there,HeaderToneandFooterTonethe tone behind it, so a clinic gets a light footer
with opening hours and a map and a school an enrolment banner with a button, with no barakoPress
release. A site that sets neither renders the built-in header, top bar and footer fromTopBar,
HeaderLinks,FooterColumnsandSocialLinks, with the markup it always had, and so does one
whose region path has nothing served at it. A region page is chrome, so it is left out of the menu
and the sitemap. (#48) - Blocks in four layers: layout and content primitives that take theme tokens only, presets a tenant
saves as data, and bindings. Any string prop can hold{{site.Name}},{{item.Fee | money}}or
{{query.class ?? all}}, resolved on the server as the request's tenant;source,repeat,
pagerandshowIfload, list, page and choose. The five block types stored pages already hold
render unchanged. The block schemaapp/api/blockspublishes is now version 2: it adds each
block's layer, each field'sbindable, and the scopes and formats a binding may name. (#33) - The theme carries a spacing scale (
space) and a type scale (text), settable per tenant through
theSpaceandTextsite settings, andlayout.columnMinis where a row or a grid wraps. Block
primitives read these instead of pixel values. (#33, toward #49) - New site settings:
Presets(a tenant's named blocks),Currency(what themoneyformat uses)
andEmbedHosts(which hosts anembedblock may frame). A preset setting that is not read says
why once in the server log, one line per reason: an entry that is not a preset, a type that is not
a name or that an earlier preset already uses, fields that are not a list or are not fields, a name
a registered block already has, and the ones past the block budget. (#33) - A look check: a reusable Playwright job that screenshots a reference and the rebuilt page at 390px and 1280px, compares them against a per page threshold, and uploads the reference, the rebuilt screenshot and the diff on failure. The page list is data the site owns, not code here.
npm run look:selftestruns it green and red, and CI runs that. (#27) - One
PRESS_SECRETkeys every tenant's webhook key and every share session, with one rule of at least 32 characters.REVALIDATE_SECRETandPRESS_PREVIEW_SECRETare read only while it is unset, and every key derives byte for byte as before, so keys already in tenants' workflows keep verifying. AREVALIDATE_SECRETshorter than 32 characters logs a warning rather than refusing, until 1.0.0. (#50) - A
HoldingMessagesite setting is the line the default holding page shows under the name and tagline, in place of the fixed "Coming soon.". Unset shows no line. (#46)