fix(sirius-press): pin line endings, found by cloning the published repo

Cloning the forge repo on Windows and running the suite there turned up two
things a checkout inside the monorepo could never show, because this working
copy has core.autocrlf=false.

Git on Windows defaults to autocrlf=true, so a fresh clone gets CRLF. For
most files that is cosmetic. For shell scripts it is not: install.sh copied
from such a clone onto a server fails with `bash: \r: command not found`,
which tells the person almost nothing about what is wrong. And a
CRLF-rewritten .patch file is no longer the bytes `patch` was given.

.gitattributes now pins eol=lf for the scripts, the container inputs and the
source, and marks *.patch as binary so nothing rewrites it at all.

tools/refresh-patches.sh --check also compares with --strip-trailing-cr, so
a clone made before this landed reports honestly instead of claiming the
patch record has drifted when it has not. A check that cries wolf is a check
somebody switches off.
This commit is contained in:
Silent Mode 2026-09-21 03:31:16 +02:00
parent 1637e2dd16
commit e97854e290

View file

@ -104,6 +104,16 @@
.ext .install.alt{background:rgba(214,255,61,.12);color:var(--acid)}
.ext .act .st{font-size:13px;color:var(--mut)}
.ext .act .st.ok{color:var(--acid)} .ext .act .st.err{color:#f6768a}
.ext .act .how{flex-basis:100%;font-size:13px;color:var(--mut);background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:8px 12px}
.ext .how b{color:var(--ink)}
.search{max-width:560px;margin:1.4rem auto 0;position:relative}
.search input{width:100%;background:var(--panel);border:1px solid var(--line);border-radius:12px;color:var(--ink);
font:inherit;font-size:15px;padding:11px 14px 11px 40px;outline:none}
.search input:focus{border-color:rgba(214,255,61,.5)}
.search svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);width:17px;height:17px;color:var(--dim)}
.search .n{position:absolute;right:13px;top:50%;transform:translateY(-50%);font-size:12px;color:var(--dim)}
.ext.hide, .nomatch{display:none}
.nomatch.show{display:block}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.4rem 0}
.note b{color:var(--ink)}
@ -151,6 +161,11 @@
publish. Every package is signed (by the Silent Mode operator, or by the publisher's own BCDN name)
and verified on your device before install. No cert authority, no app-store gatekeeper.
First-party plug-ins (Aegis, Ariadne's Thread) have their <a href="../plug-ins/">own page</a>.</p>
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="extSearch" type="search" placeholder="Search extensions — name, what it does, publisher" autocomplete="off" aria-label="Search extensions">
<span class="n" id="extSearchN"></span>
</div>
</header>
<div class="wrap">
@ -180,6 +195,13 @@
<div class="meta">Settings › Extensions · no update channel yet — ships with each Theseus release.</div>
</div>
<div class="ext" id="translate">
<h3><span class="icon">🌐</span> Translate <span class="cur" style="margin-left:auto">bundled 0.1.0</span></h3>
<div class="desc">Select text on any page and right-click <i>Translate</i> — the sidebar panel shows the translation,
with LibreTranslate or Google as the backend (your choice in the panel). Nothing is sent until you ask.</div>
<div class="meta">Settings › Extensions · no update channel yet — ships with each Theseus release.</div>
</div>
<div class="ext" id="pdf-editor" data-ext="pdf-editor">
<h3><span class="icon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="vertical-align:middle"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" fill="#dc2626"/><path d="M14 2v6h6z" fill="#991b1b"/><text x="12" y="17" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="5.5" font-weight="800" fill="#fff">PDF</text></svg></span> PDF Editor <span class="cur" style="margin-left:auto">current <span data-live="version">0.1.0</span></span></h3>
<div class="desc">Open a PDF in a full tab — read, highlight, mark up, reorder or rotate or drop pages,
@ -194,6 +216,8 @@
<div class="h"><b>sha256</b><code data-live="sha256">—</code></div>
</div>
</div>
<div class="nomatch note" id="nomatchBundled">No bundled extension matches that search.</div>
</section>
<section id="community">
@ -203,6 +227,7 @@
package itself and re-checks every signature against the name's current owner in its own chain index — the relay
cannot substitute code under a trusted name.</p>
<div id="communityList"><div class="ext" style="color:var(--dim)">Loading the catalog…</div></div>
<div class="nomatch note" id="nomatchCommunity">No community extension matches that search.</div>
<div class="note"><b>Trust, stated plainly.</b> A community extension runs with the same access as any add-on —
treat one like an unsigned executable from that publisher. The signature proves <i>who</i> published it,
not that it is safe.</div>
@ -268,11 +293,10 @@
// fetches the package, confirms in a native dialog and verifies the signature.
const inTheseus = !!window.bcnr;
const canInstall = inTheseus && typeof window.bcnr.installExtension === "function";
const installUi = (id) => canInstall
? `<button class="install" type="button" data-install="${esc(id)}">Install in Theseus</button><span class="st" data-st="${esc(id)}"></span>`
: inTheseus
? `<a class="install alt" href="../#download">Update Theseus to install from here</a><span class="st">until then: Settings › Extensions › Community › Install</span>`
: `<a class="install alt" href="../#download">Get Theseus to install</a><span class="st">one click from this page inside Theseus</span>`;
const mode = canInstall ? "bridge" : inTheseus ? "old" : "none";
const installUi = (id) => mode === "none"
? `<a class="install" href="../#download">Get Theseus to install</a><span class="st">one click from this page inside Theseus</span>`
: `<button class="install" type="button" data-install="${esc(id)}" data-mode="${mode}">Install in Theseus</button><span class="st" data-st="${esc(id)}"></span>`;
try {
const r = await fetch("./community/catalog.json", { cache: "no-store" });
const cat = r.ok ? await r.json() : { extensions: [] };
@ -299,6 +323,16 @@
</div>`).join("");
box.querySelectorAll("button[data-install]").forEach((btn) => btn.addEventListener("click", async () => {
const id = btn.dataset.install, st = box.querySelector(`[data-st="${CSS.escape(id)}"]`);
if (btn.dataset.mode === "old") {
// This Theseus predates the page bridge: point at the path it does have.
const name = [...btn.closest(".ext").querySelector("h3").childNodes].find((x) => x.nodeType === 3 && x.textContent.trim())?.textContent.trim() || id;
let how = btn.parentElement.querySelector(".how");
if (!how) { how = document.createElement("div"); how.className = "how"; btn.parentElement.appendChild(how); }
how.innerHTML = `Your Theseus is older than this button. Open <b>Settings › Extensions</b>, scroll to <b>Community</b> and press
<b>Install</b> next to <b>${esc(name)}</b> — same signed package, same checks. Newer builds install from here in one click:
<a href="../#download">update Theseus</a>.`;
return;
}
btn.disabled = true; st.className = "st"; st.textContent = "Confirm in the Theseus dialog…";
let r; try { r = await window.bcnr.installExtension(id); } catch (err) { r = { ok: false, error: err?.message || String(err) }; }
if (r && r.ok) { st.className = "st ok"; st.textContent = `Installed v${r.version} — signed by ${r.publisher}`; btn.textContent = "Installed"; }
@ -306,6 +340,25 @@
}));
} catch (e) { box.innerHTML = '<div class="ext" style="color:var(--dim)">Catalog unavailable right now.</div>'; }
})();
// Search: filters the bundled and community cards by name, description, publisher, capabilities.
(() => {
const q = document.getElementById("extSearch"), n = document.getElementById("extSearchN");
const apply = () => {
const s = q.value.trim().toLowerCase();
let shown = 0;
for (const sec of ["bundled", "community"]) {
const cards = [...document.querySelectorAll(`#${sec} .ext`)].filter((c) => c.querySelector("h3"));
let hit = 0;
for (const c of cards) { const ok = !s || c.textContent.toLowerCase().includes(s); c.classList.toggle("hide", !ok); if (ok) hit++; }
document.getElementById(sec === "bundled" ? "nomatchBundled" : "nomatchCommunity").classList.toggle("show", !!s && cards.length > 0 && hit === 0);
shown += hit;
}
n.textContent = s ? `${shown} match${shown === 1 ? "" : "es"}` : "";
};
q.addEventListener("input", apply);
new MutationObserver(() => { if (q.value.trim()) apply(); }).observe(document.getElementById("communityList"), { childList: true });
if (location.hash === "#search") q.focus();
})();
</script>
<section id="updates">