First real commit of site/. Until now outreach/.git tracked exactly one file
(README.md) and every content pack stamped git_sha=5619a40 regardless of what it
shipped — a signature with no provenance behind it. The site had no witness, which
is why a deploy could erase two sessions of work with nothing to diff against and
nothing to restore from.
Tracked here (authored): site/content, site/config, site/i18n, site/public/images,
justfile, scripts/, .just/, run*.sh, rustelo.manifest.toml.
Ignored (reproducible, or secret):
.k, .env real keys — outreach mirrors to a PUBLIC remote
site/r, site/public/r content indexes, rebuilt by `just content`
provisioning/.content-packs 228 MB of deploy tarballs
cache, data, logs_*.out runtime droppings
Three trees are deliberately NOT ignored despite looking generated, because their
declared source cannot currently reproduce them — ignoring them would delete the
only copy:
site/public/images 196 of 209 images exist nowhere else (assets/site is empty)
site/content/{adr,catalog} projections whose generators are unwired (gen-adr-pages)
site/content/domains or failing (`just graph`)
They become ignorable when an audit proves regeneration is a no-op, not before.
Fixes carried in this import
---------------------------
content: the mirror ran one way and reverted its own work. content_processor writes
site/r, but the recipe ended with `rsync -a site/public/r/ -> site/r/`, so each fresh
index was clobbered by the stale deploy-tree copy: the tool reported "index.json with
69 posts" while the disk kept 58. Meanwhile public/r is the tree the deploy pack ships,
so no new content could ever reach production. The two trees own different files, so
the mirror now runs both ways: content indexes r -> public/r (with --delete, or a
renamed slug survives as a live URL), and the four nu artifacts (about, adr-map,
taglines, content_graph) back public/r -> r, excluded from the outbound leg so stale
copies cannot overwrite the fresh originals.
templates: the assembler has two layers (framework defaults, source-project templates)
but the level chain has three (rustelo -> website-htmx-rustelo -> outreach/site). The
site level had no overlay slot, so its template work had nowhere legitimate to live and
ended up in htmx-templates/ — the one tree `just templates` rm -rf's. That is how the
nav submenus were lost. site/templates-overlay/ is the missing third slot; 214 lines
(nav submenus, subscribe CTA, post engagement block) now live in a declared source and
survive regeneration byte-identical.
adr: projected ADR-059..069 into the content tree. They were written months ago and
never reached the site because gen-adr-pages.nu is wired into no recipe — a generator
outside the dependency chain is a generator that does not run.
Gates added
-----------
templates-check reassembles into a temp tree and asserts an empty diff against the
live one: any drift means htmx-templates/ holds work that exists
live one: any drift means htmx-templates/ holds work that exists
nowhere else and the next run deletes it.
Both gates run before the destructive operation, not after. A check you can only run
afterwards is not a gate, it is an autopsy.
Refs: ADR-062 (projection-not-own-repo: site is a Projection of outreach, not its own
repo — different deploy target is not a different visibility boundary), ADR-048
(materialization), ADR-066 (the check decides, never the reporter — which the content
pipeline, the surface that publishes ontoref, was the last place not to honour).
129 lines
3.6 KiB
JavaScript
129 lines
3.6 KiB
JavaScript
(function() {
|
|
/** @type {import("../htmx").HtmxInternalApi} */
|
|
var api
|
|
|
|
var attrPrefix = 'hx-target-'
|
|
|
|
// IE11 doesn't support string.startsWith
|
|
function startsWith(str, prefix) {
|
|
return str.substring(0, prefix.length) === prefix
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elt
|
|
* @param {number} respCode
|
|
* @returns {HTMLElement | null}
|
|
*/
|
|
function getRespCodeTarget(elt, respCodeNumber) {
|
|
if (!elt || !respCodeNumber) return null
|
|
|
|
var respCode = respCodeNumber.toString()
|
|
|
|
// '*' is the original syntax, as the obvious character for a wildcard.
|
|
// The 'x' alternative was added for maximum compatibility with HTML
|
|
// templating engines, due to ambiguity around which characters are
|
|
// supported in HTML attributes.
|
|
//
|
|
// Start with the most specific possible attribute and generalize from
|
|
// there.
|
|
var attrPossibilities = [
|
|
respCode,
|
|
|
|
respCode.substr(0, 2) + '*',
|
|
respCode.substr(0, 2) + 'x',
|
|
|
|
respCode.substr(0, 1) + '*',
|
|
respCode.substr(0, 1) + 'x',
|
|
respCode.substr(0, 1) + '**',
|
|
respCode.substr(0, 1) + 'xx',
|
|
|
|
'*',
|
|
'x',
|
|
'***',
|
|
'xxx'
|
|
]
|
|
if (startsWith(respCode, '4') || startsWith(respCode, '5')) {
|
|
attrPossibilities.push('error')
|
|
}
|
|
|
|
for (var i = 0; i < attrPossibilities.length; i++) {
|
|
var attr = attrPrefix + attrPossibilities[i]
|
|
var attrValue = api.getClosestAttributeValue(elt, attr)
|
|
if (attrValue) {
|
|
if (attrValue === 'this') {
|
|
return api.findThisElement(elt, attr)
|
|
} else {
|
|
return api.querySelectorExt(elt, attrValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
/** @param {Event} evt */
|
|
function handleErrorFlag(evt) {
|
|
if (evt.detail.isError) {
|
|
if (htmx.config.responseTargetUnsetsError) {
|
|
evt.detail.isError = false
|
|
}
|
|
} else if (htmx.config.responseTargetSetsError) {
|
|
evt.detail.isError = true
|
|
}
|
|
}
|
|
|
|
htmx.defineExtension('response-targets', {
|
|
|
|
/** @param {import("../htmx").HtmxInternalApi} apiRef */
|
|
init: function(apiRef) {
|
|
api = apiRef
|
|
|
|
if (htmx.config.responseTargetUnsetsError === undefined) {
|
|
htmx.config.responseTargetUnsetsError = true
|
|
}
|
|
if (htmx.config.responseTargetSetsError === undefined) {
|
|
htmx.config.responseTargetSetsError = false
|
|
}
|
|
if (htmx.config.responseTargetPrefersExisting === undefined) {
|
|
htmx.config.responseTargetPrefersExisting = false
|
|
}
|
|
if (htmx.config.responseTargetPrefersRetargetHeader === undefined) {
|
|
htmx.config.responseTargetPrefersRetargetHeader = true
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {Event} evt
|
|
*/
|
|
onEvent: function(name, evt) {
|
|
if (name === 'htmx:beforeSwap' &&
|
|
evt.detail.xhr &&
|
|
evt.detail.xhr.status !== 200) {
|
|
if (evt.detail.target) {
|
|
if (htmx.config.responseTargetPrefersExisting) {
|
|
evt.detail.shouldSwap = true
|
|
handleErrorFlag(evt)
|
|
return true
|
|
}
|
|
if (htmx.config.responseTargetPrefersRetargetHeader &&
|
|
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)) {
|
|
evt.detail.shouldSwap = true
|
|
handleErrorFlag(evt)
|
|
return true
|
|
}
|
|
}
|
|
if (!evt.detail.requestConfig) {
|
|
return true
|
|
}
|
|
var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status)
|
|
if (target) {
|
|
handleErrorFlag(evt)
|
|
evt.detail.shouldSwap = true
|
|
evt.detail.target = target
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
})
|
|
})()
|