chore: update site content

This commit is contained in:
Jesús Pérez 2026-07-17 00:52:21 +01:00
parent cc664d6dd4
commit 1faea7491c
180 changed files with 15773 additions and 359 deletions

View file

@ -19,6 +19,7 @@ tags: ["rust", "infrastructure", "automation", "provisioning", "rustikon", "deve
# Card thumbnail + social/feature image
thumbnail: "/images/rustikon-2026-poster.webp"
image_url: "/images/rustikon-2026-poster.webp"
og_image: "/images/og/rustikon-2026-poster.png"
# Display
sort_order: 1

View file

@ -19,6 +19,7 @@ tags: ["rust", "infraestructura", "automatizacion", "provisioning", "rustikon",
# Card thumbnail + social/feature image
thumbnail: "/images/rustikon-2026-poster.webp"
image_url: "/images/rustikon-2026-poster.webp"
og_image: "/images/og/rustikon-2026-poster.png"
# Display
sort_order: 1

View file

@ -0,0 +1,182 @@
---
id: "adr-020"
title: "Three-Layer Model for Project Ontoref Instances — the Export Axis Declared on manifest.layers"
slug: "020"
subtitle: "Accepted"
excerpt: "Multiple projects now adopt the ontoref protocol (lian-build is the explicit"
author: "ontoref"
date: "2026-07-13"
published: true
featured: false
category: "accepted"
tags: ["adr-020", "adr", "accepted"]
css_class: "category-adr"
---
<h2>Context</h2>
<p>Multiple projects now adopt the ontoref protocol (lian-build is the explicit
external case under bl-002 / bl-008). Field experience across ontoref +
lian-build + provisioning shows that an ontoref-onboarded project's
repository carries TWO distinct ontoref-shaped layers, while a third layer
exists outside the project (in caller repositories). Without codification,
adopters re-derive the model per project, mix layers accidentally, and lose
boundaries silently:</p>
<p>- Layer 3 content (caller-side cabling) drifts into a project's qa.ncl,
making the FAQ a how-to-deploy guide for one specific caller.
- Layer 2 schemas live under reflection/ as 'project notes', drifting
from the binary because they're not on the contract path.
- Layer 1 architectural rationale ends up in catalog/domains/&lt;id&gt;/
contract.ncl, where consumers expecting a typed shape get prose.</p>
<p>The model has been observed (lian-build/reflection/qa.ncl::lian-build-what-
and-why), described (ontoref/reflection/qa.ncl::ontoref-three-layer-model),
and queued for codification (bl-009). This ADR commits the model with
machine-checkable constraints. Acceptance is gated on the constraints
running clean across the existing ontoref-onboarded projects (ontoref
itself, lian-build, provisioning).</p>
<p>WHY IT SAT IN 'Proposed FOR TWO MONTHS, AND WHAT UNSTUCK IT (2026-07-13).</p>
<p>The gate was never a judgement call. The constraints could not run clean because
the executor that runs them was broken (ADR-070 / migration 0044: check paths
resolved against the caller's cwd, and `.ontoref/`-prefixed paths came out
double-prefixed and missed). With the executor anchored, the four Layer 1 checks
run clean on ontoref, lian-build and provisioning as written. The Layer 1 half of
this ADR was correct and unenforced, which is the worst state a constraint can
be in — it looks satisfied because nothing evaluates it.</p>
<p>The Layer 2 half was wrong, and the question that surfaced it was a user's:
&quot;ontoref carries ADRs and modes about its own site, doors, campaigns and CV —
what use are those to a project that merely adopts ontoref?&quot; The empirical
answer is: none, and they do not travel — provisioning's spine carries 44 ADRs
of its own and not one of ours. But the question exposed that the protocol had
no TYPED answer to &quot;what of me is yours&quot;. It had a paragraph in CLAUDE.md (the
migration-trigger table) doing the job of a `pub` keyword.</p>
<p>Two defects followed from that:</p>
<p>1. This ADR located Layer 2 by SNIFFING A PATH (`catalog/domains/`), which is
provisioning's spelling generalised too early. ontoref publishes its domain
artifacts from `code/domains/` (ADR-012 domain extensions, and `code/` only
because ADR-052 split the constellation). lian-build spells it differently
again. A protocol check that hard-codes one member's layout is the very
confusion this ADR exists to end. Worse, patching the check to also look in
`code/domains/` would have BAKED ONTOREF'S CONSTELLATION SHAPE INTO A
PROTOCOL CONSTRAINT.</p>
<p>2. The mechanism already existed and had rotted unseen. `manifest.ncl::layers`
has always declared &quot;a named region of the repo with visibility rules per
mode&quot;, and ontoref's own manifest already named a `protocol` layer (&quot;the
contract layer that projects implement&quot;) beside a `self-description` layer
(&quot;ontoref consuming ontoref&quot;). That IS Layer 2 beside Layer 1. Nobody
noticed, because nothing checked it — its paths still pointed at the
pre-ADR-032 layout (`ontology/`, `.ontology/`), naming directories that had
not existed for months. A third meaning of the word &quot;layer&quot; sat in the
schema while this ADR worried only about colliding with &quot;level&quot; (ADR-018).</p>
<p>So Layer 2 is not discovered, it is DECLARED — on the mechanism that was already
there. The layer taxonomy stops being prose about where files sit and becomes the
export axis of an ontoref instance: the `pub` the user asked for, checkable.</p>
<h2>Decision</h2>
<p>A project's ontoref instance has THREE distinct layers — but only the first
two live in the project's own repository:</p>
<p>LAYER 1 — Self-management ontoref (about the project itself)
paths .ontology/ reflection/ adrs/
audience this project's developers and maintainers
purpose describe the project to itself — axioms, FSM dimensions,
binding decisions, open questions, accepted knowledge
presence MANDATORY on every ontoref-onboarded project</p>
<p>LAYER 2 — Specialized domain/mode ontoref (the integration surface)
paths DECLARED, never sniffed — the paths of any manifest layer whose
kind = 'IntegrationSurface, plus manifest.ncl::registry_provides
audience OTHER projects that want to integrate this project
purpose the contract surface other projects bind to — typed domain
artifacts, orchestration mode artifacts, registry-namespace
claim
presence OPTIONAL but BICONDITIONAL — a project declares an
'IntegrationSurface layer if and only if it declares
registry_provides. Half-Layer-2 is a contract violation.</p>
<p>LAYER 3 — Caller-side implementations (NOT in this project)
paths &lt;caller&gt;/extensions/&lt;this-project&gt;/
&lt;caller&gt;/catalog/components/&lt;...&gt;/ (when consuming)
&lt;workspace&gt;/infra/&lt;ws&gt;/integrations/
audience operators and CI of caller projects
presence PER CALLER, NEVER in this project's repo. Cross-references
from this project to Layer 3 are explicit pointers, never
copy-paste.</p>
<p>The three-layer axis is ORTHOGONAL to ADR-018's level hierarchy
(Base/Domain/Instance). A project at any level may have any combination of
Layer 1 (always) and Layer 2 (sometimes). Layer 3 is the boundary outward,
not a property of the project. The 3-layer × 3-level matrix is navigable in
both axes: 'where in the protocol hierarchy' (level) is independent of
'where in the project's repo' (layer).</p>
<p>Cross-layer references inside a project carry an explicit layer-N tag on
qa entries. A qa entry whose primary topic is a Layer N concern wears
the layer-N tag; satellite entries (operational how-to, troubleshooting)
inherit the layer of their anchor without re-tagging.</p>
<p>THE MECHANISM — manifest.ncl::layers CARRIES THE AXIS</p>
<p>`layer_type` gains one field, `kind`:</p>
<p>'SelfManagement Layer 1. Never travels.
'IntegrationSurface Layer 2. What a consumer binds to.
'Internal Neither — reached through a released binary or
artifact, never imported as source. The DEFAULT,
so a manifest that never mentions `kind` keeps
validating unchanged.</p>
<p>ADDITIVE, BUT ORDERED — and the ordering is not optional. Nickel record contracts
are CLOSED: a manifest that declares `kind` against a data-dir schema predating the
field fails outright with `extra field `kind``. So the field costs nothing to IGNORE
and requires a reinstall to USE. Reinstall precedes tagging; migration 0046 says so
first, and it says so because the first attempt at tagging the two consumer projects
failed exactly this way. ontoref itself did not catch it, because ontoref's manifest
imports the schema SOURCE by relative path (`../../code/ontology/defaults/manifest.ncl`)
while every consumer imports the INSTALLED copy — the self-hosting project is the one
project that cannot feel this class of break. Worth remembering the next time an
&quot;additive&quot; schema change is asserted rather than tested on a consumer.</p>
<p>Layer 3 has no `kind` BY CONSTRUCTION: it has no region in this repo. A project
that finds itself wanting to tag one has put caller cabling in its own tree.</p>
<p>The consequence is that &quot;what of me is yours&quot; stops being prose. It is a query:</p>
<p>the union of the paths of every layer with kind = 'IntegrationSurface</p>
<p>and everything else — the site, the doors, the campaigns, the CV modes, the
constellation instance — is 'SelfManagement or 'Internal, and is nobody's
business but this project's. That is the whole content of the answer to
&quot;of what use are ontoref's own site ADRs to provisioning?&quot;: none, and the
manifest now says so in a form a validator can read.</p>
<p>THE RULING ON `constellation` — THE CASE THAT DECIDED THE AXIS</p>
<p>`constellation` was the test case, because it is the one artifact of ontoref's
that has demonstrably reached another project. It is not one thing but three,
and they split exactly along the layers:</p>
<p>reflection/schemas/constellation.ncl → LAYER 2. Protocol.
The taxonomy ('Primary/'Addon/'Projection), the forge/mirror/sovereign
vocabularies, and the cross-field rule that a 'Projection owns no repo and
a 'Never member carries no mirror. These are generic. The proof is
empirical, not argued: provisioning BOUND to this schema, EXTENDED it
(a `distribution`/'OCI axis and a `workspaces` block) and ADR'd it (its own
adr-052). A second project binding and extending an artifact is what an
integration surface IS. It stays.</p>
<p>ontology/constellation.ncl → LAYER 1. Self.
WHICH members ontoref has (code, outreach, vault, desktop) and where they
push. Per project. Provisioning has its own, listing its own.</p>
<p>ontoref's forge POLICY → LAYER 1 SITTING INSIDE LAYER 2.
&quot;Forgejo always canonical · GitHub always a one-way mirror · vault never
mirrored&quot; is ontoref's own constellation policy, and it was written into
the comments of the shared schema and into the error message of its
contract. This is the ONLY ontoref-self content that has ever reached a
consumer — and it did: provisioning's copy repeats the `vault` rule
although vault is not a member of provisioning. It comes out of the schema.</p>
<p>The schema is protocol. What leaves it is the policy, not the contract. And note
HOW it travelled: provisioning did not import the schema, it COPIED it — 118
lines here, 164 there, the comments already diverged. That is the drift this ADR
predicted in its own context, arriving on schedule, and it is now an anti-pattern
with a name (`layer-2-adopted-by-copy`).</p>
<p>A type cannot stop a comment from lying. That is precisely why the export axis
has to be a FIELD and not a paragraph.</p>
<h2>Constraints</h2>
<ul><li><strong>Hard</strong> Every ontoref-onboarded project has .ontoref/ontology/core.ncl</li><li><strong>Hard</strong> Every ontoref-onboarded project has .ontoref/reflection/qa.ncl</li><li><strong>Hard</strong> Every ontoref-onboarded project has .ontoref/reflection/backlog.ncl</li><li><strong>Hard</strong> Every ontoref-onboarded project has an .ontoref/adrs/ directory</li><li><strong>Hard</strong> A manifest layer with kind = 'IntegrationSurface is declared if and only if the manifest declares registry_provides — and every path that layer names resolves from the project root</li><li><strong>Soft</strong> qa entries whose primary topic is a Layer N concern carry the layer-N tag (anchors only; satellites inherit by association)</li></ul>
<h2>Alternatives considered</h2>
<ul><li><strong>Keep sniffing a path, and teach the check about code/domains/ as well as catalog/domains/</strong><em>rejected:</em> The cheap fix, and it inverts the ADR. `code/domains/` is where ontoref's domains sit BECAUSE ADR-052 split the constellation — a fact about one member's repo layout. Teaching a protocol constraint to look there bakes ontoref's own shape into a check every consumer runs, which is precisely the contamination this ADR exists to prevent, committed by the ADR itself. It also fails on contact with the next adopter who spells it a third way, and it can never verify that the surface resolves. Rejected as anti-PAP: the self-hosting project must not privilege its own layout in the protocol.</li><li><strong>A new top-level `exports` block in the manifest, separate from layers</strong><em>rejected:</em> A fourth region-of-the-repo concept beside `layers`, `levels` and this ADR's own layers. manifest.layers ALREADY means 'a named region of the repo with visibility rules per mode' and already carried `protocol` beside `self-description` — the axis existed and had only rotted for want of a reader. Adding a parallel block would leave two declarations of the same regions, free to disagree, which is the ADR-070 pathology (declared twice, no witness) reproduced one layer down. One typed field on the existing mechanism, not a second mechanism.</li><li><strong>Single 'ontoref content' namespace, no layering</strong><em>rejected:</em> Observed drift outcomes (Layer 3 in qa, Layer 2 schemas treated as notes, etc.) are caused precisely by absence of layering. The single-namespace alternative is what we're correcting; rejecting it is the entire decision.</li><li><strong>Two-layer model collapsing self-management with integration-surface</strong><em>rejected:</em> lian-build's adoption explicitly experienced the schema-vs-rationale split: schemas/build_directives.ncl (Layer 2 contract) and adrs/adr-001-lian-build-as-standalone.ncl (Layer 1 rationale) have different audiences, different change cadences, different validation rules. Collapsing them produced the FAQ-vs-contract confusion that retiring lian-build/.ontology/FAQ.md addressed. The two-layer alternative re-creates that confusion.</li><li><strong>Make Layer 3 (caller-side) optionally co-resident with Layer 2 in the producer's repo</strong><em>rejected:</em> Creates ambiguity about who owns the cabling. If a producer's repo carries `extensions/&lt;self&gt;/`, who maintains it when a caller's workspace evolves? The producer doesn't know about the caller's infrastructure; the caller can't depend on the producer to update the cabling on its schedule. Co-residence inverts the integration arrow.</li><li><strong>Numbered layers (Layer 1 / 2 / 3) vs named layers ('self' / 'integration-surface' / 'caller-side')</strong><em>rejected:</em> Named layers are more descriptive but verbose; numbered layers are more precise but flat. The compromise (this ADR): use 'Layer N — descriptive name' in prose, 'layer-N' in tags. Tag economy wins; prose retains the descriptive name.</li></ul>
<h2>Anti-patterns</h2>
<ul><li><strong>One Project's Own Policy Written Into the Contract Everyone Binds To</strong> — A Layer 2 artifact — a schema, a template, a default — carries the authoring project's Layer 1 policy in its comments, its error strings or its examples. The contract is generic; the prose around it is not. Consumers inherit a rule about a thing they do not have, and cannot tell which half was meant for them. Measured, not hypothetical: reflection/schemas/constellation.ncl carried ontoref's forge policy ('vault never mirrored'), and provisioning's copy repeats the vault rule although vault is not one of its members. A type cannot stop a comment from lying — which is the whole argument for making the export axis a field.</li><li><strong>Binding to an Integration Surface by Forking It</strong> — A consumer adopts a Layer 2 artifact by copying the file into its own tree and editing it, instead of importing it and extending it. The copy is a fork with no upstream: it drifts silently, and the drift is invisible precisely because both sides still look like the same file. Observed: provisioning's constellation.ncl is 164 lines against ontoref's 118, the comments already divergent, with no mechanism that could ever report the gap. This is the drift this ADR predicted in its own context, arriving on schedule.</li><li><strong>The Protocol Surface Defined by a Paragraph Nothing Checks</strong> — What a protocol exports to its consumers is recorded in documentation — a migration-trigger table in CLAUDE.md, a README section, a convention — rather than in the data. It works exactly as long as everyone remembers, and it fails silently: the project's own site ADRs, campaign modes and CV workflows accumulate in the same directories as the protocol's schemas and adoption modes, with nothing able to state which is which. The self-hosting project is most exposed, because it is the one whose own concerns share a spine with the contract.</li></ul>
<h2>Related ADRs</h2>
<p><a href="/adr/001">ADR-001</a> · <a href="/adr/012">ADR-012</a> · <a href="/adr/016">ADR-016</a> · <a href="/adr/018">ADR-018</a> · <a href="/adr/032">ADR-032</a> · <a href="/adr/052">ADR-052</a> · <a href="/adr/062">ADR-062</a> · <a href="/adr/070">ADR-070</a></p>

View file

@ -0,0 +1,12 @@
{
id = "adr-020",
title = "Three-Layer Model for Project Ontoref Instances — the Export Axis Declared on manifest.layers",
slug = "020",
excerpt = "",
tags = ["adr", "accepted"],
page_route = "/adr/020",
graph = {
implements = [],
related_to = ["adr-001", "adr-012", "adr-016", "adr-018", "adr-032", "adr-052", "adr-062", "adr-070"],
},
}

View file

@ -22,7 +22,7 @@ css_class: "category-adr"
<h2>Constraints</h2>
<ul><li><strong>Hard</strong> New kernel crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric, ontoref-core, ontoref-modes, ontoref-cli) MUST NOT depend on stratumiops crates (stratum-graph, stratum-state, stratum-db, platform-nats)</li><li><strong>Hard</strong> Every operation MUST round-trip through canonical CBOR encoding with byte-stable output: encode(op) == encode(op) for all op; OpId(op) == blake3(encode(op))</li><li><strong>Hard</strong> Substrate crates MUST NOT contain todo!(), unimplemented!(), or panic!() with not-implemented messages</li><li><strong>Hard</strong> Plan §1.bis MUST exist and name both engaged tensions (ontology-vs-reflection, formalization-vs-adoption) with direction-of-motion and synthesis state; this ADR cites it</li><li><strong>Hard</strong> At G12 completion, crates/legacy/ MUST NOT exist; crates/ontoref-ontology/, crates/ontoref-reflection/ MUST NOT exist; reflection/, .ontology/, adrs/*.ncl, templates/ MUST NOT exist</li></ul>
<ul><li><strong>Hard</strong> New kernel crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric, ontoref-core, ontoref-modes, ontoref-cli) MUST NOT depend on stratumiops crates (stratum-graph, stratum-state, stratum-db, platform-nats)</li><li><strong>Hard</strong> Every operation MUST round-trip through canonical CBOR encoding with byte-stable output: encode(op) == encode(op) for all op; OpId(op) == blake3(encode(op))</li><li><strong>Hard</strong> Substrate crates MUST NOT contain todo!(), unimplemented!(), or panic!() with not-implemented messages</li><li><strong>Hard</strong> Plan §1.bis MUST exist and name both engaged tensions (ontology-vs-reflection, formalization-vs-adoption) with direction-of-motion and synthesis state; this ADR cites it</li><li><strong>Soft</strong> At G12 completion, crates/legacy/ MUST NOT exist; crates/ontoref-ontology/, crates/ontoref-reflection/ MUST NOT exist; reflection/, .ontology/, adrs/*.ncl, templates/ MUST NOT exist</li></ul>
<h2>Alternatives considered</h2>

View file

@ -74,12 +74,21 @@ pipeline is gated on them.</p>
work is tracked until that stops being true. This is the rule that governs the
.gitignore, and it is why site/public/images (196 of 209 images exist nowhere else),
site/content/{adr,catalog,domains} (projections whose generators were unwired or
broken) and htmx-templates/ (until templates-overlay/ existed) are tracked despite
broken) and htmx-templates/ (until site/_htmx/ existed) are tracked despite
looking generated. Ignoring a tree is a claim that it is reproducible; making that
claim falsely deletes work in silence.</p>
<p>Consequently, the site level gets the overlay slot the assembler never gave it:
site/templates-overlay/ is the declared source for template work owned by THIS level.
Anything edited straight into the assembled output is, by definition, unwitnessed.</p>
site/_htmx/ is the declared source for work owned by THIS level — templates in
site/_htmx/templates/, htmx-runtime overrides in site/_htmx/assets/ — and the `_`
prefix says what site/_public already says here: this is the tree you edit, its
unprefixed twin is the tree that gets generated. Anything edited straight into the
assembled output is, by definition, unwitnessed.</p>
<p>And the rule is ONE rule, so it governs both delivery trees and both are assembled by
ONE script (scripts/build/assemble-layers.nu): a file present in a higher layer wins,
a file absent from it is inherited from the layer below — the exact cascade the
implementation already applies to the framework. htmx-assets/ had only the framework
layer, which meant an ext/* override shipped by website-htmx-rustelo could never reach
the site; the asymmetry was invisible precisely because nothing had tried to use it.</p>
<p>THE GATES DECIDE, AND THEY RUN BEFORE THE DAMAGE. Three exist and pass today:
`adr-check` (the spine holds no ADR the site never published), `templates-check`
(the assembled tree is exactly reproducible from its declared sources — any drift
@ -95,7 +104,7 @@ pipeline is gated on them.</p>
<h2>Constraints</h2>
<ul><li><strong>Hard</strong> Every projection generator that feeds a site content-kind MUST be invoked by a recipe reachable from `sync-full`; a generator runnable only by hand is drift by construction.</li><li><strong>Hard</strong> The site/r &lt;-&gt; site/public/r mirror MUST be typed by file ownership, never by direction: content indexes flow r -&gt; public/r with --delete, and the four nu artifacts flow public/r -&gt; r and are excluded from the outbound leg.</li><li><strong>Hard</strong> Template work owned by the site level MUST live in site/templates-overlay/; htmx-templates/ is assembled output and MUST NOT be hand-edited — `just templates` deletes it on every run.</li><li><strong>Hard</strong> `just templates-check` MUST pass: the assembled template tree reproduces exactly from its declared sources, so no file in it exists nowhere else.</li><li><strong>Hard</strong> `just adr-check` MUST pass: every ADR in .ontoref/adrs has a page in the site's adr content-kind.</li><li><strong>Hard</strong> A tree MUST NOT be gitignored unless a tracked source plus a named recipe reproduce it; a generated tree holding the only copy of any work stays tracked until that ceases to be true.</li><li><strong>Hard</strong> site/content/ MUST contain only content instances; schemas and contracts live outside it (site/schemas/), because every consumer of that tree nickel-exports each .ncl as data.</li><li><strong>Soft</strong> The deploy SHOULD refuse to ship when a declared gate fails — the check decides, never the operator's memory. Enforcement point: the publish path in website-htmx-rustelo.</li></ul>
<ul><li><strong>Hard</strong> Every projection generator that feeds a site content-kind MUST be invoked by a recipe reachable from `sync-full`; a generator runnable only by hand is drift by construction.</li><li><strong>Hard</strong> The site/r &lt;-&gt; site/public/r mirror MUST be typed by file ownership, never by direction: content indexes flow r -&gt; public/r with --delete, and the four nu artifacts flow public/r -&gt; r and are excluded from the outbound leg.</li><li><strong>Hard</strong> Work owned by the site level in either delivery tree MUST live in its layer under site/_htmx/ — templates in site/_htmx/templates/, htmx-runtime overrides in site/_htmx/assets/. htmx-templates/ and htmx-assets/ are assembled output and MUST NOT be hand-edited: `just templates` and `just htmx-assets` delete them on every run.</li><li><strong>Hard</strong> `just templates-check` AND `just assets-check` MUST pass: each assembled delivery tree reproduces exactly from its declared layers, so no file in either exists nowhere else.</li><li><strong>Hard</strong> `just adr-check` MUST pass: every ADR in .ontoref/adrs has a page in the site's adr content-kind.</li><li><strong>Hard</strong> A tree MUST NOT be gitignored unless a tracked source plus a named recipe reproduce it; a generated tree holding the only copy of any work stays tracked until that ceases to be true.</li><li><strong>Hard</strong> site/content/ MUST contain only content instances; schemas and contracts live outside it (site/schemas/), because every consumer of that tree nickel-exports each .ncl as data.</li><li><strong>Soft</strong> The deploy SHOULD refuse to ship when a declared gate fails — the check decides, never the operator's memory. Enforcement point: the publish path in website-htmx-rustelo.</li></ul>
<h2>Alternatives considered</h2>

View file

@ -0,0 +1,134 @@
---
id: "adr-071"
title: "A concept's rendering is declared per language, and the gate declares what it cannot check"
slug: "071"
subtitle: "Accepted"
excerpt: "Writing about software in Spanish has no settled convention, and the project had been resolving the"
author: "ontoref"
date: "2026-07-13"
published: true
featured: false
category: "accepted"
tags: ["adr-071", "adr", "accepted"]
css_class: "category-adr"
---
<h2>Context</h2>
<p>Writing about software in Spanish has no settled convention, and the project had been resolving the
question one file at a time. The first attempt, `anglicisms.ncl`, was a flat deny-list: ~50 English
words, a Spanish replacement, a severity, and a gate that failed on a Hard hit.</p>
<p>It was the right instinct with the wrong shape, and it was wrong in a way that MEASURES.</p>
<p>WHAT WAS MEASURED (2026-07-12, across the glossary and the whole positioning layer, code spans
stripped, word boundaries honoured):</p>
<p>2 concepts DIVERGED BETWEEN THE TWO SURFACES
`reflection` glossary kept the English (4×) · positioning wrote &quot;reflexión&quot; (3×)
`gate` glossary kept the English (3×) · positioning wrote &quot;compuertas&quot;</p>
<p>5 concepts were INCOHERENT INSIDE A SINGLE FILE
the glossary — the document whose ONLY job is to fix the vocabulary — wrote both `schema` and
«esquema», both `drift` and «deriva»; the positioning layer wrote both `witness` and «testigo»,
both `runtime` and «tiempo de ejecución».</p>
<p>The headline is not that two documents disagreed. It is that EACH DOCUMENT DISAGREED WITH ITSELF.
The arbiter did not agree with itself — not through carelessness, but because the decision had
NOWHERE TO LIVE. A rendering was a fact about a string in a file, not a property of the concept.</p>
<p>Six vocabularies were live: the glossary, the positioning layer, two sets of `.ftl` (one of them
DEAD — `content/i18n/build-tools/`, which nothing reads), a hand-rolled Nushell VOCAB table
(`expediente-vocab.nu`), and seven generators with Spanish strings baked in. None owned, none
witnessing the others. This is the ADR-070 pathology — declared twice, no witness — one layer down,
and it is the third clone of the same pathogen after `deriva-hardcoded` and `espejo-que-revertía`.</p>
<p>AND THE COLLISION NOBODY COULD SEE. The glossary renders `door`&quot;puerta&quot; (the funnel entry,
ADR-064). Its OWN Spanish definition of `testigo-verificado` was rendering `gate`&quot;puerta&quot; as
well. Two distinct concepts had already collapsed into one Spanish word, inside the document written
to prevent exactly that. Each decision is defensible ALONE, which is why reading never catches it.</p>
<p>THE FIRST ATTEMPT AT A FIX REPRODUCED THE DISEASE, LIVE. The surface-typed lexicon that preceded this
ADR (a four-class deny-list with a per-surface axis) was a SEVENTH vocabulary, built parallel to the
six without seeing them. And it misfiled `reflection` as untranslatable — because it collided with
the `reflection/` directory — while correctly rendering `schema` as «esquema» in the same file, on
the same day, under a rule its own author had just written. Nothing could contradict it, so it
convinced itself. The agent had the full ontology loaded. That is what a vocabulary without a
witness produces, and it is the most useful evidence this ADR has.</p>
<h2>Decision</h2>
<p>A concept's rendering is DECLARED PER LANGUAGE, in one registry, under contracts that run across
every surface that speaks. And the gate DECLARES WHAT IT CANNOT CHECK.</p>
<p>STRUCTURE</p>
<p>reflection/schemas/rendering.ncl how a concept renders in a language + the seven cross-contracts
reflection/schemas/term.ncl gains an OPTIONAL `policy` — a concept carries its own decision
reflection/schemas/lexicon.ncl LANGUAGES, vocabulary that is not a concept, coverage
ontology/glossary.ncl concepts WITH a definition (protocol surface; ADR-063 gates it)
ontology/lexicon.ncl vocabulary WITHOUT one (`drift`, `flag` — nothing to define)
ontology/registry.ncl THE UNION — the only place the contracts run, and the only
thing a consumer may read</p>
<p>The two registries are not a compromise: forcing a definition onto `flag` would be ceremony, and
deleting `glossary.ncl` would break the protocol seed and ADR-063's Hard constraints. What makes the
split SAFE is that the union is contracted as ONE namespace. A consumer reading half a registry
believes half a policy.</p>
<p>TWO ROADS TO KEEPING A SOURCE FORM. CONFLATING THEM IS THE BUG.</p>
<p>'Identifier it appears verbatim in a command, an API, a `kind:`, an error message.
Translating breaks the REFERENCE — the reader cannot get from the text to
the tool. Mechanical, true in EVERY language. (`commit`, `ADR`, `NCL`)</p>
<p>'NoFaithfulRendering no word in THIS language carries the concept without displacing it.
Translating breaks the CONCEPT. A fact about ONE language, possibly false in
the next. (`gate`)</p>
<p>Same verdict, incompatible reasons, and the contract enforces the difference: 'Keep justified by
'FaithfulRendering is REFUSED — if a faithful word exists, use it. That single rule is what corrects
`reflection` → «reflexión», and it is the rule the previous design could not express.</p>
<p>For `gate`, the note must say what each candidate LOSES, because each looks reasonable alone:
«compuerta» sends it to ELECTRONICS · «puerta» is already `door`'s · «umbral» loses the typed policy
· «punto de control» loses that it BLOCKS · «criterio de paso» is honest and is a periphrasis.</p>
<p>A REJECTED RENDERING IS NOT ONLY A CALQUE.</p>
<p>'Calque a source verb forced into the target's conjugation (&quot;mergear&quot;). It grates, so it is
found. The defect is the MORPHOLOGY, not the loanword: a borrowed noun is inert, a
borrowed verb must pick a conjugation and drags its register in. Hence the repair —
LIGHT VERB + KEPT NOUN, &quot;hacer un commit&quot; — carried on the denial itself, because a
noun cannot repair a verb.
'FalseFriend library → «librería» (a SHOP). Reads perfectly. Is wrong. Has largely won. THESE ARE
THE DANGEROUS ONES, because nobody hunts for a mistake that sounds right.
'Displaces a true cognate borrowed from ANOTHER DISCIPLINE («compuerta», from electronics).
'Regional correct elsewhere, rejected here for REGISTER («corrida»). A choice, and it says so.</p>
<p>Each denial carries its reason IN THE DATA, or someone reverts it in good faith in six months.</p>
<p>RENDERINGS ARE INJECTIVE, PER LANGUAGE. Two concepts may not render to the same word. This is the
contract a human found and no validator would have, and it fires on the real data today.</p>
<p>THE LANGUAGE AXIS IS A GUARANTEE. `languages` is declared ONCE — a fact about the project, not the
website; rustelo's `site.ncl` becomes a consumer, not a second truth. Adding one FAILS THE BUILD
until every concept answers for it and every surface declares its content. A half-added language is
not a pending translation; it is a site quietly serving the wrong one — which four `domains/es/` door
pages did for months, carrying Spanish frontmatter over English bodies, because a surface had no
language and nothing could ask.</p>
<p>THE GATE DECLARES WHAT IT CANNOT CHECK — and this is half the decision, not a footnote.</p>
<p>On its first run the gate reported 52 Hard findings. FORTY-THREE WERE FALSE, every one of them the
checker asserting what a regex cannot establish:</p>
<p>· &quot;puerta&quot; is denied for `gate` and is the RIGHT rendering of `door`. Injectivity guarantees a form
is the PREFERRED rendering of at most ONE concept; it says nothing about a form preferred by one
and denied by another — and that case is legal and common. It is what an AVOIDED collision looks
like from the other side. → `gateable = false`
· &quot;build system&quot;, &quot;un fork&quot;, &quot;el deploy&quot; are BORROWED COMPOUNDS, entering the language as units.
Demanding «compilar» inside «cada build system usa DAGs» is nonsense. → `gate_source = false`
· `tags = [&quot;witness&quot;, &quot;adr-070&quot;]` is a routing key, not prose. Renaming it breaks the filter.
· a verbatim quotation in the source language is a CITATION. You do not translate a quote, and no
checker can tell one from the author's prose — on the page they are identical. → `&lt;!-- lang:en --&gt;`</p>
<p>The rule is still EMITTED — the glossary and the public note print it, because a human writing a
sentence HAS the context a regex lacks — but no surface fails on it. This is not weakening the
policy. It is the policy refusing to claim knowledge it does not have. A gate that cries wolf is a
gate people learn to skip, and a skipped gate is the failure this entire ADR is about.</p>
<p>THE POLICY EXPLAINS ITSELF WHERE IT IS READ. The Spanish glossary and a public &quot;Nota sobre el
lenguaje&quot; are GENERATED from the same NCL the gate reads; `--check` fails on drift. A rule enforced
by a machine and justified nowhere is a rule people comply with and resent. This is not a legal
disclaimer: the right analogy is the licence or the code of conduct — normative, published,
versioned — and unlike either, executed by CI.</p>
<h2>Constraints</h2>
<ul><li><strong>Hard</strong> The rendering policy MUST live in `ontology/registry.ncl` (the union of glossary + lexicon) under the contracts in `reflection/schemas/rendering.ncl`. `anglicisms.ncl` MUST NOT exist. Consumers MUST read the union, never a registry half.</li><li><strong>Hard</strong> A 'Keep MUST declare WHICH road: 'Identifier (translating breaks the reference) or 'NoFaithfulRendering (translating breaks the concept), and MUST carry the evidence for it. 'Keep justified by 'FaithfulRendering is forbidden — if a faithful word exists, use it.</li><li><strong>Hard</strong> Within a language, two concepts MUST NOT render to the same form. `gate` → «puerta» and `door` → «puerta» is refused by contract.</li><li><strong>Hard</strong> `languages` is declared ONCE. Adding one MUST fail the build until every governed concept declares its rendering and every surface declares its content in that language.</li><li><strong>Hard</strong> A rule the checker cannot falsify MUST be marked uncheckable IN THE DATA (`gateable`, `gate_source`) or handled by a declared marker (`&lt;!-- lang:xx --&gt;`, the key whitelists) — never by weakening the policy, and never by editing correct prose to appease the tool.</li><li><strong>Hard</strong> Coverage MUST declare every (surface × target language). A surface with no content is `sources = []` — inactive by absence of input. Omission is forbidden. A glob MUST NOT be both source and projection, and a BILINGUAL file MUST declare `mode = 'I18nKeys`.</li><li><strong>Hard</strong> The gate and both oracles MUST be reachable from a CI recipe (`just ci-lexicon`). A gate runnable only by hand is drift by construction.</li><li><strong>Hard</strong> The reader-facing surfaces (`glosario-es.md`, the public language note) MUST be generated from the registry the gate reads, and their `--check` MUST fail on drift.</li><li><strong>Soft</strong> A `web` document leaning on KEPT terms SHOULD hand the reader the glossary. The check REPORTS the count and MUST NOT fail while the site serves no glossary route; when `gloss_markers` names a real affordance it becomes enforceable with no code change.</li></ul>
<h2>Alternatives considered</h2>
<ul><li><strong>Keep the four-class surface-typed lexicon (the design this supersedes) and just fix `reflection` by hand.</strong><em>rejected:</em> It would fix the symptom and leave the cause. A single 'rigid' class CANNOT express why `commit` and `gate` are both kept, so the next term would be misfiled by the same reasoning — and the misfiling is invisible, because the class name reads as a decision. The bug was in the schema's expressive power, not in the entry.</li><li><strong>One merged registry: fold the glossary into the lexicon, delete `glossary.ncl`.</strong><em>rejected:</em> The literal merge the design called for, and the evidence refused it: `glossary.ncl` inherits the protocol-shipped seed and ADR-063 carries accepted Hard constraints with `Grep` checks against it. Deleting it breaks an accepted ADR and forces a migration across every consumer. Merging the POLICY (onto the term) achieves the goal without any of that; merging the FILES achieves nothing extra.</li><li><strong>Define the vocabulary in Fluent `.ftl` terms, since rustelo already speaks FTL.</strong><em>rejected:</em> FTL is a runtime MESSAGE catalogue and is excellent at that. It has no types, no contracts, and no way to declare that a term is untranslatable or WHY — so pushing the vocabulary into it gains reach and loses governance. The right direction is the inverse: the NCL registry is the source, and `.ftl` terms become one of its PROJECTIONS. Then FTL reaches everywhere without changing FTL, and Weblate becomes a possible consumer instead of the owner of the policy.</li><li><strong>Make every finding Hard: no `gateable`, no `gate_source`, no citation marker.</strong><em>rejected:</em> This is the run that produced 52 findings of which 43 were false. It fails a slide for «cada build system usa DAGs», fails a page for the word «puerta» in a text about the four funnel doors, and demands a translated tag that would break the routing. Nobody would keep such a gate for a week, and an ignored gate is a green tick over unread files — the failure this ADR exists to end, reintroduced by the opposite route.</li><li><strong>A style guide in prose, no gate.</strong><em>rejected:</em> This is precisely what the project had, and the record shows what it produced: the arbiter contradicting itself in seven places, four Spanish pages serving English for months, and a false friend in a published post. Advisory prose does not bind a human under deadline and binds a probabilistic agent not at all — `testigo-verificado` names the reason: what is not verified is not held. The agent that wrote the FIRST version of this policy misfiled a term the same day, under its own rule.</li></ul>
<h2>Anti-patterns</h2>
<ul><li><strong>One Lexical Class for Two Incompatible Reasons</strong> — A vocabulary marks a term 'do not translate' without recording WHY. Two incompatible facts hide under one label: 'translating breaks the reference' (it is an identifier — mechanical, universal) and 'translating breaks the concept' (this language has no word for it — a claim about ONE language). Because the label reads as a decision, the misfiling is invisible: a term with a perfectly faithful cognate gets filed beside one with no rendering at all, and nobody re-examines it. The tell is a term kept because it COLLIDES WITH A DIRECTORY NAME — an identifier collision, which blanking code spans already handles, dressed up as a lexical fact.</li><li><strong>Trusting a Cognate Because It Looks Like Its Source</strong> — A translation policy hunts for the ugly borrowings ('mergear', 'deployar') and accepts anything that looks native. But languages evolve their cognates APART, and the resulting displacement is invisible precisely because the word still resembles its origin: «librería» for library is a SHOP; «compuerta» for gate is a real cognate borrowed from ELECTRONICS and drags a logic-circuit reading into a sentence about state transitions. The dangerous rendering is the one that does NOT grate — it reads fluently, it often WINS in usage, and nobody hunts for a mistake that sounds right.</li><li><strong>Two Concepts, One Word, Each Decision Defensible Alone</strong> — Concept A renders to a word; concept B renders to the same word; each choice is correct in isolation and the two live in different files. The translation MERGES two concepts the source kept apart — inventing an ambiguity the original never had. This is the classic failure of technical translation and it is nearly invisible to reading, because there is no single line where the mistake appears. Ours had already happened: the glossary rendered `door` → «puerta» while its own Spanish definition of the witness rendered `gate` → «puerta» too.</li><li><strong>A Gate That Asserts What a Regex Cannot Establish</strong> — A checker reports findings that are TRUE ABOUT THE STRING and FALSE ABOUT THE LANGUAGE: a word that is another concept's legitimate rendering, a loanword inside a borrowed compound ('build system'), a routing key in a tag list, a verbatim citation in the source language. Each finding is unfixable as reported — the prose is correct — so the author learns the tool is unreliable and starts skipping it. This is WORSE than a false positive that is obviously wrong, because it takes real thought to dismiss. 43 of our first 52 findings were this. The wrong repairs are equally available and both destroy the policy: weaken the rule until the tool goes quiet, or edit correct prose to appease it.</li><li><strong>A Checker Whose Coverage Is a Glob Nobody Audits</strong> — A gate hardcodes the files it scans, the content tree grows past that glob, and it goes on printing a green tick over families it never opens. The green is not a lie about the files it read — it is a lie about the files it did not, and silence about coverage reads to every human as full coverage. The extreme case: a gate that CANNOT read the file format where its own violation was measured (a bilingual .ncl) and therefore reports clean over the very surface that is broken.</li></ul>
<h2>Related ADRs</h2>
<p><a href="/adr/070">ADR-070</a> · <a href="/adr/066">ADR-066</a> · <a href="/adr/064">ADR-064</a> · <a href="/adr/063">ADR-063</a> · <a href="/adr/035">ADR-035</a></p>

View file

@ -0,0 +1,12 @@
{
id = "adr-071",
title = "A concept's rendering is declared per language, and the gate declares what it cannot check",
slug = "071",
excerpt = "",
tags = ["adr", "accepted"],
page_route = "/adr/071",
graph = {
implements = [],
related_to = ["adr-070", "adr-066", "adr-064", "adr-063", "adr-035"],
},
}

View file

@ -0,0 +1,168 @@
---
id: "adr-072"
title: "A check declares its coverage, a mechanism declares its extent, and the witness carries the extent"
slug: "072"
subtitle: "Accepted"
excerpt: "A witness proves THAT a check ran and what it returned. ADR-050 says so explicitly, and says it as"
author: "ontoref"
date: "2026-07-13"
published: true
featured: false
category: "accepted"
tags: ["adr-072", "adr", "accepted"]
css_class: "category-adr"
---
<h2>Context</h2>
<p>A witness proves THAT a check ran and what it returned. ADR-050 says so explicitly, and says it as
a limitation: structure, never truthfulness.</p>
<p>There is a second limitation it does not say, and it is the one that bites.</p>
<p>A WITNESS DOES NOT PROVE THAT THE CHECK LOOKED AT EVERYTHING IT SHOULD HAVE.</p>
<p>Coverage — which files, which formats, which levels, which languages a check actually reads — is
UNWITNESSED. So a check can go green over a shrinking fraction of reality and its receipt stays
perfectly valid. It does not fail. It does not warn. It looks where it always looked, finds nothing
wrong there, and signs.</p>
<p>AND THE COVERAGE EXPIRES PRECISELY WHEN YOU IMPROVE THE MECHANISM. That is the finding, and it is
not a metaphor. In one session, on one witness (`ore rustelo routes`), THREE TIMES:</p>
<p>· the SITE gained ownership of its templates (adr-070's overlay). The witness read only the
framework's `crates/pages_htmx/templates/` and reported a site-owned page as broken.
· the CONSUMER gained ownership of its routes (`merge_site_routes`). The witness read only the
image's `routes.ncl` — blind to the very mechanism that had just been built to enable it. It
counted 26 routes where there were 28 and would have declared a live page non-existent.
· those routes moved TOML → NCL, TO PUT THEM UNDER CONTRACT. The witness kept reading `.toml`.
Blind again, to the same mechanism, one step later.</p>
<p>Warnings emitted across all three: ZERO.</p>
<p>Two cousins in the same session: a check that returned an EMPTY LIST when its source failed to
export (green because it could not look), and a check that measured &quot;does this route have a title?&quot;
instead of &quot;does this route have a body renderer?&quot; — satisfiable by adding fourteen strings while
304 declared Fluent keys stayed unserved on every request.</p>
<p>And a fourth, which is the one that settles the argument. The tool that PUBLISHES the case file
about this pathology reported `8 case(s) reproduce from their .ncl — no hand-edited render` while
five cases existed in Spanish and three in English. GREEN OVER AN INCOMPLETE BILINGUAL SET. It could
not say otherwise: it only ever verified what EXISTED, never what SHOULD exist. The expediente about
the blind witness was published by a blind witness.</p>
<p>AND A FIFTH, ON THE SAME TOOL, AN HOUR LATER, which names the axis the other four did not. The case
was generated, validated, activated and indexed. Its article answered 200. Its entry was in the
index. AND IT WAS NOT IN THE GRID. Not pagination — the page size was twelve and there were five.
`content_processor` writes the indexes to `site/r/`; THE SERVER READS `site/public/r/`, and a
separate recipe mirrors one into the other. It had not run. The two trees said five and three, dated
a day apart, and every check on the page walked straight past it BECAUSE THEY ALL ASKED &quot;DOES THE
FILE EXIST?&quot; OF THE TREE NOBODY SERVES. This is expediente 69/58 — the mirror that reverted its own
work — on a path its own cure never covered.</p>
<p>The axis, then, is not a format and not a language. It is WHERE. And it is the axis nobody thinks to
declare, because a file that exists is assumed to exist — when existence is not a property of a
file, it is a property of THE TREE THAT IS READ.</p>
<p>THE OBVIOUS FIX IS THE WRONG ONE, AND THE EVIDENCE IS DECISIVE. &quot;Write it in the howto.&quot; &quot;Remember
to update the check.&quot; That is advisory prose, and this protocol has spent three case files proving
advisory prose does not bind — not a human under deadline, and not a probabilistic actor at all.
The proof here is total: THE AGENT THAT BROKE THE RULE THREE TIMES HAD AUTHORED IT THAT SAME
AFTERNOON, in adr-071, under its own name — `gate-green-over-nothing`. Writing it down was not
enough. Nothing that depends on remembering is enough.</p>
<p>AND THE PRIMITIVE ALREADY EXISTS, BUILT TWICE, UNRECOGNISED. `CoversDeclaredLanguages` (adr-071)
does exactly the right thing on one axis: adding a language to the registry FAILS THE BUILD until
all 45 concepts answer for it. A half-added language cannot ship. `CoversEverySurfaceAndLanguage`
does the same for surfaces. Both were written for a concrete purpose and both turn out to be the
special case of one general rule that the protocol has never named.</p>
<h2>Decision</h2>
<p>COVERAGE BECOMES PART OF THE WITNESSED SURFACE.</p>
<p>Three declarations and one gate:</p>
<p>1. A MECHANISM DECLARES ITS EXTENT — the axes over which it exists. Formats (`.ncl`, `.toml`),
levels (framework / site overlay), languages (`en`, `es`), locations (image / consumer),
surfaces, and — the axis that had to be learned the hard way — THE TREE THAT IS READ. The
extent is DATA, in the mechanism's own declaration, not folklore in a script.</p>
<p>2. A CHECK DECLARES ITS COVERAGE — which of those axes it actually reads. This already exists in
one place (`coverage` in `ontology/lexicon.ncl`) and it was not enough on its own, which is
the instructive part: the coverage declared what the check READ, and there was nothing to
check it against. A coverage declaration with no extent to compare it to is a signed opinion.</p>
<p>3. A GATE: COVERAGE ⊇ EXTENT, or the build breaks. Not &quot;the check should be updated&quot; — the build
BREAKS. A mechanism that grows an axis its witness does not read is a mechanism whose witness
is unproven, and unproven is not green.</p>
<p>4. THE WITNESS CARRIES THE EXTENT'S DIGEST. A receipt issued against the old extent is not a
passing receipt against the new one. It stops being GREEN and becomes UNPROVEN — a different
word, and today there was no such word. This is what makes the expiry automatic rather than
remembered: the witness invalidates itself when the world it witnessed changes shape.</p>
<p>THE INVERSION, WHICH IS THE WHOLE DECISION:</p>
<p>before → improving the mechanism BLINDS its witness, in silence
after → improving the mechanism BREAKS its witness, loudly</p>
<p>That is what one wants from a witness. A check that cannot notice the world changed is not a check;
it is a certificate of a world that no longer exists, and it signs in green.</p>
<p>AND THE PUBLICATION AXIS HAS A RULE OF ITS OWN, BECAUSE IT IS THE ONE THAT LOOKS ALREADY SOLVED:</p>
<p>A CHECK VERIFIES THE TREE THAT IS SERVED, NEVER THE TREE THAT IS WRITTEN.</p>
<p>A canonical tree and a served tree that disagree is A PUBLICATION THAT DID NOT HAPPEN, and the
disagreement is invisible to every existence check ever written, because they all ask &quot;does the file
exist?&quot; — a question with a true answer in the tree nobody reads. The artifact answers 200, sits in
the index, and is absent from the page. So the cheapest possible witness is also the one that was
missing: COMPARE THE TWO TREES, and fail when the served one is short.</p>
<p>AND THE DISK IS ITSELF A PROXY, WHICH IS WHY THE CHAIN DOES NOT END THERE. Comparing two trees on
disk closes a real gap, and not the last one: THE SERVER READS ITS INDEX INTO MEMORY AT STARTUP.
After the mirror ran and both trees said five, the grid still showed four, and only a restart fixed
it. A gate that stops at the filesystem certifies a publication no reader can see.</p>
<p>THE PAGE IS THE PUBLICATION. THE WITNESS AT THE END OF THE CHAIN ASKS: IS THE CASE ON THE PAGE?</p>
<p>And it asks it the expensive way, because the cheap way is what failed. IT COUNTS THE CARD, NOT THE
STRING: a case's id also lives in the page's nav, its meta tags and two JSON blobs — `testigo-ciego`
occurs FOUR TIMES before the first card ever opens — so a page-wide grep reports a case as present
while its card is missing. That grep is not hypothetical: it is the verification that reported &quot;5
cards&quot; over a grid rendering four. And ZERO CARDS IS A FINDING, NEVER AN ANSWER: a page that yields
no card cannot be distinguished from a card the check no longer knows how to read, and both are
findings.</p>
<p>The two questions are different and the chain must keep them apart. &quot;Is the disk right?&quot; is
answerable offline, always. &quot;Is the site right?&quot; REQUIRES A SITE — and where there is none the
answer is not green, it is `I DID NOT LOOK`. So the offline aggregate now ends by saying what it is
NOT: six gates green ON DISK is not proof of publication.</p>
<p>AND THE WITNESS ITSELF IS A MECHANISM, SO IT TOO GETS THE TREATMENT. To recognise a card it must
know the framework's markup — which is a coupling, and therefore an axis, and therefore the same
defect one level up: re-skin the grid and the recogniser silently matches nothing. A witness that
can only report &quot;I found no cards&quot; is reporting A MYSTERY, and a mystery is read as an empty grid,
which is read as health. That is this ADR's own pathology, wearing the ADR's own uniform.</p>
<p>So the marker is DECLARED (`card`, in the surface file — never a literal in the checker), and the
page is read TWICE, by two readings that adjudicate each other:</p>
<p>the MARKER reading knows the card class. Precise, and blinded by a re-skin.
the STRUCTURAL reading knows no class at all — it deletes what is not content (script, nav,
header, footer) and asks which case links survive. It CANNOT be re-skinned into silence,
because it never knew the skin.</p>
<p>When the marker finds nothing and the structure finds cases, THE PAGE IS FINE AND THE WITNESS IS
STALE — and now it can say exactly that, name the marker, and name the file that repairs it. The
sentence &quot;I do not recognise what I am watching&quot; is the third thing a check could not say, and it
is the one a check needs most, because it is the only one that indicts the check itself.</p>
<p>WHAT A CHECK MUST BE ABLE TO SAY, AND COULD NOT. Three sentences, and the protocol had a word for
none of them:</p>
<p>&quot;I did not look.&quot; — a source that fails to load is a FINDING, not an empty list. One
check returned zero findings BECAUSE IT COULD NOT LOOK, and that
is the worst possible green.
&quot;I cannot know this.&quot; — adr-071's `gateable` / `gate_source`. A rule a regex cannot
falsify must be marked uncheckable IN THE DATA, or the check
cries wolf and gets skipped.
&quot;I am no longer complete.&quot; — THIS ADR. The extent grew and my coverage did not.
&quot;I no longer recognise — THIS ADR, turned on the witness. The thing I watch was re-skinned
what I am watching.&quot; and I match nothing in it. Without this sentence the check reports
&quot;no cards found&quot;, which is a MYSTERY — and a mystery is read as an
empty grid, and an empty grid is read as health. It is the only one
of the four that indicts the CHECK rather than the world.</p>
<p>FALSIFY AGAINST THE NEW CAPABILITY, NOT THE OLD ONE. The gate above makes forgetting impossible; it
does not make the check CORRECT. Every one of the three blindings was caught the same way — by
falsifying: injecting `fr` into the languages, injecting `gate → puerta` beside `door → puerta`,
running the gate against the page just created. Where the falsification was run against the OLD
capability, the check stayed green and lied. So the constraint is not &quot;the check passes&quot; but &quot;the
check has been SEEN TO REFUSE the new capability's failure mode&quot;.</p>
<p>AND A CHECK MEASURES THE CAPABILITY, NOT ITS PROXY. &quot;Has a title key&quot; is a proxy for &quot;renders its
content&quot;, and it is satisfiable by adding fourteen strings while the page stays empty. A gate that
can be turned green without fixing anything is a gate that WILL be, and that is not a hypothesis:
it is what nearly happened, and the word used at the time was &quot;trivial&quot;.</p>
<h2>Constraints</h2>
<ul><li><strong>Hard</strong> A mechanism whose witness gates it MUST declare its EXTENT as data — the axes over which it exists (formats, levels, languages, locations, surfaces). An extent that lives only in a script is folklore, and a witness cannot be measured against folklore.</li><li><strong>Hard</strong> A check MUST declare its COVERAGE, and a gate MUST enforce coverage ⊇ extent. Growing an axis the witness does not read MUST break the build — not warn, not log, BREAK.</li><li><strong>Hard</strong> A check whose source fails to load MUST report a FINDING, never an empty result. Returning zero findings because you could not look is the worst available green.</li><li><strong>Hard</strong> A check extended for a new capability MUST be SEEN TO REFUSE that capability's failure mode. Passing is not evidence; refusing is. Every oracle MUST carry a negative case per axis it covers, and the negative case MUST be COMMITTED — a falsification performed by hand and then discarded leaves the next extension exactly as unprotected as the last one.</li><li><strong>Hard</strong> An expediente MUST exist in every language the project declares. `gen-expediente.nu --check` MUST read the declared languages and fail on a case that is missing one — and MUST be seen to refuse a language added to the extent that no case answers for.</li><li><strong>Hard</strong> A check that verifies a publication MUST verify the SERVED tree, not the canonical one. A canonical tree and a served tree that disagree is a publication that did not happen. The check MUST be seen to REFUSE a case present in the canonical tree and absent from the served one.</li><li><strong>Hard</strong> A publication is proven on the SERVED SURFACE, never on the filesystem. The witness MUST load the page a reader loads and assert the case is RENDERED — counting the rendered element, not an occurrence of its id in the markup. Its recogniser MUST be DECLARED as data, never a literal in the checker, and the page MUST be read a SECOND time by a reading that knows no markup — so that a re-skin is reported as a STALE WITNESS by name, and not as an empty grid. A page that cannot be fetched, a page in which neither reading finds anything, and two readings that disagree are all FINDINGS: none is an empty result and none is green.</li><li><strong>Soft</strong> A check SHOULD measure the capability, not a proxy for it. A gate that can be turned green without fixing anything WILL be.</li></ul>
<h2>Alternatives considered</h2>
<ul><li><strong>Write it in the howto: 'remember to extend the check when you extend the mechanism.'</strong><em>rejected:</em> Tried, this session, and disproved by the strongest possible case: the agent that broke the rule three times had AUTHORED it hours earlier, in an ADR, under its own name. If a rule cannot hold the person who wrote it, with the text still open, it holds nobody. Advisory prose is the failure mode, not the cure.</li><li><strong>Test coverage metrics — require every check to report a percentage.</strong><em>rejected:</em> A percentage of WHAT? The denominator is the extent, and the extent is precisely what nothing declares. A coverage number computed against the files that happen to exist reports 100% while an entire language, level or format is missing — which is exactly the number `gen-expediente.nu --check` was reporting when it said '8 cases reproduce'. Without a declared denominator, a coverage metric is the disease wearing a lab coat.</li><li><strong>Make the checks read the world dynamically — discover formats, levels and languages at runtime rather than declaring them.</strong><em>rejected:</em> This is what they already did, and it is the bug. A check that discovers its own extent from what it finds can never notice what is MISSING: it globs `*.toml`, finds no `.ncl`, and reports success. Discovery cannot distinguish 'there is nothing there' from 'I do not know how to look'. Only a declaration can, because only a declaration can be WRONG.</li><li><strong>Verify the canonical tree and trust the sync recipe to have run.</strong><em>rejected:</em> The recipe not having run IS the defect, so trusting it is assuming the conclusion. And the trust is not idle: it was held by every check on the page, all of which passed while the case was invisible, because the tree they trusted was the one the recipe reads FROM. A witness whose validity depends on a step it does not observe is witnessing the step it can see and signing for the one it cannot.</li><li><strong>Make the generator write straight into the served tree — delete the second tree and the problem with it.</strong><em>rejected:</em> It removes this instance and keeps the class, which is the trade this ADR exists to refuse. Two trees exist for a reason (adr-070: they are typed by OWNERSHIP — one is generated, one is assembled from several owners), and collapsing them to dodge a blind check re-creates the anti-pattern adr-070 closed. The defect was never that there were two trees; it was that the WITNESS did not know there were two. Fix the witness, not the world it failed to see.</li><li><strong>Fail the build on any change to a mechanism until every check is re-run.</strong><em>rejected:</em> Re-running a blind check produces a blind pass. All three blindings survived every subsequent run of the check — that is what made them blindings rather than failures. What must expire is not the RUN but the PROOF, and only a witness bound to an extent can expire.</li></ul>
<h2>Anti-patterns</h2>
<ul><li><strong>The Witness That Certified a World Which No Longer Existed</strong> — A check is written against the mechanism as it is, and it is CORRECT. Then the mechanism is EXTENDED — a new format, a new level, a new owner, a new language — and the check stays where it was. It does not fail. It does not warn. It looks where it always looked, finds nothing wrong there, and signs in green. The blinding is caused by the IMPROVEMENT, which is why it is invisible: the moment a witness is most likely to go blind is the moment you extend what it watches, and that is also the moment you are least likely to be looking at the witness. It happened three times in four hours to one witness, on three consecutive improvements, with zero warnings emitted.</li><li><strong>Zero Findings Because the Check Could Not Look</strong> — A check's source fails to load — an export dies, a path does not resolve, a format is unknown — and the check returns an EMPTY result rather than a finding. Zero findings is then reported as clean. This is the worst available green, because it is indistinguishable from health by every downstream reader, and because the failure that produced it is often the most interesting thing on the run. The sibling defect: a RESOLVER that returns a wrong answer instead of NO answer — `.canonicalize().unwrap_or_else(|_| &lt;the path that just failed&gt;)` — so a missing directory travels three layers and surfaces as a bare 'IO error' and a blank page.</li><li><strong>A Gate That Can Be Turned Green Without Fixing Anything</strong> — A check measures a PROXY for the capability instead of the capability. It asks 'does this route declare a title key?' when the thing that matters is 'does this route render its content?'. The proxy is cheap to satisfy — fourteen strings — and satisfying it turns the gate green while the real defect (304 declared Fluent keys never rendered, ten pages returning 200 over an empty body) is untouched. The tell is the word used to describe the fix: 'trivial'. A gate that can be satisfied trivially will be, and then it certifies the defect it was built to catch.</li><li><strong>The File Exists, in the Tree No One Reads</strong> — A generator writes to a canonical tree, a server reads a served tree, and a recipe mirrors one into the other. Run the generator alone and the artifact is generated, validated, activated and INDEXED — and it is not on the page. Every existence check passes, because every one of them asks 'does the file exist?' and that question HAS A TRUE ANSWER in the tree nobody serves. Nothing is missing and nothing errors; the two trees simply hold different worlds, dated a day apart. The tell is the contradiction that looks impossible: the article answers 200, its entry is in the index, and the grid does not show it. Existence is not a property of a file — it is a property of the tree that is READ, and a check that never names which tree that is has not checked publication at all.</li><li><strong>Writing It in the Guide and Calling It Fixed</strong> — A structural defect — one that recurs because of how the system is built, not because of how anyone behaved — is met with a documented rule: 'remember to update the check'. It reads as diligence and it changes nothing, because a rule that depends on remembering fails exactly when memory is loaded, which is exactly when the defect is created. The disproof available here is total: the agent that broke this rule three times had AUTHORED it that same afternoon, in an ADR, under its own name (`gate-green-over-nothing`), with the text open. If a rule cannot hold its own author, hours later, it holds nobody.</li></ul>
<h2>Related ADRs</h2>
<p><a href="/adr/071">ADR-071</a> · <a href="/adr/070">ADR-070</a> · <a href="/adr/066">ADR-066</a> · <a href="/adr/050">ADR-050</a></p>

View file

@ -0,0 +1,12 @@
{
id = "adr-072",
title = "A check declares its coverage, a mechanism declares its extent, and the witness carries the extent",
slug = "072",
excerpt = "",
tags = ["adr", "accepted"],
page_route = "/adr/072",
graph = {
implements = [],
related_to = ["adr-071", "adr-070", "adr-066", "adr-050"],
},
}

View file

@ -1,86 +0,0 @@
---
id: "adr-020"
title: "Three-Layer Model for Project Ontoref Instances"
slug: "020"
subtitle: "Proposed"
excerpt: "Multiple projects now adopt the ontoref protocol (lian-build is the explicit"
author: "ontoref"
date: "2026-05-03"
published: true
featured: false
category: "proposed"
tags: ["adr-020", "adr", "proposed"]
css_class: "category-adr"
---
<h2>Context</h2>
<p>Multiple projects now adopt the ontoref protocol (lian-build is the explicit
external case under bl-002 / bl-008). Field experience across ontoref +
lian-build + provisioning shows that an ontoref-onboarded project's
repository carries TWO distinct ontoref-shaped layers, while a third layer
exists outside the project (in caller repositories). Without codification,
adopters re-derive the model per project, mix layers accidentally, and lose
boundaries silently:</p>
<p>- Layer 3 content (caller-side cabling) drifts into a project's qa.ncl,
making the FAQ a how-to-deploy guide for one specific caller.
- Layer 2 schemas live under reflection/ as 'project notes', drifting
from the binary because they're not on the contract path.
- Layer 1 architectural rationale ends up in catalog/domains/&lt;id&gt;/
contract.ncl, where consumers expecting a typed shape get prose.</p>
<p>The model has been observed (lian-build/reflection/qa.ncl::lian-build-what-
and-why), described (ontoref/reflection/qa.ncl::ontoref-three-layer-model),
and queued for codification (bl-009). This ADR commits the model with
machine-checkable constraints. Acceptance is gated on the constraints
running clean across the existing ontoref-onboarded projects (ontoref
itself, lian-build, provisioning).</p>
<h2>Decision</h2>
<p>A project's ontoref instance has THREE distinct layers — but only the first
two live in the project's own repository:</p>
<p>LAYER 1 — Self-management ontoref (about the project itself)
paths .ontology/ reflection/ adrs/
audience this project's developers and maintainers
purpose describe the project to itself — axioms, FSM dimensions,
binding decisions, open questions, accepted knowledge
presence MANDATORY on every ontoref-onboarded project</p>
<p>LAYER 2 — Specialized domain/mode ontoref (the integration surface)
paths schemas/ catalog/{domains,modes}/
manifest.ncl::registry_provides
audience OTHER projects that want to integrate this project
purpose the contract surface other projects bind to — typed domain
artifacts, orchestration mode artifacts, registry-namespace
claim
presence OPTIONAL but BICONDITIONAL — a project either has all of
{schemas/, catalog/, registry_provides} or none. Half-Layer-2
is a contract violation.</p>
<p>LAYER 3 — Caller-side implementations (NOT in this project)
paths &lt;caller&gt;/extensions/&lt;this-project&gt;/
&lt;caller&gt;/catalog/components/&lt;...&gt;/ (when consuming)
&lt;workspace&gt;/infra/&lt;ws&gt;/integrations/
audience operators and CI of caller projects
presence PER CALLER, NEVER in this project's repo. Cross-references
from this project to Layer 3 are explicit pointers, never
copy-paste.</p>
<p>The three-layer axis is ORTHOGONAL to ADR-018's level hierarchy
(Base/Domain/Instance). A project at any level may have any combination of
Layer 1 (always) and Layer 2 (sometimes). Layer 3 is the boundary outward,
not a property of the project. The 3-layer × 3-level matrix is navigable in
both axes: 'where in the protocol hierarchy' (level) is independent of
'where in the project's repo' (layer).</p>
<p>Cross-layer references inside a project carry an explicit layer-N tag on
qa entries. A qa entry whose primary topic is a Layer N concern wears
the layer-N tag; satellite entries (operational how-to, troubleshooting)
inherit the layer of their anchor without re-tagging.</p>
<h2>Constraints</h2>
<ul><li><strong>Hard</strong> Every ontoref-onboarded project has .ontology/core.ncl</li><li><strong>Hard</strong> Every ontoref-onboarded project has reflection/qa.ncl</li><li><strong>Hard</strong> Every ontoref-onboarded project has reflection/backlog.ncl</li><li><strong>Hard</strong> Every ontoref-onboarded project has an adrs/ directory</li><li><strong>Hard</strong> A federated-peer catalog (catalog/domains/ or catalog/modes/) exists if and only if manifest.ncl declares registry_provides</li><li><strong>Soft</strong> qa entries whose primary topic is a Layer N concern carry the layer-N tag (anchors only; satellites inherit by association)</li></ul>
<h2>Alternatives considered</h2>
<ul><li><strong>Single 'ontoref content' namespace, no layering</strong><em>rejected:</em> Observed drift outcomes (Layer 3 in qa, Layer 2 schemas treated as notes, etc.) are caused precisely by absence of layering. The single-namespace alternative is what we're correcting; rejecting it is the entire decision.</li><li><strong>Two-layer model collapsing self-management with integration-surface</strong><em>rejected:</em> lian-build's adoption explicitly experienced the schema-vs-rationale split: schemas/build_directives.ncl (Layer 2 contract) and adrs/adr-001-lian-build-as-standalone.ncl (Layer 1 rationale) have different audiences, different change cadences, different validation rules. Collapsing them produced the FAQ-vs-contract confusion that retiring lian-build/.ontology/FAQ.md addressed. The two-layer alternative re-creates that confusion.</li><li><strong>Make Layer 3 (caller-side) optionally co-resident with Layer 2 in the producer's repo</strong><em>rejected:</em> Creates ambiguity about who owns the cabling. If a producer's repo carries `extensions/&lt;self&gt;/`, who maintains it when a caller's workspace evolves? The producer doesn't know about the caller's infrastructure; the caller can't depend on the producer to update the cabling on its schedule. Co-residence inverts the integration arrow.</li><li><strong>Numbered layers (Layer 1 / 2 / 3) vs named layers ('self' / 'integration-surface' / 'caller-side')</strong><em>rejected:</em> Named layers are more descriptive but verbose; numbered layers are more precise but flat. The compromise (this ADR): use 'Layer N — descriptive name' in prose, 'layer-N' in tags. Tag economy wins; prose retains the descriptive name.</li></ul>
<h2>Related ADRs</h2>
<p><a href="/adr/001">ADR-001</a> · <a href="/adr/016">ADR-016</a> · <a href="/adr/018">ADR-018</a></p>

View file

@ -1,12 +0,0 @@
{
id = "adr-020",
title = "Three-Layer Model for Project Ontoref Instances",
slug = "020",
excerpt = "",
tags = ["adr", "proposed"],
page_route = "/adr/020",
graph = {
implements = [],
related_to = ["adr-001", "adr-016", "adr-018"],
},
}

View file

@ -19,6 +19,7 @@ tags: ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"]
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-release-one-model-post.webp"
image_url: "/images/ontoref-release-one-model-post.webp"
og_image: "/images/og/ontoref-release-one-model-post.png"
# Display
read_time: "8 min read"

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflecti
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-ai-knowledge-post.webp"
image_url: "/images/ontoref-ai-knowledge-post.webp"
og_image: "/images/og/ontoref-ai-knowledge-post.png"
# Display
read_time: "7 min read"

View file

@ -18,6 +18,7 @@ tags: ["ontoref", "context-engineering", "library-science", "warrant", "knowledg
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-context-declared-not-filled.webp"
og_image: "/images/og/ontoref-context-declared-not-filled.png"
---
<picture>

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "dag", "ontology", "knowledge-graphs", "software-architecture"
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-dags-everywhere_post.webp"
image_url: "/images/ontoref-dags-everywhere_post.webp"
og_image: "/images/og/ontoref-dags-everywhere_post.png"
# Display
read_time: "6 min read"

View file

@ -25,6 +25,7 @@ category_published: true
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-one-protocol-multiple-subjects.webp"
og_image: "/images/og/ontoref-one-protocol-multiple-subjects.png"
---

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "ai-agents", "verification", "adr", "governance", "pep-kep"]
# Image (card thumbnail + social/feature)
thumbnail: "/images/ontoref-seven-sins-post.webp"
image_url: "/images/ontoref-seven-sins-post.webp"
og_image: "/images/og/ontoref-seven-sins-post.png"
# Display
read_time: "6 min read"

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "ai-agents", "governance", "verification", "witness", "work-or
# Image (card thumbnail + social/feature)
thumbnail: "/images/ontoref-trust-output-post.webp"
image_url: "/images/ontoref-trust-output-post.webp"
og_image: "/images/og/ontoref-trust-output-post.png"
# Display
read_time: "7 min read"

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "sovereignty", "federation", "radicle", "jujutsu", "knowledge-
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontology-live-with-code-post.webp"
image_url: "/images/ontology-live-with-code-post.webp"
og_image: "/images/og/ontology-live-with-code-post.png"
# Display
read_time: "6 min read"

View file

@ -19,6 +19,7 @@ tags: ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"]
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-release-one-model-post.webp"
image_url: "/images/ontoref-release-one-model-post.webp"
og_image: "/images/og/ontoref-release-one-model-post.png"
# Display
read_time: "8 min de lectura"
@ -56,7 +57,7 @@ La superficie de release vive en un único modelo NCL de flujo de trabajo
- `OnCommit` — hooks de pre-commit
- `OnPR` — comprobaciones por pull request
- `OnMainMerge` — integración al hacer merge a main
- `OnMainMerge` — integración al fusionar en main
- `OnTag` — la capa de release
Dentro de esas capas, el trabajo es tipado. Los build kinds llevan `'Bundle` y
@ -130,7 +131,7 @@ runner.
Esto es más barato y determinista: un índice multi-arch construido desde binarios
por-arch ya compilados, no ejecutando un builder amd64 bajo emulación arm64. Y es
un *constraint*, no una convención — el modelo prohíbe recompilar por arch para
una *restricción*, no una convención — el modelo prohíbe recompilar por arch para
construir la imagen, así que un futuro contribuidor que tire del familiar
`docker buildx --platform` queda frenado por el modelo en vez de publicar
silenciosamente un binario divergente.
@ -151,7 +152,7 @@ dependencia de *ejecución* — y `Protocolo, no Runtime` es un axioma invariant
modelo hace explícita la resolución en vez de confiar en ella. El instalador
`curl|sh` instala un CLI + capa de datos que funcionan **sin** el daemon (<a href="/adr/029" class="adr-ref">ADR-029</a>:
el daemon es una caché, no una dependencia dura); la imagen ejecutable es un
**acelerador opcional** para despliegues en contenedor. Un constraint tipado
**acelerador opcional** para despliegues en contenedor. Una restricción tipada
`daemon-image-optional-not-runtime` — vigila que un cambio futuro no pueda hacer el
daemon obligatorio sin avisar.

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflecti
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-ai-knowledge-post.webp"
image_url: "/images/ontoref-ai-knowledge-post.webp"
og_image: "/images/og/ontoref-ai-knowledge-post.png"
# Display
read_time: "7 min read"
@ -56,7 +57,7 @@ El sexto paso en su stack es "Govern — esto es infraestructura, stewardship, v
La pregunta real es: *¿quién asegura que la ontología no derive respecto al sistema que describe?*
El software evoluciona. Una decisión arquitectónica tomada en marzo invalida un nodo del grafo en octubre. Se incorpora un nuevo equipo con modelos mentales distintos. Una librería se reemplaza y un constraint se convierte en ficción. Los knowledge graphs acumulan deuda igual que los codebases — silenciosamente, sin alarmas.
El software evoluciona. Una decisión arquitectónica tomada en marzo invalida un nodo del grafo en octubre. Se incorpora un nuevo equipo con modelos mentales distintos. Una biblioteca se reemplaza y una restricción se convierte en ficción. Los knowledge graphs acumulan deuda igual que los codebases — silenciosamente, sin alarmas.
En el mundo enterprise de knowledge graphs, la respuesta al governance es headcount: contratar ontólogos, bibliotecarios, knowledge engineers. Funciona a escala organizacional con presupuestos dedicados. No es una respuesta viable para un proyecto de software, un entorno de infraestructura, o un individuo que intenta mantener auto-conocimiento estructurado.
@ -80,13 +81,14 @@ Este es el Yang al Yin de la ontología. Talisman describe el Yin con precisión
**Ontología (lo que ES):** nodos y aristas tipadas que representan prácticas, principios, tensiones, capacidades. No documentación — compromisos formales. Un nodo `Practice` que `implements` un `Principle`, `enforces` un `Constraint`, `enables` una `Capability`, y está en `tension` activa con otra `Practice`. El proyecto como knowledge graph.
**Reflection (lo que DEVIENE):** DAGs ejecutables — modos operacionales que corren contra el estado ontológico, detectan deriva, registran transiciones y propagan cambios. El comando `sync diff --docs` que detecta cuándo la documentación de un crate ha derivado de su nodo ontológico. La máquina de estados en `state.ncl` que registra dónde está cada dimensión del proyecto versus adónde pretende ir. El sistema de migraciones que asegura que los cambios de protocolo lleguen a cada proyecto consumidor.
**Reflexión (lo que DEVIENE):** DAGs ejecutables — modos operacionales que corren contra el estado ontológico, detectan deriva, registran transiciones y propagan cambios. El comando `sync diff --docs` que detecta cuándo la documentación de un crate ha derivado de su nodo ontológico. La máquina de estados en `state.ncl` que registra dónde está cada dimensión del proyecto versus adónde pretende ir. El sistema de migraciones que asegura que los cambios de protocolo lleguen a cada proyecto consumidor.
La tensión entre estas dos capas no es un defecto de diseño — está nombrada explícitamente en la ontología del propio proyecto como su identidad central:
<!-- lang:en -->
> *"Ontology captures what IS. Reflection captures what BECOMES. Both must coexist without one dominating."*
Sin Reflection, la Ontología cristaliza. Se convierte en una fotografía de lo que el proyecto quería ser en el momento en que se escribió. Sin Ontología, Reflection es ejecución sin verdad — sabe cómo operar pero no sobre qué opera.
Sin Reflexión, la Ontología cristaliza. Se convierte en una fotografía de lo que el proyecto quería ser en el momento en que se escribió. Sin Ontología, Reflexión es ejecución sin verdad — sabe cómo operar pero no sobre qué opera.
## Los números de precisión
@ -96,11 +98,11 @@ Los datos de Talisman sobre lo que el conocimiento estructurado hace a la precis
- GraphRAG vs. vector RAG: **3.4× precisión** en 43 queries enterprise (Diffbot KG-LM Benchmark, 2023)
- Vector RAG colapsa a **0% precisión más allá de cinco entidades por query**. El retrieval anclado en KG sostiene el rendimiento mucho más allá
La brecha de precisión no se cierra con un modelo más grande. Se cierra con un schema definido, una ontología, y una query validada.
La brecha de precisión no se cierra con un modelo más grande. Se cierra con un esquema definido, una ontología, y una query validada.
Pero solo si la ontología se mantiene viva. Solo si algo o alguien ejecuta el loop operacional que evita que derive hacia la ficción.
Esa es la parte que el framework de Talisman no provee. Para eso existe Reflection.
Esa es la parte que el framework de Talisman no provee. Para eso existe Reflexión.
---

View file

@ -18,6 +18,7 @@ tags: ["ontoref", "context-engineering", "library-science", "warrant", "knowledg
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-context-declared-not-filled-es.webp"
og_image: "/images/og/ontoref-context-declared-not-filled-es.png"
---
<picture>
@ -65,7 +66,7 @@ costura del testigo existe para rechazar.
## Convergencia, campo a campo
El detalle incómodo y satisfactorio: <a href="/about" class="brand-site">ontoref</a> nunca citó Z39.19. El schema de términos del
El detalle incómodo y satisfactorio: <a href="/about" class="brand-site">ontoref</a> nunca citó Z39.19. El esquema de términos del
glosario se diseñó desde las necesidades del propio protocolo — y convergió con el estándar
casi campo a campo.

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "dag", "ontology", "knowledge-graphs", "arquitectura-software"
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-dags-everywhere_post.webp"
image_url: "/images/ontoref-dags-everywhere_post.webp"
og_image: "/images/og/ontoref-dags-everywhere_post.png"
# Display
read_time: "6 min read"
@ -98,7 +99,7 @@ Tres cosas tuvieron que alinearse:
**Los agentes como consumidores.** Antes de la IA agéntica, no había ningún consumidor automatizado que necesitara conocimiento estructurado sobre un proyecto. Los humanos podían leer el wiki. Los agentes no pueden — necesitan conocimiento del proyecto tipado, consultable y machine-readable para trabajar con precisión en lugar de alucinar contexto. El DAG de conocimiento de <a href="/about" class="brand-site">ontoref</a> es lo que los agentes consumen vía MCP.
**Lenguajes de configuración con contratos.** Expresar una ontología sin un triplestore externo requería un lenguaje de configuración con tipos y contratos. Nickel (NCL) provee exactamente eso: un lenguaje de configuración tipado y lazy donde las violaciones de schema se detectan en tiempo de evaluación, no en tiempo de ejecución. RDF/OWL habría requerido infraestructura dedicada y expertise especializado.
**Lenguajes de configuración con contratos.** Expresar una ontología sin un triplestore externo requería un lenguaje de configuración con tipos y contratos. Nickel (NCL) provee exactamente eso: un lenguaje de configuración tipado y lazy donde las violaciones de esquema se detectan en tiempo de evaluación, no en tiempo de ejecución. RDF/OWL habría requerido infraestructura dedicada y expertise especializado.
**Escala de repositorio, no de empresa.** Los knowledge graphs enterprise son iniciativas de varios años. <a href="/about" class="brand-site">Ontoref</a> opera a la escala de un repositorio único — adopción incremental, sin enforcement, sin equipo dedicado. La unidad más pequeña que puede adoptarlo es un proyecto de uno.

View file

@ -25,6 +25,7 @@ category_published: true
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontoref-one-protocol-multiple-subjects-es.webp"
og_image: "/images/og/ontoref-one-protocol-multiple-subjects-es.png"
---
<picture>

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "agentes-ia", "verificacion", "adr", "gobernanza", "pep-kep"]
# Image (card thumbnail + social/feature)
thumbnail: "/images/ontoref-seven-sins-post-es.webp"
image_url: "/images/ontoref-seven-sins-post-es.webp"
og_image: "/images/og/ontoref-seven-sins-post-es.png"
# Display
read_time: "6 min de lectura"
@ -77,7 +78,7 @@ solos**:
llega a `Accepted` si no está citado por una *Proof* del posicionamiento. La
graduación exige evidencia **referencial**, comprobable por máquina, no la palabra
de nadie.
- **Contra el pecado #1 (validación soberbia).** Las *constraints* de cada ADR no son
- **Contra el pecado #1 (validación soberbia).** Las *restricciones* de cada ADR no son
prosa: son checks tipados —`Grep`, `Cargo`, `NuCmd`, `ApiCall`, `FileExists`— que el
modo `validate-project` dispara y que **fallan en rojo** si la realidad se desvía. No
puedes ajustar el test a la solución: el test *es* la solución declarada.
@ -97,7 +98,7 @@ solos**:
| Alternativas rechazadas obligatorias | sí | sí | sí (`alternatives_considered`, *required*) |
| Graduación por criterio pre-declarado | no | sí (humano) | **sí, witnessed y decidible** |
| Criterio acotado y verificable por máquina | no | no | **sí (slice + decidibilidad)** |
| Constraints ejecutables, no prosa | no | no | **sí (5 variantes tipadas)** |
| Restricciones ejecutables, no prosa | no | no | **sí (5 variantes tipadas)** |
| Cruce con invariantes del dominio | no | no | **sí (ondaod / `ontology_check`)** |
PEP y KEP son estándares maduros, y <a href="/about" class="brand-site">ontoref</a> hereda lo mejor de ambos. Pero cierra el

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "agentes-ia", "gobernanza", "verificacion", "testigo", "orden-
# Image (card thumbnail + social/feature)
thumbnail: "/images/ontoref-trust-output-post.webp"
image_url: "/images/ontoref-trust-output-post.webp"
og_image: "/images/og/ontoref-trust-output-post.png"
# Display
read_time: "7 min de lectura"

View file

@ -19,6 +19,7 @@ tags: ["ontoref", "soberania", "federacion", "radicle", "jujutsu", "infraestruct
# Image (card thumbnail + in-post featured)
thumbnail: "/images/ontology-live-with-code-post.webp"
image_url: "/images/ontology-live-with-code-post.webp"
og_image: "/images/og/ontology-live-with-code-post.png"
# Display
read_time: "6 min read"

View file

@ -1,5 +1,5 @@
<!-- generated from .ontoref/positioning/spine.ncl — wire into the home/Ontoref page component -->
# Pisa firme y anda ligero.
# Pisa firme, con paso ligero.
> Tu proyecto, tu infra, tu obra, tu vida: ¿sigues yendo a donde dijiste?

View file

@ -17,12 +17,6 @@ category_description: "Use cases and implementations of ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-authoring-en.avif" type="image/avif">
<source srcset="/images/ontoref-authoring-en.webp" type="image/webp">
<img src="/images/ontoref-authoring-en.webp" alt="Authoring domain — author your Work once and diffuse it everywhere without fragmenting" width="1536" height="1024" loading="lazy">
</picture>
**Author your Work once and diffuse it everywhere without it fragmenting. A Work — a book as archetype — is modeled as a coherent authored whole (a Central work with Satellites and Elements) carrying six typed content types; the same source projects to print, digital, web, interactive and an AI-queryable surface (RAG/MCP). Dissemination is an expression of the Work across channels, never a re-authoring per channel — so the Work stays coherent everywhere it appears.**
An authored Work lives as source files (Pages, ODT, Markdown). Each medium —

View file

@ -17,12 +17,6 @@ category_description: "Use cases and implementations of ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-developer-en.avif" type="image/avif">
<source srcset="/images/ontoref-developer-en.webp" type="image/webp">
<img src="/images/ontoref-developer-en.webp" alt="Developer domain — keep AI-written code on course against declared intent" width="1536" height="1024" loading="lazy">
</picture>
> **AI writes faster and breaks more: +861% churn, ×3.4 incidents per PR, 31.3% of PRs merged without a single review. Team maturity does not protect you.**
>
> — [Faros · AI Engineering Report 2026 — The Acceleration Whiplash](https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways) · [See what breaks](/blog?tag=developer)

View file

@ -17,12 +17,6 @@ category_description: "Use cases and implementations of ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-infra-en.avif" type="image/avif">
<source srcset="/images/ontoref-infra-en.webp" type="image/webp">
<img src="/images/ontoref-infra-en.webp" alt="Infrastructure domain — your infrastructure on its own trajectory, not just up or down" width="1536" height="1024" loading="lazy">
</picture>
**Your infrastructure tells you where it is on its own trajectory — not just whether resources match. `describe state` returns each dimension's current → desired with the blocker and catalyst between them; `describe connections` maps how one action depends on the next. The infra is structured as a DAG that carries sense, journey, and perspective, with declared actions that reproduce — so HA and resilience rise to another plane: a property of a system that knows its own state and how it got there, not redundancy bolted on.**
The operator who inherits infrastructure cannot see its trajectory.

View file

@ -17,12 +17,6 @@ category_description: "Use cases and implementations of ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-personal-en.avif" type="image/avif">
<source srcset="/images/ontoref-personal-en.webp" type="image/webp">
<img src="/images/ontoref-personal-en.webp" alt="Personal domain — model your career and commitments as a graph with a declared desired state" width="1536" height="1024" loading="lazy">
</picture>
**See where your effort drifts from your intent. Model your career and commitments as a graph with a declared desired state; `describe state` shows current → desired and what blocks the next step, and the graph surfaces which commitments move you toward where you said you want to go — and which are accidental. Your own trajectory becomes queryable: what have I done, what connects, what should I do next — answered, not felt. You don't end up where you didn't choose.**
A person's career, content, decisions, and intentions live as scattered

View file

@ -17,12 +17,6 @@ category_description: "Casos de uso e implementaciones de ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-authoring-es.avif" type="image/avif">
<source srcset="/images/ontoref-authoring-es.webp" type="image/webp">
<img src="/images/ontoref-authoring-es.webp" alt="Dominio de autoría — crea tu Obra una vez y difúndela en todas partes sin fragmentarla" width="1536" height="1024" loading="lazy">
</picture>
**Author your Work once and diffuse it everywhere without it fragmenting. A Work — a book as archetype — is modeled as a coherent authored whole (a Central work with Satellites and Elements) carrying six typed content types; the same source projects to print, digital, web, interactive and an AI-queryable surface (RAG/MCP). Dissemination is an expression of the Work across channels, never a re-authoring per channel — so the Work stays coherent everywhere it appears.**
An authored Work lives as source files (Pages, ODT, Markdown). Each medium —

View file

@ -17,12 +17,6 @@ category_description: "Casos de uso e implementaciones de ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-developer-es.avif" type="image/avif">
<source srcset="/images/ontoref-developer-es.webp" type="image/webp">
<img src="/images/ontoref-developer-es.webp" alt="Dominio de desarrollo — mantén el código escrito por IA en rumbo frente a la intención declarada" width="1536" height="1024" loading="lazy">
</picture>
> **El AI escribe más rápido y rompe más: +861% de churn, ×3.4 incidentes por PR, 31.3% de PRs fusionados sin una sola revisión. La madurez del equipo no protege.**
>
> — [Faros · AI Engineering Report 2026 — The Acceleration Whiplash](https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways) · [Comprueba qué se rompe](/blog?tag=developer)

View file

@ -17,12 +17,6 @@ category_description: "Casos de uso e implementaciones de ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-infra-es.avif" type="image/avif">
<source srcset="/images/ontoref-infra-es.webp" type="image/webp">
<img src="/images/ontoref-infra-es.webp" alt="Dominio de infraestructura — tu infraestructura en su propia trayectoria, no solo arriba o abajo" width="1536" height="1024" loading="lazy">
</picture>
**Your infrastructure tells you where it is on its own trajectory — not just whether resources match. `describe state` returns each dimension's current → desired with the blocker and catalyst between them; `describe connections` maps how one action depends on the next. The infra is structured as a DAG that carries sense, journey, and perspective, with declared actions that reproduce — so HA and resilience rise to another plane: a property of a system that knows its own state and how it got there, not redundancy bolted on.**
The operator who inherits infrastructure cannot see its trajectory.

View file

@ -17,12 +17,6 @@ category_description: "Casos de uso e implementaciones de ontoref"
category_published: true
---
<picture>
<source srcset="/images/ontoref-personal-es.avif" type="image/avif">
<source srcset="/images/ontoref-personal-es.webp" type="image/webp">
<img src="/images/ontoref-personal-es.webp" alt="Dominio personal — modela tu carrera y compromisos como un grafo con un estado deseado declarado" width="1536" height="1024" loading="lazy">
</picture>
**See where your effort drifts from your intent. Model your career and commitments as a graph with a declared desired state; `describe state` shows current → desired and what blocks the next step, and the graph surfaces which commitments move you toward where you said you want to go — and which are accidental. Your own trajectory becomes queryable: what have I done, what connects, what should I do next — answered, not felt. You don't end up where you didn't choose.**
A person's career, content, decisions, and intentions live as scattered

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -123,4 +123,8 @@ auth-email-otp-subject = Your verification code
auth-email-otp-title = Your verification code
auth-email-otp-subtitle = Use this code to sign in:
auth-email-otp-expire-msg = This code expires in %minutes% minutes.
auth-email-otp-ignore-msg = If you didn't request this, you can safely ignore this email.
auth-email-otp-ignore-msg = If you didn't request this, you can safely ignore this email.
# <head> SEO for the /login route (LoginPage → login). Lives here because every login
# string does; Fluent merges the locale's files into one bundle.
login-page-title = Sign In
login-page-description = Sign in to your account.

View file

@ -22,3 +22,9 @@ common-spanish = Spanish
# Image viewer
common-expand-image = Expand
common-close = Close
# Footer. The column holds the two legal pages and the note on language; the latter obliges
# nobody — it is a style contract — so the column cannot be called "Legal" without misdescribing
# what is in it.
footer-legal-title = Terms & style
footer-social-title = Connect

View file

@ -6,4 +6,25 @@ nav-about-us = About Us
# Page Metadata
about-us-page-title = About Us
about-us-page-subtitle = The people and the practice behind Ontoref.
about-us-page-description = Ontoref is designed and maintained by Jesús Pérez, a Rust developer and provisioning-systems architect with over three decades in software. It grows from hands-on work with self-hosted, open-source infrastructure and from a conviction that systems should carry their own reasoning: an explicit ontology, recorded decisions, and disciplined reflection. We build small, sovereign tools that organizations can truly own and control, and we share the protocol and its practice openly with a growing community of collaborators.
about-us-page-description = Ontoref is designed and maintained by <a href="https://jesusperez.pro" class="ds-link" rel="me noopener" target="_blank">Jesús Pérez</a>, a Rust developer and provisioning-systems architect with over three decades in software. It did not start on a blank page: it comes from years of hands-on work with self-hosted, open-source infrastructure, and from a conviction that fits in one sentence — a system must carry its own reasoning.
# Who
about-us-h-quien = Who is behind it
about-us-quien-1 = One person, and the open work of a growing community. Authorship matters here for a concrete reason: every practice the protocol imposes exists because its absence once cost something. Ontoref is distilled from real systems that drifted, not designed on paper.
about-us-quien-2 = The craft is the ordinary one — Rust for what executes, Nickel for what is declared, Nushell for what orchestrates — with the discipline of someone who has had to keep in production what he wrote ten years ago.
# Why
about-us-h-porque = Why ontoref exists
about-us-porque-1 = Because a project's knowledge lives where nobody can query it: in the head of whoever built it, in a chat thread, in a README that already lies. When that person leaves — or when an AI agent writes half the code — nothing is left to answer the question that matters: <em>why is this the way it is, and what breaks if I change it?</em>
about-us-porque-2 = Ontoref's answer is to declare the reasoning: axioms, tensions, practices, state and gates as typed NCL — queryable and verifiable. A protocol, never a runtime dependency. What it actually does is laid out in <a href="/about" class="ds-link">About ontoref</a>.
# How
about-us-h-como = How we work
about-us-como-1 = By an uncomfortable rule: <b>a check nobody has seen refuse is not a check</b>. Before trusting a gate you must falsify it — break, on purpose, the thing it should catch, and confirm that it names it. If you have only ever seen it pass, all you know is that it can say yes.
about-us-como-2 = And by a second one: failures get published. When something drifts we open a case file — the failure, its root cause, and what stayed in the repository so it cannot happen again. They are public, named and dated, in <a href="/case-files" class="ds-link">Case Files</a>. Architecture decisions, with their live constraints, in <a href="/adr" class="ds-link">ADR</a>.
about-us-como-3 = This is not transparency as a gesture. A system that records its own drifts is a system that someone who was not there can audit — which is, precisely, the problem ontoref sets out to solve.
# Open
about-us-h-abierto = Open and sovereign
about-us-abierto-1 = Small tools an organization can genuinely own and control: no SaaS in the middle, no hostages, protocol and practice shared in the open. You enter with a single NCL file and climb tiers only when it hurts.
about-us-abierto-2 = If any of this resonates — or if you think it is wrong and want to say so — <a href="/contact" class="ds-link">get in touch</a>.

File diff suppressed because one or more lines are too long

View file

@ -62,7 +62,6 @@ contact-get-in-touch = Get In Touch
# URLs for navigation
contact-blog-url = /blog
contact-prescriptions-url = /prescriptions
contact-work-request-url = /work-request
contact-services-url = /services
# Contact form labels and placeholders
@ -75,3 +74,5 @@ contact-subject-placeholder = Brief description of your inquiry
contact-message-label = Message
contact-message-placeholder = Tell me about your project, goals, timeline, and any specific requirements...
contact-submit-success = Your message has been sent! I'll get back to you within 24 hours.
contact-work-request-url = /work-request

View file

@ -3,3 +3,8 @@ content-graph-ontology-context = Ontology context
content-graph-title = Knowledge Graph
content-graph-subtitle = An interactive map of all content, ontology nodes, and architectural decisions — and how they connect.
content-graph-hint = Click a node to navigate. Scroll to zoom. Drag to pan. Content nodes link to their page.
# <head> SEO. The key prefix is the route component minus its Page suffix, kebab-cased
# (ContentGraphPage → content-graph) — see build-config/src/site_config.rs::ftl_page_prefix.
content-graph-page-title = Knowledge Graph
content-graph-page-description = An interactive map of all content, ontology nodes, and architectural decisions — and how they connect.

View file

@ -8,7 +8,7 @@ home-page-keywords = ontology, reflection, protocol, rust, mcp, nickel, knowledg
# stays hand-maintained. Dead home-hero-* / home-cta-adopt / home-tagline-door-
# {developer,infrastructure,personal} removed 2026-06-20 (template renders spine-*
# and spine-door-*-label instead).
home-hero-badge = Operational ontology, with reflection · v0.1.7
home-hero-badge = Operational ontology, with reflection · v0.1.8
# Hero tagline rotator — only the 'all' door label is hand-maintained here.
home-tagline-door-all = All
@ -50,11 +50,30 @@ home-how-step3-desc = Start the daemon for HTTP UI and MCP. Configure via ~/.con
# Architecture
home-arch-title = Architecture
home-arch-img-light = /images/ontoref-architecture.svg
home-arch-img-dark = /images/projects/ontoref_architecture-dark.svg
home-arch-caption = Protocol layers — entry point → declarative NCL → operational Nushell → optional Rust runtime. Each layer is independently testable.
# The SVG is the hook: it shows the layers and their live counts, but the 171 edges it declares are
# a number inside a box — it draws 5 arrows. The graph draws all of them. This is the line the hook
# was missing (ADR-057 hook-static / prize-live, route-to-live-graph).
# The target is NOT invented here: it is the `live_view` declared in .ontoref/positioning/about.ncl,
# the same one /about's graph block routes to. One source, two surfaces.
home-arch-live-href = /images/ontoref-graph.html
home-arch-live-label = Explore the live graph — 60 nodes, 171 edges →
# CTA
home-cta-title = Structure Your Project's Knowledge
home-cta-desc = Install the CLI, run ontoref setup, and your project gains machine-queryable invariants, living ADRs, actor-aware operational modes, and optional daemon context sharing.
home-cta-primary = Get Started
home-cta-secondary = Read the Protocol
# CTA destinations. They are KEYS, not literals in the template: /about is /acerca-de in Spanish,
# and a hardcoded href would send every Spanish reader to the English URL. Both primary CTAs used to
# point at /blog — "explore the protocol" delivered a list of posts.
home-cta-explore-url = /about
home-cta-primary-url = /about
home-cta-secondary-url = /blog
# Alt text for the hero illustration. It is NOT a repeat of the headline — the headline is now
# live text in the <h1> above it. What this alt must carry is the only text left inside the
# raster: the four value labels baked around the octagon. They exist nowhere else in the
# codebase, so without this line they are invisible to a screen reader and to every crawler.
home-hero-art-alt = ontoref: a lit path climbing to the ontoref octagon, ringed by four value nodes — Understand before you change · Verify every change · One knowledge for every actor · Tools, agents and CI aligned.

View file

@ -0,0 +1,4 @@
# GENERATED from .ontoref/positioning/taglines.ncl by gen-taglines.nu — do not edit by hand.
# The rotator's idx-0 frame, rendered server-side so the hero does not flash (or, with JS
# off, sit on) a phrase the rotator was going to replace anyway.
home-tagline-initial = A project, an infrastructure, a life: are you still going where you said you were going?

View file

@ -0,0 +1,55 @@
# A note on language — English
# GENERATED from .ontoref/ontology/registry.ncl by scripts/gen-nota-lenguaje.nu. Do not edit.
lenguaje-page-title = A note on language
lenguaje-page-subtitle = Why the Spanish pages here say «haz un commit» and not «confirma los cambios»
lenguaje-page-description = The criterion deciding which term is translated and which is not — declared, versioned, and enforced by CI.
lenguaje-page-keywords = language, terminology, translation, spanish, glossary
lenguaje-intro = Writing about software in Spanish has no settled convention. No authority dictates it, the big style guides contradict each other at the edges, and every team ends up deciding text by text. So did we. This is what we decided instead.
lenguaje-h-criterio = The criterion
lenguaje-criterio-lead = A word stays in English for two DIFFERENT reasons. Conflating them is easy, and it is the mistake that made us rewrite this policy from scratch.
lenguaje-criterio-1-t = One: it is an identifier.
lenguaje-criterio-1 = It appears verbatim in the command line, in an API, in an error message you are about to paste into a search box. Translating it breaks the REFERENCE: the text reads well and is useless, because you can no longer get from it to the tool. This holds in every language — it is not an opinion about Spanish.
lenguaje-criterio-2-t = Two: there is no faithful word.
lenguaje-criterio-2 = No Spanish word carries the concept without displacing it. Translating it breaks the CONCEPT. «Compuerta» sends it to electronics; «puerta» already means something else here; «umbral» loses the typed policy; «punto de control» loses that it BLOCKS. Every candidate looks reasonable and each loses something different. This one IS a claim about Spanish, and in another language it might be false.
lenguaje-criterio-3 = And if Spanish does have the word, we use it. A term that happens to resemble a directory name is not thereby untranslatable.
lenguaje-criterio-run = It also settles the question that seemed to have no answer. «How do you say run in Spanish?» has none BECAUSE IT IS NOT A TERM: it is an ordinary English word. But a Run with an id and a log IS an entity, and it stays. We were looking for one translation of two different things; that is why it never turned up.
lenguaje-h-kept = What we do not translate
lenguaje-kept-lead = These stay in English, and the last column says by which of the two roads.
lenguaje-th-termino = Term
lenguaje-th-que-es = What it is
lenguaje-th-porque-queda = Why it stays
lenguaje-h-rendered = What we do translate
lenguaje-rendered-lead = Spanish carries the whole concept. There is nothing to preserve.
lenguaje-th-en-ingles = In English
lenguaje-th-decimos = We write
lenguaje-h-verbos = The verbs, which are the real problem
lenguaje-verbos-1 = This is where everyone gets stuck, and the fix is grammatical, not a matter of taste.
lenguaje-verbos-2 = A borrowed NOUN is harmless: it enters Spanish, picks a gender, and is done.
lenguaje-verbos-3 = A borrowed VERB must pick a conjugation — and in Spanish that is -ear, which drags a colloquial register in with it. That is why «commitear» grates and «commit» does not: it is not the loanword, it is the MORPHOLOGY.
lenguaje-verbos-4 = The way out is a construction Spanish already uses productively: LIGHT SPANISH VERB + ENGLISH NOUN. «Hacer un commit». Spanish supplies the grammar; the loanword stays a noun and keeps pointing at the artifact.
lenguaje-th-no-escribimos = We do not write
lenguaje-th-escribimos = We write
lenguaje-th-porque = Why
lenguaje-h-friends = And the ones that do not grate — the truly dangerous
lenguaje-friends-1 = A calque announces itself. A FALSE FRIEND does not: it looks like the right translation, reads fluently, and displaces the meaning unnoticed. «Librería» is a SHOP; the word is «biblioteca». Languages evolve their cognates apart, and that drift is invisible precisely because the word still looks like its source.
lenguaje-friends-2 = Others are true cognates borrowed from ANOTHER discipline: «compuerta» comes from electronics, and drags a logic-circuit reading into a sentence about state transitions.
lenguaje-h-hablando = Speech is another matter
lenguaje-hablando-1 = None of the above applies to a conversation. A calque spoken communicates perfectly and is jargon on the page. The policy says exactly that: denied in writing, permitted in the mouth. A rule that pretended otherwise would be lying about how its own authors speak, and nobody obeys a rule that lies to them.
lenguaje-hablando-2 = A false friend, by contrast, misleads a listener exactly as much as a reader. That one is always flagged.
lenguaje-hablando-3 = The same goes for pronunciation. Saying «ráuter» inside a Spanish sentence is not ignorance: it is phonological accommodation, and every borrowing language does it.
lenguaje-h-legal = This is not a legal notice
lenguaje-legal-1 = The page next door is the privacy policy, and it exists because the law compels it. This one does not. This is a STYLE CONTRACT: the right analogy is the licence or the code of conduct — normative, published, versioned, referenced.
lenguaje-legal-2 = With one difference from either: this one is EXECUTED.
lenguaje-legal-3 = The tables you just read are not hand-written. They are generated from the same declared file that checks every Spanish text on this site before it ships, and that fails the build if it finds a word that should not be there — or if this page stops matching the rule it claims to explain.
lenguaje-legal-4 = That a project's language policy is itself a declared, versioned and verified artifact is not bureaucracy. It is the project's thesis applied to itself.
lenguaje-foot = This page is a projection of the project's lexical policy. If it contradicts what you read elsewhere on the site, that is a bug and we want to know.

View file

@ -1,7 +1,7 @@
ontoref-adoption-subtitle = ontoref setup wires up any new or existing project — idempotent scaffold with optional auth key bootstrap.
ontoref-adoption-title = Adopt in Any Project
ontoref-architecture-title = Architecture
ontoref-badge = Protocol + Runtime · v0.1.0
ontoref-badge = Protocol + Runtime · v0.1.8
ontoref-crates-title = Crates & Tooling
ontoref-cta-explore = Explore the Protocol
ontoref-cta-subtitle = Start with ontoref setup. Your project gains machine-queryable invariants, living ADRs, actor-aware operational modes, and a daemon that shares context across every actor in real time.

View file

@ -74,3 +74,7 @@ rustelo-tech-title = Built on Proven Foundations
# Bottom CTA
rustelo-cta-title = Ready to build with Rust?
rustelo-cta-subtitle = Open source · Self-hosted · Zero unsafe · 100% Rust
# <head> SEO (RusteloPage → rustelo).
rustelo-page-title = Rustelo — Full-Stack Rust Web Framework
rustelo-page-description = Modular, type-safe Rust platform built on Leptos and Axum. Configuration-driven architecture with language-agnostic routing, plugin system, and dual-target WASM+native compilation.

View file

@ -76,7 +76,7 @@ services-tier2-card-desc = Ed25519 witnesses and replay determinism: every chang
# ── CTA ─────────────────────────────────────────────────────────────────────
services-cta-title = Where should you start?
services-cta-desc = Tell us your tier and your stack — we will point to the smallest first step, whether that is a coaching session or a tier-2 engagement.
services-cta-primary = Start a conversation
services-cta-primary-url = /contact
services-cta-primary = Request Services
services-cta-primary-url = /work-request
services-cta-secondary = See what ontoref is
services-cta-secondary-url = /about

View file

@ -6,7 +6,7 @@ work-request-page-description = Submit a work request for your project
work-request-page-keywords = work, request, consultation, project
# Page Header
work-request-title = Request a Project Quote
work-request-title = Request a Service Quote
work-request-subtitle = Let's discuss your project requirements and how I can help you achieve your goals.
work-request-description = Fill out the form below with your project details, and I'll get back to you within 24 hours with a personalized proposal.
@ -130,7 +130,7 @@ work-request-schedule-call = Schedule Call
work-request-form-action = mailto:hello@ontoref.dev
# Missing keys needed by the component
work-request-project-type-label = Project Type
work-request-project-type-label = Service Type
work-request-project-type-placeholder = Select project type
work-request-project-type-new = New Project
work-request-project-type-existing = Existing Project Enhancement

View file

@ -123,4 +123,8 @@ auth-email-otp-subject = Tu código de verificación
auth-email-otp-title = Tu código de verificación
auth-email-otp-subtitle = Usa este código para iniciar sesión:
auth-email-otp-expire-msg = Este código expira en %minutes% minutos.
auth-email-otp-ignore-msg = Si no solicitaste esto, puedes ignorar este correo.
auth-email-otp-ignore-msg = Si no solicitaste esto, puedes ignorar este correo.
# <head> SEO de la ruta /login (LoginPage → login). Vive aquí porque aquí viven todas las
# cadenas de login; Fluent une los ficheros de cada locale en un solo bundle.
login-page-title = Iniciar Sesión
login-page-description = Inicia sesión en tu cuenta.

View file

@ -22,3 +22,9 @@ common-spanish = Español
# Visor de imagen
common-expand-image = Ampliar
common-close = Cerrar
# Pie de página. La columna reúne las dos páginas legales y la nota sobre el idioma; esta
# última no obliga a nada, es un contrato de estilo, así que la columna no puede llamarse
# "Legal" sin mentir sobre lo que contiene.
footer-legal-title = Condiciones y estilo
footer-social-title = Redes Sociales

View file

@ -6,4 +6,25 @@ nav-about-us = Nosotros
# Metadatos de Página
about-us-page-title = Nosotros
about-us-page-subtitle = Las personas y la práctica detrás de Ontoref.
about-us-page-description = Ontoref es diseñado y mantenido por Jesús Pérez, desarrollador Rust y arquitecto de sistemas de aprovisionamiento con más de tres décadas en software. Nace del trabajo directo con infraestructura auto-hospedada y de código abierto, y de la convicción de que los sistemas deben portar su propio razonamiento: una ontología explícita, decisiones registradas y una reflexión disciplinada. Construimos herramientas pequeñas y soberanas que las organizaciones pueden poseer y controlar de verdad, y compartimos el protocolo y su práctica de forma abierta con una comunidad creciente de colaboradores.
about-us-page-description = Ontoref lo diseña y lo mantiene <a href="https://jesusperez.pro" class="ds-link" rel="me noopener" target="_blank">Jesús Pérez</a>, desarrollador Rust y arquitecto de sistemas de aprovisionamiento con más de tres décadas en software. No sale de una hoja en blanco: sale de años de trabajo con infraestructura auto-hospedada y de código abierto, y de una convicción que cabe en una frase — un sistema debe portar su propio razonamiento.
# Quién
about-us-h-quien = Quién está detrás
about-us-quien-1 = Una persona, y el trabajo abierto de una comunidad que crece. La autoría importa aquí por una razón concreta: cada práctica que el protocolo impone existe porque su ausencia costó algo. Ontoref es el destilado de sistemas reales que derivaron, no un diseño sobre el papel.
about-us-quien-2 = El oficio es el de siempre — Rust para lo que se ejecuta, Nickel para lo que se declara, Nushell para lo que se orquesta — con la disciplina de quien ha tenido que sostener en producción lo que escribió hace diez años.
# Por qué
about-us-h-porque = Por qué existe ontoref
about-us-porque-1 = Porque el conocimiento de un proyecto vive donde nadie puede consultarlo: en la cabeza de quien lo construyó, en un hilo de chat, en un README que ya miente. Cuando esa persona se va — o cuando un agente de IA escribe la mitad del código — no queda nada que responda a la pregunta que importa: <em>¿por qué esto es así, y qué se rompe si lo cambio?</em>
about-us-porque-2 = La respuesta de ontoref es declarar el razonamiento: axiomas, tensiones, prácticas, estado y gates como NCL tipado, consultable y verificable. Un protocolo, nunca una dependencia en tiempo de ejecución. Lo que hace, en detalle, está en <a href="/acerca-de" class="ds-link">Acerca de ontoref</a>.
# Cómo
about-us-h-como = Cómo trabajamos
about-us-como-1 = Con una regla incómoda: <b>una verificación que nadie ha visto negarse no es una verificación</b>. Antes de confiar en una gate hay que falsificarla — romper a propósito lo que debería detectar, y comprobar que lo nombra. Si sólo la has visto pasar, lo único que sabes es que sabe decir que sí.
about-us-como-2 = Y con una segunda: los fallos se publican. Cuando algo deriva se levanta expediente — el fallo, su causa raíz y lo que quedó en el repositorio para que no vuelva. Están abiertos, con nombre y fecha, en <a href="/expedientes" class="ds-link">Expedientes</a>. Las decisiones de arquitectura, con sus restricciones vivas, en <a href="/adr" class="ds-link">ADR</a>.
about-us-como-3 = No es transparencia como gesto. Un sistema que registra sus propias derivas es un sistema que puede auditar quien no estuvo allí — que es, exactamente, el problema que ontoref intenta resolver.
# Abierto
about-us-h-abierto = Abierto y soberano
about-us-abierto-1 = Herramientas pequeñas que una organización puede poseer y controlar de verdad: sin SaaS de por medio, sin rehenes, con el protocolo y su práctica compartidos en abierto. Se entra con un solo fichero NCL y se sube de nivel sólo cuando duele.
about-us-abierto-2 = Si algo de esto te resuena — o si crees que está equivocado y quieres decirlo — <a href="/contacto" class="ds-link">escríbenos</a>.

File diff suppressed because one or more lines are too long

View file

@ -62,7 +62,6 @@ contact-get-in-touch = Ponte en Contacto
# URLs para navegación
contact-blog-url = /blog
contact-prescriptions-url = /prescripciones
contact-work-request-url = /solicitud-trabajo
contact-services-url = /servicios
# Etiquetas y marcadores de posición del formulario de contacto
@ -75,3 +74,5 @@ contact-subject-placeholder = Breve descripción de tu consulta
contact-message-label = Mensaje
contact-message-placeholder = Cuéntame sobre tu proyecto, objetivos, cronograma, y cualquier requisito específico...
contact-submit-success = ¡Tu mensaje ha sido enviado\! Me pondré en contacto contigo en 24 horas.
contact-work-request-url = /solicitud-trabajo

View file

@ -3,3 +3,8 @@ content-graph-ontology-context = Contexto ontológico
content-graph-title = Grafo de Conocimiento
content-graph-subtitle = Un mapa interactivo de todo el contenido, nodos ontológicos y decisiones arquitectónicas — y cómo se conectan.
content-graph-hint = Haz clic en un nodo para navegar. Desplázate para hacer zoom. Arrastra para mover. Los nodos de contenido enlazan a su página.
# <head> SEO. El prefijo de clave es el componente de ruta sin su sufijo Page, en kebab-case
# (ContentGraphPage → content-graph) — ver build-config/src/site_config.rs::ftl_page_prefix.
content-graph-page-title = Grafo de Conocimiento
content-graph-page-description = Un mapa interactivo de todo el contenido, nodos ontológicos y decisiones arquitectónicas — y cómo se conectan.

View file

@ -8,7 +8,7 @@ home-page-keywords = ontología, reflexión, protocolo, rust, mcp, nickel, grafo
# se mantiene el badge a mano. Claves muertas home-hero-* / home-cta-adopt /
# home-tagline-door-{developer,infrastructure,personal} eliminadas 2026-06-20
# (la plantilla renderiza spine-* y spine-door-*-label).
home-hero-badge = Ontología operacional con reflexión · v0.1.7
home-hero-badge = Ontología operacional con reflexión · v0.1.8
# Rotador de taglines — aquí solo se mantiene la etiqueta de la puerta 'all'.
home-tagline-door-all = Todo
@ -20,7 +20,7 @@ home-cta-github = Código
# proyecto ES y lo que deja verificable); nunca reifica "el grafo" como el que da
# — ver .coder/2026-06-20-home-positioning-projection.plan.md (ondaod).
home-pillars-title = Lo que puede hacer un proyecto que se conoce
home-pillars-subtitle = Tres capacidades desde un único substrato tipado — pregunta qué se rompe, comprueba quién lo cambió, alcánzalo desde cualquier actor. Independiente de cualquier runtime.
home-pillars-subtitle = Tres capacidades desde un único substrato tipado — pregunta qué se rompe, comprueba quién lo cambió, alcánzalo desde cualquier actor. Independiente de cualquier tiempo de ejecución.
home-pillar-impact-label = PREGUNTA ANTES DE TOCAR
home-pillar-impact-title = El radio de impacto, antes del diff
@ -42,7 +42,7 @@ home-how-step1-title = ontoref setup
home-how-step1-desc = Genera el scaffold de .ontology/ y reflection/ con ficheros NCL tipados. Idempotente — seguro de re-ejecutar tras actualizaciones.
home-how-step2-title = ontoref describe project
home-how-step2-desc = Consulta la identidad, axiomas, tensiones y estado FSM de tu proyecto desde el terminal. Actor-agnostic — misma salida para developer, agente o CI.
home-how-step2-desc = Consulta la identidad, axiomas, tensiones y estado de la máquina de estados de tu proyecto desde el terminal. Actor-agnostic — misma salida para developer, agente o CI.
home-how-step3-title = ontoref run start
home-how-step3-desc = Arranca el daemon para UI HTTP y MCP. Configurable via ~/.config/ontoref/config.ncl. El protocolo funciona sin él.
@ -50,11 +50,31 @@ home-how-step3-desc = Arranca el daemon para UI HTTP y MCP. Configurable via ~/.
# Arquitectura
home-arch-title = Arquitectura
home-arch-img-light = /images/ontoref-architecture.svg
home-arch-img-dark = /images/projects/ontoref_architecture-dark.svg
home-arch-caption = Capas del protocolo — entry point → NCL declarativo → Nushell operacional → runtime Rust opcional. Cada capa es testeable de forma independiente.
home-arch-caption = Capas del protocolo — entry point → NCL declarativo → Nushell operacional → tiempo de ejecución Rust opcional. Cada capa es testeable de forma independiente.
# El SVG es el gancho: enseña las capas y sus cifras vivas, pero las 171 aristas que declara son
# un número dentro de una caja — dibuja 5 flechas. El grafo las dibuja todas. Esto es el sedal
# que le faltaba al anzuelo (ADR-057 hook-static / prize-live, route-to-live-graph).
# El destino NO se inventa aquí: es el `live_view` que declara .ontoref/positioning/about.ncl,
# el mismo al que enruta el bloque del grafo de /acerca-de. Una sola fuente, dos superficies.
home-arch-live-href = /images/ontoref-graph.html
home-arch-live-label = Explorar el grafo vivo — 60 nodos, 171 aristas →
# CTA
home-cta-title = Estructura el Conocimiento de Tu Proyecto
home-cta-desc = Instala el CLI, ejecuta ontoref setup y tu proyecto gana invariantes consultables por máquina, ADRs vivos, modos operacionales con actor-awareness y compartición opcional de contexto del daemon.
home-cta-primary = Empezar
home-cta-secondary = Leer el Protocolo
# Destinos de los CTA. Son CLAVES, no literales en la plantilla: /about es /acerca-de en español, y
# un href fijo mandaría a todo lector español a la URL inglesa. Los dos CTA primarios apuntaban a
# /blog — "explorar el protocolo" entregaba una lista de posts.
home-cta-explore-url = /acerca-de
home-cta-primary-url = /acerca-de
home-cta-secondary-url = /blog
# Texto alternativo de la ilustración del hero. NO repite el titular — el titular ya es texto
# vivo en el <h1> de encima. Lo que este alt tiene que rescatar es el único texto que queda
# dentro del ráster: las cuatro etiquetas de valor pintadas alrededor del octógono. No existen
# en ninguna otra parte del código, así que sin esta línea son invisibles para un lector de
# pantalla y para cualquier rastreador.
home-hero-art-alt = ontoref: un sendero iluminado que asciende hasta el octógono de ontoref, rodeado de cuatro nodos de valor — Entiende antes de cambiar · Verifica cada cambio · Un conocimiento para cada actor · Herramientas, agentes y CI alineados.

View file

@ -1,5 +1,5 @@
# GENERATED from .ontoref/positioning/spine.ncl by gen-spine-pages.nu — do not edit by hand.
spine-hero = Pisa firme y anda ligero.
spine-hero = Pisa firme, con paso ligero.
spine-sub = Tu proyecto, tu infra, tu obra, tu vida: ¿sigues yendo a donde dijiste?
spine-close = Lo que dejas dicho, alguien podrá comprobarlo.
spine-prize = Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo.

View file

@ -0,0 +1,4 @@
# GENERATED from .ontoref/positioning/taglines.ncl by gen-taglines.nu — do not edit by hand.
# The rotator's idx-0 frame, rendered server-side so the hero does not flash (or, with JS
# off, sit on) a phrase the rotator was going to replace anyway.
home-tagline-initial = Un proyecto, una infraestructura, una vida: ¿sigues yendo a donde dijiste que ibas?

View file

@ -0,0 +1,56 @@
# Nota sobre el lenguaje — español
# GENERADO desde .ontoref/ontology/registry.ncl por scripts/gen-nota-lenguaje.nu.
# NO EDITAR A MANO: la próxima regeneración lo sobrescribe, y `--check` rompe el build si diverge.
lenguaje-page-title = Nota sobre el lenguaje
lenguaje-page-subtitle = Por qué este sitio dice «haz un commit» y no «confirma los cambios»
lenguaje-page-description = El criterio con el que decidimos qué término se traduce y cuál no — declarado, versionado y verificado por CI.
lenguaje-page-keywords = lenguaje, terminología, traducción, español, glosario
lenguaje-intro = Escribir sobre software en español no tiene convención asentada. No hay una autoridad que lo dicte, las guías de estilo de las grandes empresas se contradicen en los bordes, y cada equipo acaba resolviéndolo texto a texto. Nosotros también lo hacíamos así. Esto es lo que decidimos en su lugar.
lenguaje-h-criterio = El criterio
lenguaje-criterio-lead = Una palabra se queda en inglés por dos motivos distintos. Confundirlos es fácil, y es el error que nos hizo reescribir esta política entera.
lenguaje-criterio-1-t = Uno: es un identificador.
lenguaje-criterio-1 = Aparece tal cual en la línea de comandos, en una API, en un mensaje de error que vas a pegar en un buscador. Traducirlo rompe la referencia: el texto se lee bien y ya no te sirve para nada, porque no puedes volver de él a la herramienta. Vale para cualquier idioma — no es una opinión sobre el español.
lenguaje-criterio-2-t = Dos: no hay palabra fiel.
lenguaje-criterio-2 = Ninguna palabra española carga el concepto sin desplazarlo. Traducirlo rompe el concepto. «Compuerta» lo manda a la electrónica; «puerta» ya significa otra cosa aquí; «umbral» pierde que es una condición tipada; «punto de control» pierde que bloquea. Cada candidato parece razonable y cada uno pierde algo distinto. Esto sí es una afirmación sobre el español, y en otro idioma podría ser falsa.
lenguaje-criterio-3 = Y si el español sí tiene la palabra, la usamos. Sin complejos y sin nostalgia. Que un término se parezca al nombre de un fichero no lo vuelve intraducible.
lenguaje-criterio-run = Eso resuelve, de paso, la pregunta que parecía no tener respuesta. «¿Cómo se dice run en español?» no la tiene porque no es un término: es una palabra inglesa cualquiera, y según la frase será ejecutar, una ejecución o una pasada. Pero un Run con su identificador y su registro sí es una entidad, y se queda. Buscábamos una traducción para dos cosas distintas; por eso no aparecía.
lenguaje-h-kept = Lo que no traducimos
lenguaje-kept-lead = Se quedan en inglés, y la última columna dice por cuál de los dos motivos.
lenguaje-th-termino = Término
lenguaje-th-que-es = Qué es
lenguaje-th-porque-queda = Por qué se queda
lenguaje-h-rendered = Lo que sí traducimos
lenguaje-rendered-lead = El español carga el concepto entero. No hay nada que preservar.
lenguaje-th-en-ingles = En inglés
lenguaje-th-decimos = Aquí decimos
lenguaje-h-verbos = Los verbos, que son el problema de verdad
lenguaje-verbos-1 = Aquí es donde todo el mundo se atasca, y la solución no es cuestión de gusto sino de gramática.
lenguaje-verbos-2 = Un préstamo sustantivo es inofensivo: entra en español, elige género y ya está. El commit. La branch. No negocia nada más.
lenguaje-verbos-3 = Un préstamo verbo, en cambio, está obligado a elegir conjugación — y en español acaba casi siempre en -ear, que arrastra un registro coloquial. Por eso «commitear» chirría y «commit» no: no es el anglicismo, es la morfología. Le estás pidiendo a la palabra inglesa que se conjugue, y eso la marca.
lenguaje-verbos-4 = La salida es una construcción que el español ya usa de forma productiva (hacer clic, hacer una copia): verbo español + sustantivo inglés. Hacer un commit. Abrir un Pull Request. Aplicar un rebase. El español pone la gramática; el préstamo se queda quieto, señalando al artefacto.
lenguaje-th-no-escribimos = No escribimos
lenguaje-th-escribimos = Escribimos
lenguaje-th-porque = Por qué
lenguaje-h-friends = Y los que no chirrían — los peligrosos de verdad
lenguaje-friends-1 = Un calco se oye venir. Un falso amigo no: parece la traducción correcta, se lee con fluidez, y desplaza el significado sin que nadie lo note. «Librería» es una tienda; la palabra que buscas es biblioteca. Cada lengua evoluciona sus cognados por su cuenta, y esa deriva es invisible precisamente porque la palabra sigue pareciéndose a su origen.
lenguaje-friends-2 = Otros son cognados de verdad, pero tomados de otra disciplina: «compuerta» viene de la electrónica, y mete la lectura de circuito en una frase que hablaba de transiciones de estado.
lenguaje-h-hablando = Hablando es otra cosa
lenguaje-hablando-1 = Nada de lo anterior se aplica a una conversación. «Commiteé y luego mergeé» comunica perfectamente entre quienes trabajan con esto, y escrito sería jerga. La política dice exactamente eso: el calco está prohibido en todo texto y permitido en la boca. Una regla que fingiera lo contrario estaría mintiendo sobre cómo hablamos, y una regla que miente no la cumple nadie.
lenguaje-hablando-2 = Un falso amigo, en cambio, engaña igual a quien escucha que a quien lee. Ése se avisa siempre.
lenguaje-hablando-3 = Lo mismo vale para la pronunciación. Decir ráuter o dóker dentro de una frase en español no es ignorancia: es acomodación fonológica, y la hacen todas las lenguas que reciben préstamos. Pronunciar el término con fonética inglesa nativa en mitad de una frase en español es la opción marcada — por eso suena a presunción y no a precisión.
lenguaje-h-legal = Esto no es un aviso legal
lenguaje-legal-1 = La página de al lado es la política de privacidad, y existe porque la ley obliga. Ésta no. Ésta es un contrato de estilo: la analogía correcta no es el aviso legal, sino la licencia o el código de conducta — un documento normativo, publicado, versionado y referenciado.
lenguaje-legal-2 = Con una diferencia respecto a esos dos: éste se ejecuta.
lenguaje-legal-3 = Las tablas que acabas de leer no están escritas a mano. Se generan desde el mismo fichero declarado que revisa cada texto en español de este sitio antes de publicarlo, y que falla la compilación si encuentra una palabra que no debería estar — o si esta página deja de coincidir con la regla que dice explicar.
lenguaje-legal-4 = Que la política lingüística de un proyecto sea a su vez un artefacto declarado, versionado y verificado no es burocracia. Es la tesis del proyecto aplicada a sí misma.
lenguaje-foot = Esta página es una proyección de la política léxica del proyecto. Si encuentras una incoherencia entre lo que dice y lo que lees en el resto del sitio, es un fallo y nos interesa.

View file

@ -1,7 +1,7 @@
ontoref-adoption-subtitle = ontoref setup conecta cualquier proyecto nuevo o existente — scaffold idempotente con bootstrap de auth keys opcional.
ontoref-adoption-title = Adoptar en Cualquier Proyecto
ontoref-architecture-title = Arquitectura
ontoref-badge = Protocolo + Runtime · v0.1.0
ontoref-badge = Protocolo + Runtime · v0.1.8
ontoref-crates-title = Crates y Herramientas
ontoref-cta-explore = Explorar el Protocolo
ontoref-cta-subtitle = Empieza con ontoref setup. Tu proyecto gana invariantes consultables por máquina, ADRs vivos, modos operacionales con actor-awareness y un daemon que comparte contexto entre todos los actores en tiempo real.
@ -11,11 +11,11 @@ ontoref-footer-tagline = Protocolo + Runtime. Sin coacción. Un grafo por proyec
ontoref-graph-desc = Grafo dirigido por fuerzas de la ontología en vivo. Los nodos son tipados (Axioma · Tensión · Práctica) y polarizados (Yang · Yin · Espiral). Haz clic en cualquier nodo para abrir su panel de detalles.
ontoref-graph-title = La UI en Acción -> Vista de Grafo
ontoref-hero-coda = Protocolo + Runtime. Sin coacción.
ontoref-hero-desc = — codifica lo que un sistema ES (invariantes, tensiones, constraints) y hacia dónde VA (dimensiones de estado, condiciones de transición, membranas) en grafos acíclicos dirigidos consultables por máquina. Proyectos de software, sistemas operacionales personales, contextos de agente — los mismos tres ficheros, el mismo protocolo. Pregunta el radio de impacto de un cambio antes de hacerlo, verifica cada mutación contra las claves del actor, comparte un único contexto tipado en vivo entre CLI, IDE y CI. Un protocolo para desarrolladores, agentes, CI e individuos.
ontoref-hero-desc = — codifica lo que un sistema ES (invariantes, tensiones, restricciones) y hacia dónde VA (dimensiones de estado, condiciones de transición, membranas) en grafos acíclicos dirigidos consultables por máquina. Proyectos de software, sistemas operacionales personales, contextos de agente — los mismos tres ficheros, el mismo protocolo. Pregunta el radio de impacto de un cambio antes de hacerlo, verifica cada mutación contra las claves del actor, comparte un único contexto tipado en vivo entre CLI, IDE y CI. Un protocolo para desarrolladores, agentes, CI e individuos.
ontoref-hero-highlight = Ontología + Reflexión + Daemon + MCP
ontoref-layer-adopt-desc = Cada proyecto mantiene sus propios datos de .ontology/. Ontoref provee los schemas, módulos y scripts de migración. Cero vendor lock-in.
ontoref-layer-adopt-label = CAPA DE ADOPCION · Por proyecto
ontoref-layer-decl-desc = Tipos fuertes, contratos, enums. Falla en definición, no en runtime.
ontoref-layer-decl-desc = Tipos fuertes, contratos, enums. Falla en definición, no en tiempo de ejecución.
ontoref-layer-decl-label = CAPA DECLARATIVA · Nickel
ontoref-layer-entry-desc = Un único entry point por proyecto. Detecta actor (developer/agent/CI), adquiere lock, despacha al módulo Nu correcto.
ontoref-layer-entry-label = PUNTO DE ENTRADA · Bash → Nu
@ -24,11 +24,11 @@ ontoref-layer-graph-label = GRAFO DE CONOCIMIENTO · .ontology/
ontoref-layer-op-desc = Pipelines tipadas sobre datos estructurados. No streams de texto.
ontoref-layer-op-label = CAPA OPERACIONAL · Nushell
ontoref-layer-runtime-desc = Daemon persistente opcional. Caché de exports NCL, UI HTTP, servidor MCP, registro de actores, almacén de notificaciones, motor de búsqueda, persistencia SurrealDB. Nunca un requisito del protocolo.
ontoref-layer-runtime-label = CAPA RUNTIME · Rust + axum
ontoref-mcp-backlog-desc = <li><strong>pre_commit</strong> — el hook pre-commit hace POLL en <code>GET /notifications/pending?token=X&project=Y</code>; bloquea el commit git hasta que todo es reconocido</li><li><strong>drift</strong> — drift de schema detectado entre codebase y ontología</li><li><strong>ontology_drift</strong> — emitido por el observador pasivo con conteos missing/stale/drift/broken tras 15s debounce</li><li>Fail-open: si el daemon no está disponible, el hook pre-commit pasa — los commits nunca son bloqueados por caída del daemon</li><li>Ack vía UI o <code>POST /notifications/ack</code>; notificaciones custom vía <code>POST /{slug}/notifications/emit</code></li><li>Los botones de acción en notificaciones pueden enlazar a cualquier página del dashboard</li>
ontoref-layer-runtime-label = CAPA DE EJECUCIÓN · Rust + axum
ontoref-mcp-backlog-desc = <li><code>pre_commit</code> — el hook pre-commit hace POLL en <code>GET /notifications/pending?token=X&project=Y</code>; bloquea el commit git hasta que todo es reconocido</li><li><code>drift</code> — deriva de esquema detectada entre codebase y ontología</li><li><code>ontology_drift</code> — emitido por el observador pasivo con conteos <code>missing/stale/drift/broken</code> tras 15s debounce</li><li>Fail-open: si el daemon no está disponible, el hook pre-commit pasa — los commits nunca son bloqueados por caída del daemon</li><li>Ack vía UI o <code>POST /notifications/ack</code>; notificaciones custom vía <code>POST /{slug}/notifications/emit</code></li><li>Los botones de acción en notificaciones pueden enlazar a cualquier página del dashboard</li>
ontoref-mcp-backlog-title = Barrera de Notificaciones
ontoref-mcp-core-desc = ontoref-daemon es un proceso persistente opcional. Cachea exports NCL, sirve la UI web, expone la superficie de herramientas MCP, mantiene un registro de actores, almacena notificaciones, indexa todo para búsqueda y opcionalmente persiste en SurrealDB. Auth es opt-in: todas las superficies (CLI, UI, MCP) intercambian una project key por un token de sesión UUID v4 via <code>POST /sessions</code>; la CLI inyecta <code>ONTOREF_TOKEN</code> como Bearer automáticamente. Nunca cambia el protocolo — acelera y comparte el acceso a él. Configurado via <code>~/.config/ontoref/config.ncl</code> (Nickel, type-checked); edición interactiva con <code>ontoref config-edit</code>. Iniciado via NCL pipe bootstrap: <code>ontoref-daemon-boot</code>.
ontoref-mcp-knowledge-desc = <li>Habilitado con flag de feature <code>--db</code> y <code>--db-url ws://...</code></li><li>Conecta vía WebSocket al inicio — 5s timeout, <strong>fail-open</strong> (el daemon funciona sin él)</li><li>Siembra tablas de ontología desde archivos NCL locales al inicio y en cambios de fichero</li><li>Persiste: sesiones de actores, tablas de ontología sembradas, índice de búsqueda, historial de notificaciones</li><li>Sin <code>--db</code>: respaldado por DashMap en memoria, solo durante el proceso</li><li>Namespace configurable vía <code>--db-namespace</code>; credenciales vía <code>--db-username/--db-password</code></li>
ontoref-mcp-knowledge-desc = <li>Habilitado con la opción <code>--db</code> y <code>--db-url ws://...</code></li><li>Conecta vía WebSocket al inicio — 5s timeout, <strong>fail-open</strong> (el daemon funciona sin él)</li><li>Siembra tablas de ontología desde archivos NCL locales al inicio y en cambios de fichero</li><li>Persiste: sesiones de actores, tablas de ontología sembradas, índice de búsqueda, historial de notificaciones</li><li>Sin <code>--db</code>: respaldado por DashMap en memoria, solo durante el proceso</li><li>Namespace configurable vía <code>--db-namespace</code>; credenciales vía <code>--db-username/--db-password</code></li>
ontoref-mcp-knowledge-title = Persistencia SurrealDB — Opcional
ontoref-mcp-query-title = El Servidor MCP — Acceso al DAG para Agentes
ontoref-mcp-table-desc-header = Descripción
@ -36,19 +36,19 @@ ontoref-mcp-table-tool-header = Herramienta
ontoref-mcp-title = Daemon & MCP — Capa de Inteligencia en Tiempo de Ejecución
ontoref-mcp-tool-action-add-desc = Crear modo de reflexión + registrar como acción rápida
ontoref-mcp-tool-action-list-desc = Catálogo de acciones rápidas de .ontoref/config.ncl
ontoref-mcp-tool-backlog-desc = Añadir o actualizar estado de elemento del backlog
ontoref-mcp-tool-constraints-desc = Todos los constraints arquitectónicos hard + soft
ontoref-mcp-tool-backlog-desc = Añadir o actualizar estado de un elemento de la cartera de pendientes
ontoref-mcp-tool-constraints-desc = Todas las restricciones arquitectónicas hard + soft
ontoref-mcp-tool-describe-desc = Resumen de arquitectura y auto-descripción
ontoref-mcp-tool-get-adr-desc = Contenido completo de ADR con constraints
ontoref-mcp-tool-get-backlog-desc = Elementos de backlog filtrados por estado
ontoref-mcp-tool-get-adr-desc = Contenido completo de ADR con restricciones
ontoref-mcp-tool-get-backlog-desc = Elementos de la cartera de pendientes filtrados por estado
ontoref-mcp-tool-get-desc = Obtener nodo de ontología por id
ontoref-mcp-tool-get-mode-desc = Contrato DAG del modo — pasos, pre/postcondiciones
ontoref-mcp-tool-get-node-desc = Nodo completo con aristas y constraints
ontoref-mcp-tool-get-node-desc = Nodo completo con aristas y restricciones
ontoref-mcp-tool-help-desc = Lista herramientas disponibles y uso
ontoref-mcp-tool-list-adrs-desc = Listar ADRs filtrados por estado
ontoref-mcp-tool-list-modes-desc = Listar todos los modos de reflexión
ontoref-mcp-tool-list-projects-desc = Enumerar todos los proyectos registrados
ontoref-mcp-tool-project-status-desc = Dashboard completo del proyecto — salud, drift, actores
ontoref-mcp-tool-project-status-desc = Dashboard completo del proyecto — salud, deriva, actores
ontoref-mcp-tool-qa-add-desc = Persistir nueva entrada Q&A en reflection/qa.ncl
ontoref-mcp-tool-qa-list-desc = Listar almacén Q&A con filtro opcional
ontoref-mcp-tool-search-desc = Búsqueda de texto libre en nodos, ADRs, modos
@ -56,17 +56,17 @@ ontoref-mcp-tool-set-project-desc = Establecer contexto de proyecto por defecto
ontoref-metrics-title = Métricas del Protocolo
ontoref-page-subtitle = Protocolo Auto-Descriptivo para<br>Sistemas Evolutivos
ontoref-page-title = Ontoref — Un Protocolo de Ontología y Reflexión Auto-Descriptivo
ontoref-problem-1-desc = <li>Decisiones arquitectónicas en chat, olvidadas tras rotación</li><li>Sin fuente consultable por máquina de por qué algo existe</li><li>ADRs como Nickel tipado: invariantes, constraints, cadena de supersedencia</li><li>Constraints Hard aplicadas en cada operación</li>
ontoref-problem-1-desc = <li>Decisiones arquitectónicas en chat, olvidadas tras rotación</li><li>Sin fuente consultable por máquina de por qué algo existe</li><li>ADRs como Nickel tipado: invariantes, restricciones, cadena de supersedencia</li><li>Restricciones Hard aplicadas en cada operación</li>
ontoref-problem-1-title = Decisiones Sin Memoria
ontoref-problem-2-desc = <li>Configs cambian fuera de cualquier ciclo de revisión</li><li>Sin trazabilidad que vincule cambio a PR o ADR</li><li>Rollback requiere arqueología manual de ficheros</li><li>Perfiles sellados: hash sha256, historia completa, rollback verificado</li>
ontoref-problem-2-title = Drift de Configuración Invisible
ontoref-problem-3-desc = <li>Los LLMs empiezan cada sesión con cero conocimiento del proyecto</li><li>Mismos errores, mismas preguntas, sin acumulación entre operaciones</li><li>El registro de actores rastrea cada token de sesión, tipo, modo actual, último visto — persistido en disco</li><li>Las herramientas MCP dan a los agentes acceso DAG de lectura/escritura directo: nodos, ADRs, backlog, Q&A</li><li>Tareas compuestas compartidas via daemon — múltiples actores ven el mismo contexto operacional en vivo</li>
ontoref-problem-2-title = Deriva de Configuración Invisible
ontoref-problem-3-desc = <li>Los LLMs empiezan cada sesión con cero conocimiento del proyecto</li><li>Mismos errores, mismas preguntas, sin acumulación entre operaciones</li><li>El registro de actores rastrea cada token de sesión, tipo, modo actual, último visto — persistido en disco</li><li>Las herramientas MCP dan a los agentes acceso DAG de lectura/escritura directo: nodos, ADRs, cartera de pendientes, Q&A</li><li>Tareas compuestas compartidas via daemon — múltiples actores ven el mismo contexto operacional en vivo</li>
ontoref-problem-3-title = Agentes Sin Contexto
ontoref-problem-4-desc = <li>Guías en wikis, patrones en docs, decisiones en Slack</li><li>Sin fuente única consultable por humanos, agentes y CI por igual</li><li><code>.ontology/</code> separa tres concerns ortogonales: <code>core.ncl</code> (lo que ES) · <code>state.ncl</code> (dónde ESTAMOS vs queremos estar) · <code>gate.ncl</code> (cuándo LISTO para cruzar una frontera)</li><li><code>reflection/</code> lee los tres y responde consultas de autoconocimiento — un agente entiende el proyecto sin leer código, solo consultando el grafo declarativo</li>
ontoref-problem-4-title = Conocimiento de Proyecto Disperso
ontoref-problem-5-desc = <li>Cada proyecto reinventa sus propias convenciones</li><li>Sin contrato compartido para cómo se definen y ejecutan las operaciones</li><li>Modos de reflexión: contratos DAG tipados para cualquier flujo</li><li>Un protocolo adoptado por proyecto, sin imponer uniformidad</li>
ontoref-problem-5-title = Fragmentación de Protocolo
ontoref-problem-6-desc = <li>Q&A respondido en una sesión olvidado en la siguiente</li><li>El agente repite preguntas ya respondidas en sesiones anteriores</li><li>Q&A Knowledge Store: NCL tipado, versionado en git, persiste a través de resets del navegador</li><li>La barrera de notificaciones transmite drift a los agentes de forma proactiva — señales pre_commit, drift, ontology_drift bloquean hasta ser reconocidas</li>
ontoref-problem-6-desc = <li>Q&A respondido en una sesión olvidado en la siguiente</li><li>El agente repite preguntas ya respondidas en sesiones anteriores</li><li>Q&A Knowledge Store: NCL tipado, versionado en git, persiste a través de resets del navegador</li><li>La barrera de notificaciones transmite deriva a los agentes de forma proactiva — señales <code>pre_commit</code>, <code>drift</code>, <code>ontology_drift</code> bloquean hasta ser reconocidas</li>
ontoref-problem-6-title = Conocimiento Perdido Entre Sesiones
ontoref-problem-7-desc = <li>Decisiones personales y profesionales tomadas contra supuestos implícitos e inverificables</li><li>Sin modelo consultable de lo que nunca comprometes</li><li>Sin forma estructurada de preguntar: ¿viola esta oportunidad quién soy?</li><li>ontoref como ontología operacional personal — los mismos ficheros core/state/gate aplicados a dimensiones de vida, carrera y ecosistema</li><li><code>jpl validate "aceptar oferta"</code> → invariants_at_risk, aristas relevantes, veredicto</li>
ontoref-problem-7-title = Decisiones Sin Mapa
@ -77,7 +77,7 @@ ontoref-tension-1 = Ontología sin Reflexión = correcta pero estática. Inva
ontoref-tension-2 = Reflexión sin Ontología = fluida pero sin ancla. Flujos que olvidan lo que protegen.
ontoref-tension-thesis = El protocolo vive en la coexistencia.
ontoref-ui-dashboard-title = La UI Web
ontoref-yang-desc = <li><strong>Modos</strong> — contratos DAG tipados de flujo (precondiciones, pasos, postcondiciones)</li><li><strong>Formularios</strong> — recolección de parámetros que conducen modos</li><li><strong>Ciclo de vida ADR</strong> — Proposed → Accepted → Superseded, con historial de constraints</li><li><strong>Actores</strong> — developer / agent / CI, mismo protocolo, distintas capacidades</li><li><strong>Config seals</strong> — perfiles sellados con sha256, drift detection, rollback</li><li><strong>Quick Actions</strong> — atajos ejecutables sobre modos; configurados en <code>.ontoref/config.ncl</code></li><li><strong>Observador de Drift Pasivo</strong> — observa cambios de código, emite notificaciones <code>ontology_drift</code> con conteos de missing/stale/drift/broken</li>
ontoref-yang-desc = <li><strong>Modos</strong> — contratos DAG tipados de flujo (precondiciones, pasos, postcondiciones)</li><li><strong>Formularios</strong> — recolección de parámetros que conducen modos</li><li><strong>Ciclo de vida ADR</strong> — Proposed → Accepted → Superseded, con historial de restricciones</li><li><strong>Actores</strong> — developer / agent / CI, mismo protocolo, distintas capacidades</li><li><strong>Config seals</strong> — perfiles sellados con sha256, detección de deriva, rollback</li><li><strong>Quick Actions</strong> — atajos ejecutables sobre modos; configurados en <code>.ontoref/config.ncl</code></li><li><strong>Observador de Deriva Pasivo</strong> — observa cambios de código, emite notificaciones <code>ontology_drift</code> con conteos de <code>missing/stale/drift/broken</code></li>
ontoref-yang-sub = Cómo las cosas se mueven y cambian
ontoref-yang-title = Yang — La Capa de Reflexión
ontoref-yin-desc = <li><strong>Invariantes</strong> — axiomas que no pueden cambiar sin un nuevo ADR</li><li><strong>Tensiones</strong> — conflictos estructurales que el proyecto navega, nunca resuelve</li><li><strong>Prácticas</strong> — patrones confirmados con rutas a archivos reales y validadores ADR declarados</li><li><strong>Gates</strong> — membranas que controlan umbrales de preparación</li><li><strong>Dimensiones</strong> — estado actual vs deseado, con condiciones de transición</li><li><strong>Q&A Knowledge Store</strong> — Q&A acumulado persistido en NCL, versionado en git, consultable por cualquier actor</li>
@ -98,8 +98,8 @@ ontoref-ui-search-desc = Búsqueda de texto libre en nodos, ADRs y modos de refl
ontoref-ui-sessions-title = Sesiones
ontoref-ui-sessions-desc = Registro de actores con token, tipo, registered_at, last_seen y modo actual por actor.
ontoref-ui-notifications-title = Notificaciones
ontoref-ui-notifications-desc = Feed de notificaciones para señales pre_commit, drift y ontology_drift. Confirma, descarta o emite notificaciones personalizadas con botones de acción.
ontoref-ui-backlog-title = Backlog
ontoref-ui-notifications-desc = Feed de notificaciones para señales <code>pre_commit</code>, <code>drift</code> y <code>ontology_drift</code>. Confirma, descarta o emite notificaciones personalizadas con botones de acción.
ontoref-ui-backlog-title = Cartera de pendientes
ontoref-ui-backlog-desc = Elementos de trabajo filtrables por estado con operaciones de añadir y actualizar.
ontoref-ui-qa-title = Q&A
ontoref-ui-qa-desc = Almacén de conocimiento hidratado por servidor desde reflection/qa.ncl. Añade, edita y elimina entradas — persistidas como NCL tipado.
@ -109,17 +109,17 @@ ontoref-ui-modes-title = Modos
ontoref-ui-modes-desc = Lista de modos de reflexión de reflection/modes/ — nombre, descripción y contrato DAG completo por modo.
ontoref-ui-compose-title = Compose
ontoref-ui-compose-desc = Formularios de modos interactivos con compartición de sesión en vivo para flujos multi-actor.
ontoref-ui-dashboard-desc = Salud del proyecto, estado de drift, resumen de actores y notificaciones pendientes de un vistazo.
ontoref-ui-dashboard-desc = Salud del proyecto, estado de deriva, resumen de actores y notificaciones pendientes de un vistazo.
ontoref-mcp-core-title = Core
ontoref-mcp-query-desc = search, get_node, list_adrs, get_adr, list_modes, get_mode, constraints — recorrido completo del DAG.
ontoref-mcp-query-desc = <code>search, get_node, list_adrs, get_adr, list_modes, get_mode, constraints</code> — recorrido completo del DAG.
ontoref-crate-ontology-title = ontoref-ontology
ontoref-crate-ontology-desc = Carga ficheros .ontology/ NCL como structs Rust tipados. Recorrido de grafo (callers, callees, impact queries). Extracción de invariantes y validación de constraints. Sin dependencias de stratumiops — superficie de adopción mínima.
ontoref-crate-ontology-desc = Carga ficheros .ontology/ NCL como structs Rust tipados. Recorrido de grafo (callers, callees, impact queries). Extracción de invariantes y validación de restricciones. Sin dependencias de stratumiops — superficie de adopción mínima.
ontoref-crate-reflection-title = ontoref-reflection
ontoref-crate-reflection-desc = Ejecuta modos de reflexión como contratos DAG NCL tipados. Ejecución de pasos con resolución de dependencias. Ciclo de vida ADR de Proposed a Accepted a Superseded. Operaciones de config seal y rollback.
ontoref-crate-nushell-title = Módulos Nushell
ontoref-crate-nushell-desc = store.nu (caché respaldado por SurrealDB con export NCL) · sync.nu (sincronización de código de ontología) · describe.nu (autoconocimiento de proyecto con actor-awareness) · coder.nu (registros de sesión estructurados). 16 módulos en total — uno por dominio operacional.
ontoref-crate-nickel-title = Schemas Nickel
ontoref-crate-nickel-desc = Tipos de ontología core: Node, Edge, Pole, AbstractionLevel. Tipos de máquina de estados: Dimension, Transition, Gate, Membrane. Schema ADR: Constraint, Severity, Status, supersession. Schema de reflexión: Mode, Step, OnError, Dependency.
ontoref-crate-nickel-title = Esquemas Nickel
ontoref-crate-nickel-desc = Tipos de ontología core: <code>Node, Edge, Pole, AbstractionLevel</code>. Tipos de máquina de estados: Dimension, Transition, Gate, Membrane. Esquema ADR: <code>Constraint, Severity</code>, Status, supersession. Esquema de reflexión: Mode, Step, OnError, Dependency.
ontoref-crate-daemon-title = ontoref-daemon
ontoref-crate-daemon-desc = Registro de actores vía DashMap. Barrera de notificaciones con integración de hook pre-commit. Formularios Compose para despacho de modos compartidos. Persistencia respaldada por SurrealDB como feature opt-in. Feature gate NATS para setups distribuidos.

View file

@ -46,7 +46,7 @@ rustelo-feat-routing-title = Enrutamiento Agnóstico
rustelo-feat-routing-desc = Rutas definidas en TOML/NCL. Generación en tiempo de build. Sin rutas hardcodeadas. Descubrimiento automático de idiomas.
rustelo-feat-plugins-title = Sistema de Plugins
rustelo-feat-plugins-desc = Traits ResourceContributor y PageContributor. Registro al inicio, sin overhead en runtime. include_str!() para embedding en tiempo de compilación.
rustelo-feat-plugins-desc = Traits ResourceContributor y PageContributor. Registro al inicio, sin overhead en tiempo de ejecución. include_str!() para embedding en tiempo de compilación.
rustelo-feat-db-title = Abstracción de BD
rustelo-feat-db-desc = PostgreSQL y SQLite vía SQLx. Verificación de queries en tiempo de compilación. Pool de conexiones async. Migraciones separadas por tipo de base de datos.
@ -55,14 +55,14 @@ rustelo-feat-email-title = Email y Notificaciones
rustelo-feat-email-desc = Sistema de email multi-proveedor vía Lettre. Emails basados en plantillas con FTL i18n. Notificaciones en tiempo real WebSocket vía tokio-tungstenite.
rustelo-feat-codegen-title = Generación de Código en Build
rustelo-feat-codegen-desc = Nickel NCL → build.rs → OUT_DIR → include!(). Routes, pages, menus y registro FTL generados en tiempo de compilación. Coste cero en runtime.
rustelo-feat-codegen-desc = Nickel NCL → build.rs → OUT_DIR → include!(). Routes, pages, menus y registro FTL generados en tiempo de compilación. Coste cero en tiempo de ejecución.
rustelo-feat-features-title = Composición de Features Cargo
rustelo-feat-features-desc = auth, content-db, content-static, email, tls, metrics, crypto. Activa solo lo que necesitas. Compilación por features para targets WASM y nativo.
# Build pipeline
rustelo-pipeline-title = Generación Orientada a Configuración
rustelo-pipeline-subtitle = Todo generado en tiempo de compilación desde configuración Nickel NCL. Sin overhead en runtime, seguridad de tipos completa.
rustelo-pipeline-subtitle = Todo generado en tiempo de compilación desde configuración Nickel NCL. Sin overhead en tiempo de ejecución, seguridad de tipos completa.
# Quick start
rustelo-quickstart-title = En Marcha
@ -74,3 +74,7 @@ rustelo-tech-title = Construido sobre Bases Sólidas
# Bottom CTA
rustelo-cta-title = ¿Listo para construir con Rust?
rustelo-cta-subtitle = Open source · Self-hosted · Zero unsafe · 100% Rust
# <head> SEO (RusteloPage → rustelo).
rustelo-page-title = Rustelo — Framework Web Full-Stack en Rust
rustelo-page-description = Plataforma Rust modular y type-safe construida sobre Leptos y Axum. Arquitectura orientada a configuración con enrutamiento agnóstico al idioma, sistema de plugins y compilación dual WASM+nativo.

View file

@ -15,7 +15,7 @@ services-tier2-eyebrow = TIER 2 · OPERACIONES · SOSTIENE EL RUMBO
services-tier2-title = Operaciones y gobernanza, operadas como servicio
services-tier2-desc = El tier 2 es donde cada mutación pasa por una operación tipada y deposita un testigo Ed25519 — el recorrido de los cambios, no solo el estado, queda sostenido y verificable. El protocolo sigue abierto; la superficie atestiguada es lo que se opera o se da soporte por ti.
services-tier2-ops-title = Adopción de la capa de operaciones
services-tier2-ops-desc = Levantar el tier 2: operaciones de dominio como único camino de mutación del agente, precondiciones tipadas, dispatch y el daemon. El runtime pasa a ser el único sitio donde el estado autoritativo puede cambiar (ADR-024).
services-tier2-ops-desc = Levantar el tier 2: operaciones de dominio como único camino de mutación del agente, precondiciones tipadas, dispatch y el daemon. El tiempo de ejecución pasa a ser el único sitio donde el estado autoritativo puede cambiar (ADR-024).
services-tier2-audit-title = Traza de auditoría atestiguada
services-tier2-audit-desc = Un DAG de oplog Ed25519, direccionado por contenido y reproducible — un registro a prueba de manipulación que cualquiera verifica contra las claves de actor sin confiar en quien lo produjo (ADR-023/047, vp-004).
services-tier2-validators-title = Validadores y criterios a medida
@ -62,7 +62,7 @@ services-training-content-desc = Guías escritas y el docsite proyectado (mdBook
# ── Los tres tiers — cuál encaja ────────────────────────────────────────────
services-tiers-title = Qué tier encaja contigo
services-tiers-desc = La adopción es graduada (ADR-029): entra con un solo fichero NCL y sube solo cuando duela. Nunca eres rehén de un runtime ni de un SaaS.
services-tiers-desc = La adopción es graduada (ADR-029): entra con un solo fichero NCL y sube solo cuando duela. Nunca eres rehén de un tiempo de ejecución ni de un SaaS.
services-tier0-label = TIER 0 · DECLARATIVO
services-tier0-title = Sostiene su porqué
services-tier0-desc = Una ontología tipada más describe: declaras la razón por la que tu proyecto existe como NCL. El coaching te lleva aquí.
@ -76,7 +76,7 @@ services-tier2-card-desc = Testigos Ed25519 y determinismo de replay: cada cambi
# ── CTA ─────────────────────────────────────────────────────────────────────
services-cta-title = ¿Por dónde empezar?
services-cta-desc = Dinos tu tier y tu stack — te señalamos el primer paso más pequeño, sea una sesión de coaching o una intervención de tier 2.
services-cta-primary = Empezar una conversación
services-cta-primary-url = /contacto
services-cta-primary = Solicitar servicios
services-cta-primary-url = /solicitud-trabajo
services-cta-secondary = Ver qué es ontoref
services-cta-secondary-url = /acerca-de

View file

@ -13,27 +13,27 @@ stratumiops-problem-1-desc = Decisiones en Slack, guias en wikis, patrones en do
stratumiops-problem-2-title = Costos LLM sin Control
stratumiops-problem-2-desc = Sin visibilidad en gasto IA por equipo. Sin limites o controles de presupuesto. Vapora provee presupuestos en tiempo real, fallback automatico a proveedores baratos y routing basado en expertise.
stratumiops-problem-3-title = Configuracion YAML Fragil
stratumiops-problem-3-desc = Errores de runtime por config sin tipos. Sin validacion antes del deployment. Provisioning usa Nickel con validacion pre-runtime. TypeDialog provee formularios con validacion de contratos.
stratumiops-problem-3-desc = Errores de tiempo de ejecución por config sin tipos. Sin validacion antes del deployment. Provisioning usa Nickel con validacion pre-tiempo de ejecución. TypeDialog provee formularios con validacion de contratos.
stratumiops-problem-4-title = Criptografia Estatica
stratumiops-problem-4-desc = Sin preparacion para amenazas cuanticas. Bloqueado en una biblioteca cripto. SecretumVault entrega cripto post-cuantico en produccion con ML-KEM-768, ML-DSA-65 y 4 backends enchufables.
stratumiops-problem-5-title = Flujos de Proyecto Caoticos
stratumiops-problem-5-desc = TODOs dispersos, sin fases estructuradas. Sin trazabilidad de cambios de estado. Syntaxis provee orquestacion sistematica de proyectos con 4 interfaces y coordinacion de agentes IA lista para VAPORA.
stratumiops-problem-6-title = Decisiones Sin Memoria
stratumiops-problem-6-desc = Decisiones arquitectonicas en verbal o en chat. Constraints olvidadas tras cambios de equipo. Ontologia + ADRs proveen invariantes consultables por maquina con constraints hard aplicadas en cada operacion.
stratumiops-problem-7-title = Drift de Configuracion Invisible
stratumiops-problem-7-desc = Configs modificadas fuera de cualquier ciclo de revision. Sin trazabilidad que vincule cambio a PR o ADR. Reflection provee perfiles de config sellados con sha256, drift detection, rollback verificado e historia completa.
stratumiops-problem-6-desc = Decisiones arquitectonicas en verbal o en chat. Restricciones olvidadas tras cambios de equipo. Ontologia + ADRs proveen invariantes consultables por maquina con restricciones hard aplicadas en cada operacion.
stratumiops-problem-7-title = Deriva de Configuracion Invisible
stratumiops-problem-7-desc = Configs modificadas fuera de cualquier ciclo de revision. Sin trazabilidad que vincule cambio a PR o ADR. Reflexión provee perfiles de config sellados con sha256, deriva detection, rollback verificado e historia completa.
stratumiops-problem-8-title = Documentacion Muerta
stratumiops-problem-8-desc = Docs escritas una vez, nunca actualizadas. Distintos actores generan distintos formatos sin fuente comun. Kogral + Ontologia genera docs vivas desde el mismo grafo — docs para humanos, contexto para agentes, informes de CI.
# Ontologia, Reflection y ADRs
stratumiops-ontology-title = Ontologia, Reflection y ADRs
stratumiops-ontology-title = Ontologia, Reflexión y ADRs
stratumiops-yin-title = Yin — La Capa Formal
stratumiops-yin-subtitle = Lo que debe ser verdad
stratumiops-yin-items = Nickel schemas — correccion estructural en tiempo de definicion
ADR constraints — "esto nunca puede violarse"
Restricciones ADR — "esto nunca puede violarse"
Config seals — estados sellados, verificables con sha256
Ontology invariants — lo que no puede cambiar sin un nuevo ADR
Invariantes de ontología — lo que no puede cambiar sin un nuevo ADR
Hashes matematicos — prueba de que fue sellado y cuando
stratumiops-yang-title = Yang — La Capa Operacional
@ -50,10 +50,10 @@ stratumiops-tension-thesis = El sistema vive en la coexistencia.
stratumiops-layer-decl-label = Capa Declarativa — Nickel
stratumiops-layer-decl-items = .ontology/ · adrs/ · reflection/schemas/ · reflection/configs/
stratumiops-layer-decl-desc = Tipos fuertes, contratos, enums. Falla en definicion, no en runtime.
stratumiops-layer-decl-desc = Tipos fuertes, contratos, enums. Falla en definicion, no en tiempo de ejecución.
stratumiops-layer-op-label = Capa Operacional — Nushell
stratumiops-layer-op-items = adr · register · config · backlog · forms · prereqs
stratumiops-layer-op-items = adr · register · config · cartera de pendientes · forms · prereqs
stratumiops-layer-op-desc = Pipelines tipadas sobre datos estructurados. No streams de texto.
stratumiops-layer-entry-label = Punto de Entrada — Bash a Nu
@ -66,7 +66,7 @@ stratumiops-layer-graph-desc = El sistema sabe que sabe. Actor-agnostic. Machine
stratumiops-layer-seal-label = Estados Sellados — Config + Historia
stratumiops-layer-seal-items = perfiles · sellos sha256 · audit trail · rollback
stratumiops-layer-seal-desc = Inmutabilidad verificable. Drift detection. Trazabilidad completa ADR/PR/bug.
stratumiops-layer-seal-desc = Inmutabilidad verificable. Deriva detection. Trazabilidad completa ADR/PR/bug.
# Diagramas de Arquitectura
stratumiops-arch-title = Arquitectura
@ -101,7 +101,7 @@ stratumiops-stratum-embeddings-role = Proveedores de embeddings + VectorStore
stratumiops-stratum-nats-role = Consumidor JetStream + auth NKey
stratumiops-stratum-ncl-role = Resolutor OCI a Nickel local
stratumiops-stratum-ontology-role = Grafo de ontologia, invariantes, gates, dimensiones
stratumiops-stratum-reflection-role = Ciclo de vida ADR, config seals, backlog, modos
stratumiops-stratum-reflection-role = Ciclo de vida ADR, config seals, cartera de pendientes, modos
stratumiops-stratum-dispatcher-role = Dispatcher modular con actor-awareness
stratumiops-stratum-plugins-role = nickel-export, tera-render, nats pub

View file

@ -2,14 +2,14 @@
# Hero
typedialog-badge = 3.818 Tests | 6 Backends | 4 Proveedores LLM
typedialog-tagline = Dialogos tipados para inputs, formularios y schemas en los que puedes confiar
typedialog-tagline = Dialogos tipados para inputs, formularios y esquemas en los que puedes confiar
typedialog-page-title = Crea Dialogos Interactivos con Type-Safety
typedialog-page-subtitle = Formularios declarativos con definiciones Nickel/TOML, 6 backends (CLI, TUI, Web, AI, Agent, Prov-Gen) y validacion type-safe. Desde prompts interactivos hasta generacion de infraestructura. Un schema. Toda superficie.
typedialog-page-subtitle = Formularios declarativos con definiciones Nickel/TOML, 6 backends (CLI, TUI, Web, AI, Agent, Prov-Gen) y validacion type-safe. Desde prompts interactivos hasta generacion de infraestructura. Un esquema. Toda superficie.
# Problems
typedialog-problems-title = Lo que TypeDialog Resuelve
typedialog-problem-1-title = Codigo Por-Backend
typedialog-problem-1-desc = Una definicion Nickel o TOML controla CLI, TUI, Web y AI. Sin codigo de formularios por backend. El schema es la fuente unica de verdad.
typedialog-problem-1-desc = Una definicion Nickel o TOML controla CLI, TUI, Web y AI. Sin codigo de formularios por backend. El esquema es la fuente unica de verdad.
typedialog-problem-2-title = Validacion Fail-Open
typedialog-problem-2-desc = Los contratos Nickel validan cada predicado en tiempo de carga. Predicados desconocidos causan fallos duros, no passes silenciosos. Sin reimplementacion paralela en Rust.
typedialog-problem-3-title = I/O Oculto en Ejecucion
@ -24,7 +24,7 @@ typedialog-how-forms-desc = Define formularios en Nickel (.ncl) o TOML (.toml).
typedialog-how-execution-title = Ejecucion en Tres Fases
typedialog-how-execution-desc = Fase 1: Ejecutar campos selector que controlan condicionales. Fase 2: Construir lista de elementos (puro, sin I/O). Fase 3: Despachar al backend con evaluacion when/when_false. Modos de renderizado completo o campo a campo.
typedialog-how-factory-title = BackendFactory
typedialog-how-factory-desc = Feature gates en tiempo de compilacion eliminan codigo muerto. Match runtime de BackendType despacha al backend seleccionado. Auto-deteccion via env TYPEDIALOG_BACKEND con fallback CLI.
typedialog-how-factory-desc = Feature gates en tiempo de compilacion eliminan codigo muerto. Match tiempo de ejecución de BackendType despacha al backend seleccionado. Auto-deteccion via env TYPEDIALOG_BACKEND con fallback CLI.
typedialog-how-ai-title = Backends AI y Agent
typedialog-how-ai-desc = Backend AI con RAG, embeddings y busqueda semantica. Backend Agent ejecuta archivos .agent.mdx con soporte multi-LLM (Claude, OpenAI, Gemini, Ollama). Variables de template, imports de archivos, output en streaming.
typedialog-how-infra-title = Generacion de Infraestructura

View file

@ -33,7 +33,7 @@ vapora-feat-a2a-desc = Coordinación distribuida de agentes con despacho de tare
vapora-feat-knowledge-title = Knowledge Graph
vapora-feat-knowledge-desc = Historial de ejecución temporal con relaciones causales. Curvas de aprendizaje desde agregaciones diarias con ventana. Búsqueda de similitud recomienda soluciones de tareas pasadas. 20 tests verifican persistencia de grafo y perfiles de aprendizaje.
vapora-feat-nats-title = NATS JetStream
vapora-feat-nats-desc = Entrega confiable de mensajes para coordinación de agentes. Streams JetStream para eventos de workflow, completado de tareas y actualizaciones de estado. Fallback graceful cuando NATS no disponible.
vapora-feat-nats-desc = Entrega confiable de mensajes para coordinación de agentes. Streams JetStream para eventos de flujo de trabajo, completado de tareas y actualizaciones de estado. Fallback graceful cuando NATS no disponible.
vapora-feat-surrealdb-title = SurrealDB
vapora-feat-surrealdb-desc = Base de datos multi-modelo con capacidades de grafo. Scopes multi-tenant para aislamiento de workspace. Relaciones de grafo nativas para Knowledge Graph. Todas las queries usan bindings parametrizados. Tablas SCHEMAFULL con índices explícitos.
vapora-feat-api-title = Backend API y Conectores MCP

View file

@ -6,7 +6,7 @@ work-request-page-description = Envía una solicitud de trabajo para tu proyecto
work-request-page-keywords = trabajo, solicitud, consulta, proyecto
# Encabezado de la Página
work-request-title = Solicitar Cotización de Proyecto
work-request-title = Solicitar Cotización de Servicio
work-request-subtitle = Hablemos sobre los requisitos de tu proyecto y cómo puedo ayudarte a alcanzar tus objetivos.
work-request-description = Completa el formulario a continuación con los detalles de tu proyecto, y te responderé en 24 horas con una propuesta personalizada.
@ -16,7 +16,7 @@ work-request-project-title = Título del Proyecto
work-request-project-title-placeholder = Título breve para tu proyecto
work-request-project-description = Descripción del Proyecto
work-request-project-description-placeholder = Describe tu proyecto, objetivos y requisitos en detalle...
work-request-project-type = Tipo de Proyecto
work-request-project-type = Tipo de Servicio
work-request-timeline = Cronograma
work-request-budget = Rango de Presupuesto
work-request-priority = Nivel de Prioridad
@ -130,7 +130,7 @@ work-request-schedule-call = Programar Llamada
work-request-form-action = mailto:hello@ontoref.dev
# Claves faltantes necesarias por el componente
work-request-project-type-label = Tipo de Proyecto
work-request-project-type-label = Tipo de Servicio
work-request-project-type-placeholder = Seleccionar tipo de proyecto
work-request-project-type-new = Proyecto Nuevo
work-request-project-type-existing = Mejora de Proyecto Existente

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 247.73 83.02">
<defs>
<style>
.cls-1 {
fill: #f1f2f2;
font-family: SignPainter-HouseScript, SignPainter;
font-size: 62px;
}
.cls-2 {
letter-spacing: .02em;
}
.cls-3 {
letter-spacing: .03em;
}
.cls-4 {
letter-spacing: .02em;
}
.cls-5 {
letter-spacing: .02em;
}
.cls-6 {
letter-spacing: .02em;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<text class="cls-1" transform="translate(.38 52.7)"><tspan class="cls-5" x="0" y="0">Je</tspan><tspan class="cls-2" x="49.41" y="0">s</tspan><tspan class="cls-4" x="69.69" y="0">ú</tspan><tspan class="cls-2" x="96.04" y="0">s </tspan><tspan class="cls-6" x="131.5" y="0"></tspan><tspan class="cls-3" x="179.55" y="0">rez</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 987 B

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,340 @@
(function () {
var deck = document.getElementById('deck-view');
if (!deck) return;
var W = 980, H = 552;
// Strings come from the markup, not from the script: one deck.js serves every language and
// stays cacheable, while the chrome around the deck speaks the page's language.
var T_SLIDE = deck.dataset.tSlide || 'Slide';
var T_ZOOM = deck.dataset.tZoom || 'Zoom';
var n = +deck.dataset.count;
var slides = [].slice.call(deck.querySelectorAll('.deck-slide'));
var count = deck.querySelector('.deck-count');
var navs = deck.querySelector('.deck-navs');
var list = deck.querySelector('.deck-toc-list');
var i = 0;
// The index is DERIVED from each slide's own H1, never authored — a hand-written TOC
// is one more surface to drift out of sync with the deck, which is the exact disease
// this projection exists to cure.
slides.forEach(function (s, k) {
s.setAttribute('data-slide-label', (k + 1) + ' / ' + n);
// El id ya viene EN EL MARCADO (build-body). Asignarlo aquí es asignarlo tarde: el
// navegador ya resolvió el hash y no encontró nada.
var h = s.querySelector('h1, h2, h3');
var title = (h && h.textContent.trim()) || (T_SLIDE + ' ' + (k + 1));
var z = document.createElement('button');
z.type = 'button'; z.className = 'deck-zoom'; z.dataset.zoomSlide = k;
z.setAttribute('aria-label', T_ZOOM + ' ' + (k + 1));
z.textContent = '\u2921';
s.appendChild(z);
var li = document.createElement('li');
var b = document.createElement('button');
b.type = 'button'; b.className = 'deck-toc-link'; b.dataset.goto = k;
b.innerHTML = '<span class="deck-toc-num"></span><span class="deck-toc-text"></span>';
b.querySelector('.deck-toc-num').textContent = (k + 1) + '.';
b.querySelector('.deck-toc-text').textContent = title;
li.appendChild(b); list.appendChild(li);
});
var links = [].slice.call(list.querySelectorAll('.deck-toc-link'));
// Each slide's REAL content height. Slidev's frame is 552px tall and 12 of this deck's 16
// slides hold more than that — up to 672px — so a rigid frame silently cuts three quarters
// of the deck. Slidev clips them too; nobody noticed, because until now the slides were
// PNGs of themselves. A reading surface may not lose content.
function contentHeight(slide) {
var page = slide.querySelector('.slidev-page') || slide.querySelector('.print-slide-container');
if (!page) return H;
// The container is already scaled by --deck-k, and getBoundingClientRect reports
// POST-transform coordinates. Divide it back out, or --deck-h lands in scaled pixels and
// the CSS scales it a second time.
var k = parseFloat(getComputedStyle(slide).getPropertyValue('--deck-k')) || 1;
var top = page.getBoundingClientRect().top;
var max = H, all = page.querySelectorAll('*');
// An image reaching past 1.5x the frame is a decorative BLEED, not content: Slidev renders it
// oversized and clips it at the 552 frame ON PURPOSE (rustikon 17/23: a Ferris at ~1170px).
// Growing the card to contain it turns the slide into a giant portrait. A small image that
// overflows alongside the text (ontology's svg at ~660px) is NOT a bleed — it grows with the
// text. So: a bleeding image pins the slide to the native frame, and the card's overflow:hidden
// clips it exactly where Slidev does. Only TEXT overflow earns a taller card.
var BLEED = H * 1.5;
for (var i = 0; i < all.length; i++) {
var el = all[i];
var bottom = (el.getBoundingClientRect().bottom - top) / k;
if (bottom > max) max = bottom;
if (/^(img|svg|picture)$/i.test(el.tagName) && bottom > BLEED) return H;
}
return Math.ceil(max);
}
// Measure in whatever mode we are in. A display:none slide has no geometry, so force the
// stacked layout, measure, and restore — synchronously, so the browser never paints the
// intermediate state. Doing this by actually SWITCHING mode created a race: the markup
// ships stacked (the right no-JS fallback), the switch to slider waited on fonts, and a
// reader who pressed "Read all" inside that window was silently thrown back to slider.
function measureAll() {
var prev = deck.dataset.mode;
deck.dataset.mode = 'read';
slides.forEach(function (s) { s.style.setProperty('--deck-h', contentHeight(s)); });
deck.dataset.mode = prev;
}
// The scale CSS cannot express: box width / native width, as a plain number.
var ro = new ResizeObserver(function (entries) {
entries.forEach(function (e) {
// A display:none slide reports 0x0. Believing it writes --deck-k: 0 onto the fifteen
// hidden slides, and when the reader switches back to stacked their cards are zero-height
// until the observer fires again — asynchronously, so anything that measures in between
// sees an empty deck. An observer must not trust a measurement of an element that is not
// in the layout: keep the last good scale.
if (e.contentRect.width > 0) e.target.style.setProperty('--deck-k', e.contentRect.width / W);
});
});
slides.forEach(function (s) { ro.observe(s); });
deck.setAttribute('data-js', '');
// In slider mode the current slide is the one shown. Stacked, it is the one you are
// LOOKING at — which is not the same as the one you last clicked, so the rail follows
// the scroll rather than the click.
var visible = 0;
function mark() {
var cur = deck.dataset.mode === 'slider' ? i : visible;
links.forEach(function (l, k) { l.setAttribute('aria-current', String(k === cur)); });
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
var k = slides.indexOf(e.target);
if (k >= 0) { visible = k; if (deck.dataset.mode === 'read') mark(); }
}
});
}, { rootMargin: '-45% 0px -45% 0px' }); // whichever slide crosses the viewport's middle
slides.forEach(function (s) { io.observe(s); });
function show(k) {
i = (k + n) % n;
slides.forEach(function (s, j) { s.toggleAttribute('data-active', j === i); });
count.textContent = (i + 1) + ' / ' + n;
mark();
}
function setMode(m) {
deck.dataset.mode = m;
deck.querySelectorAll('[data-set-mode]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.dataset.setMode === m));
});
var slider = m === 'slider';
count.hidden = !slider; navs.hidden = !slider;
// Stacked, the rail is the navigation — a collapsed <details> above 16 slides is off
// screen by slide 5. Open it. In slider mode leave it as the reader left it.
if (slider) show(i); else mark();
}
function goto(k) {
if (deck.dataset.mode === 'slider') { show(k); deck.focus(); }
else { i = k; slides[k].scrollIntoView({ behavior: 'smooth', block: 'start' }); }
// Mobile only (< 60rem, where the index and the slides cannot sit side by side): after a jump,
// close the index so the target slide is not left buried beneath it. On desktop the rail is a
// permanent sidebar — leave it open.
if (!window.matchMedia('(min-width: 60rem)').matches) setToc(false);
}
// The rail shows by default where there is width for a column; narrow, it starts hidden
// so it does not push the deck below the fold. Either way the reader can toggle it.
var wide = window.matchMedia('(min-width: 60rem)');
var toggle = deck.querySelector('.deck-toc-toggle');
function setToc(on) {
deck.dataset.toc = on ? 'on' : 'off';
toggle.setAttribute('aria-expanded', String(on));
}
setToc(wide.matches);
toggle.addEventListener('click', function () { setToc(deck.dataset.toc !== 'on'); });
// The sticky bar has to sit on the page's own background, whatever it is. Measure it
// rather than guess it, and re-measure when the SITE switches theme — it has its own
// toggle, and a bar frozen to the old palette is worse than no bar.
function pageBg() {
var e = deck.parentElement;
while (e) {
var bg = getComputedStyle(e).backgroundColor;
// No regex here on purpose. Inside a template literal every backslash is eaten once
// before the regex engine sees it, and /^rgba(0,0,0,0)$/ silently became a capture
// group that matched nothing — syntactically valid, semantically wrong, invisible to
// a syntax check. String comparison needs no escapes and cannot be mis-escaped.
if (bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)') return bg;
e = e.parentElement;
}
return getComputedStyle(document.body).backgroundColor;
}
function syncPageBg() { deck.style.setProperty('--deck-page-bg', pageBg()); }
syncPageBg();
new MutationObserver(syncPageBg).observe(document.documentElement, {
attributes: true, attributeFilter: ['class', 'data-theme'],
});
// Help panel: a plain show/hide, and its own close button. Its content is server-rendered per
// language (from decks.ncl help_items); JS only toggles visibility.
var help = deck.querySelector('.deck-help');
var helpBtn = deck.querySelector('.deck-help-toggle');
function setHelp(on) {
help.hidden = !on;
helpBtn.setAttribute('aria-expanded', String(on));
}
helpBtn.addEventListener('click', function () { setHelp(help.hidden); });
deck.querySelector('.deck-help-close').addEventListener('click', function () { setHelp(false); });
var lastFocus = null;
function zoomOpen(k) {
lastFocus = document.activeElement;
show(k);
deck.setAttribute('data-zoom', '');
document.body.style.overflow = 'hidden';
deck.focus();
}
function zoomClose() {
if (!deck.hasAttribute('data-zoom')) return;
deck.removeAttribute('data-zoom');
document.body.style.overflow = '';
if (lastFocus && lastFocus.isConnected) lastFocus.focus();
}
// Slidev's copy buttons are in the captured DOM but inert — their handler was Vue.
// Rewire them. innerText, not textContent: it is the RENDERED code, so Shiki's per-line
// spans come back as actual newlines instead of one run-on string.
function copyCode(btn) {
var wrap = btn.closest('.slidev-code-wrapper');
var pre = wrap && wrap.querySelector('pre');
if (!pre) return;
var text = pre.innerText.replace(/\n$/, '');
var done = function () {
wrap.classList.add('deck-copied');
setTimeout(function () { wrap.classList.remove('deck-copied'); }, 1200);
};
function legacy() {
var ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); done(); } finally { ta.remove(); }
}
// The Clipboard API REJECTS when the document is not focused — a real case, not a
// theoretical one. Without the catch, the copy fails in complete silence.
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(done, legacy);
} else {
legacy();
}
}
deck.addEventListener('click', function (e) {
var cp = e.target.closest('.slidev-code-copy');
if (cp) { e.preventDefault(); return copyCode(cp); }
if (e.target.closest('.deck-backdrop')) return zoomClose();
// data-zoom-slide (the button) is deliberately NOT data-zoom (the deck's open state).
var z = e.target.closest('[data-zoom-slide]');
if (z) return deck.hasAttribute('data-zoom') ? zoomClose() : zoomOpen(+z.dataset.zoomSlide);
var m = e.target.closest('[data-set-mode]');
if (m) return setMode(m.dataset.setMode);
var t = e.target.closest('[data-goto]');
if (t) return goto(+t.dataset.goto);
var nav = e.target.closest('.deck-nav');
if (nav) show(i + (+nav.dataset.dir));
});
// Keys are scoped to the deck holding focus. A document-wide Space handler would steal
// page scrolling from every reader who never asked the deck for anything.
deck.addEventListener('keydown', function (e) {
var zoomed = deck.hasAttribute('data-zoom');
if (zoomed && e.key === 'Escape') { e.preventDefault(); return zoomClose(); }
// Zoomed, the keys must drive even stacked: a lightbox you can leave but not walk is
// a dead end.
if (!zoomed && deck.dataset.mode !== 'slider') return;
if (e.target.closest('.deck-toc')) return;
var fwd = e.key === 'ArrowRight' || e.key === 'ArrowDown' || (e.key === ' ' && !e.shiftKey);
var back = e.key === 'ArrowLeft' || e.key === 'ArrowUp' || (e.key === ' ' && e.shiftKey);
if (fwd) { e.preventDefault(); show(i + 1); }
else if (back) { e.preventDefault(); show(i - 1); }
else if (e.key === 'Home') { e.preventDefault(); show(0); }
else if (e.key === 'End') { e.preventDefault(); show(n - 1); }
});
// Printing in slider mode would emit exactly one slide.
window.addEventListener('beforeprint', function () { setMode('read'); });
// ── DÓNDE ATERRIZA UN ENLACE ────────────────────────────────────────────────────────
//
// Dos formas, y el lector no tiene por qué saber cuál le tocó:
// #slide-7 posicional — toda slide la tiene, es la URL de «lo que estoy viendo».
// #ncl nombrada — declarada en el frontmatter de la slide; es la que se escribe
// en el léxico, porque sobrevive a que el deck se reordene.
// La nombrada se resuelve por el DOM (getElementById → la .deck-slide que la contiene) en
// vez de por una tabla en este script: el ancla ya está en el marcado, y una segunda copia
// aquí es una segunda cosa que se puede desincronizar de la primera.
//
// El navegador NO puede hacer este salto solo. En modo slider la slide destino está en
// display:none y un ancla dentro de ella no tiene geometría: el scroll nativo no va a
// ninguna parte. Por eso el destino se ACTIVA antes de mirarlo, y por eso el id en el
// marcado no basta por sí solo.
function slideFromHash() {
var h = location.hash;
if (!h || h === '#') return -1;
var num = h.match(/^#slide-(\d+)$/);
if (num) {
var k = +num[1] - 1;
return (k >= 0 && k < n) ? k : -1;
}
// Un id cualquiera del deck: puede ser el ancla nombrada o algo dentro de una slide.
var el = null;
try { el = document.getElementById(decodeURIComponent(h.slice(1))); } catch (e) { el = null; }
var fig = el && el.closest ? el.closest('.deck-slide') : null;
return fig ? slides.indexOf(fig) : -1;
}
// LA BARRA SE MIDE; NO SE ADIVINA. Es sticky en top:0, así que tapa la cabecera de la slide a
// la que acabas de saltar — el título cortado justo debajo. El CSS deja 4rem de suelo, pero la
// barra crece con el tipo de letra del sitio y con el ancho (los botones se envuelven), y un
// número escrito a mano está bien el día que se escribe. scroll-margin es lo único que
// scrollIntoView respeta, así que la medida va ahí.
function syncScrollMargin() {
var bar = deck.querySelector('.deck-bar');
var g = (bar ? Math.ceil(bar.getBoundingClientRect().height) : 48) + 12;
slides.forEach(function (s) { s.style.scrollMarginTop = g + 'px'; });
}
syncScrollMargin();
window.addEventListener('resize', syncScrollMargin);
function gotoHash(smooth) {
var k = slideFromHash();
if (k < 0) return false;
// No es goto(k): aquí no queremos cerrar el índice ni robar el foco de la página a la
// que el lector acaba de llegar. Solo poner delante la slide que pidió la URL.
if (deck.dataset.mode === 'slider') show(k);
else i = k;
// Y SE SCROLLEA EN LOS DOS MODOS. En slider esto faltaba: show(k) solo activa la slide, así
// que lo único que movía la página era el scroll NATIVO del navegador al ancla — hecho antes
// de que este script corra y con el deck todavía apilado. Al cambiar a slider el documento
// encoge de 16 tarjetas a una, ese scroll queda apuntando a un layout que ya no existe, y el
// lector aterriza con el título medio tapado por la barra. La slide correcta, mal puesta.
slides[k].scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'start' });
return true;
}
// Measure AFTER the webfonts land. With the fallback font the text is shorter and every
// slide "fits"; once Nunito Sans arrives it grows and spills. Measured before fonts, every
// --deck-h came back 552 and nine slides were still cut.
// And measure while still stacked: a display:none slide has no geometry at all. So the
// switch to slider waits for the measurement, not the other way round.
// Take the mode immediately — no waiting, no race with the reader.
measureAll();
setMode('slider');
gotoHash(false);
// Un enlace al MISMO deck con otro fragmento no recarga la página: sin esto, pulsar NCL y
// luego DAG desde otro tooltip cambia la URL y deja la slide donde estaba. La URL diría una
// cosa y la pantalla otra — que es la deriva de siempre, servida a 200.
window.addEventListener('hashchange', function () { gotoHash(true); });
// Then measure again once the webfonts land: with the fallback face the text is shorter
// and every slide "fits", so a pre-font measurement returns 552 for all of them and nine
// slides stay cut. This pass changes no mode and interrupts nothing.
if (document.fonts && document.fonts.ready) document.fonts.ready.then(measureAll);
window.addEventListener('resize', measureAll);
})();

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 247.73 83.02">
<defs>
<style>
.cls-1 {
fill: #f1f2f2;
font-family: SignPainter-HouseScript, SignPainter;
font-size: 62px;
}
.cls-2 {
letter-spacing: .02em;
}
.cls-3 {
letter-spacing: .03em;
}
.cls-4 {
letter-spacing: .02em;
}
.cls-5 {
letter-spacing: .02em;
}
.cls-6 {
letter-spacing: .02em;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<text class="cls-1" transform="translate(.38 52.7)"><tspan class="cls-5" x="0" y="0">Je</tspan><tspan class="cls-2" x="49.41" y="0">s</tspan><tspan class="cls-4" x="69.69" y="0">ú</tspan><tspan class="cls-2" x="96.04" y="0">s </tspan><tspan class="cls-6" x="131.5" y="0"></tspan><tspan class="cls-3" x="179.55" y="0">rez</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 987 B

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,340 @@
(function () {
var deck = document.getElementById('deck-view');
if (!deck) return;
var W = 980, H = 552;
// Strings come from the markup, not from the script: one deck.js serves every language and
// stays cacheable, while the chrome around the deck speaks the page's language.
var T_SLIDE = deck.dataset.tSlide || 'Slide';
var T_ZOOM = deck.dataset.tZoom || 'Zoom';
var n = +deck.dataset.count;
var slides = [].slice.call(deck.querySelectorAll('.deck-slide'));
var count = deck.querySelector('.deck-count');
var navs = deck.querySelector('.deck-navs');
var list = deck.querySelector('.deck-toc-list');
var i = 0;
// The index is DERIVED from each slide's own H1, never authored — a hand-written TOC
// is one more surface to drift out of sync with the deck, which is the exact disease
// this projection exists to cure.
slides.forEach(function (s, k) {
s.setAttribute('data-slide-label', (k + 1) + ' / ' + n);
// El id ya viene EN EL MARCADO (build-body). Asignarlo aquí es asignarlo tarde: el
// navegador ya resolvió el hash y no encontró nada.
var h = s.querySelector('h1, h2, h3');
var title = (h && h.textContent.trim()) || (T_SLIDE + ' ' + (k + 1));
var z = document.createElement('button');
z.type = 'button'; z.className = 'deck-zoom'; z.dataset.zoomSlide = k;
z.setAttribute('aria-label', T_ZOOM + ' ' + (k + 1));
z.textContent = '\u2921';
s.appendChild(z);
var li = document.createElement('li');
var b = document.createElement('button');
b.type = 'button'; b.className = 'deck-toc-link'; b.dataset.goto = k;
b.innerHTML = '<span class="deck-toc-num"></span><span class="deck-toc-text"></span>';
b.querySelector('.deck-toc-num').textContent = (k + 1) + '.';
b.querySelector('.deck-toc-text').textContent = title;
li.appendChild(b); list.appendChild(li);
});
var links = [].slice.call(list.querySelectorAll('.deck-toc-link'));
// Each slide's REAL content height. Slidev's frame is 552px tall and 12 of this deck's 16
// slides hold more than that — up to 672px — so a rigid frame silently cuts three quarters
// of the deck. Slidev clips them too; nobody noticed, because until now the slides were
// PNGs of themselves. A reading surface may not lose content.
function contentHeight(slide) {
var page = slide.querySelector('.slidev-page') || slide.querySelector('.print-slide-container');
if (!page) return H;
// The container is already scaled by --deck-k, and getBoundingClientRect reports
// POST-transform coordinates. Divide it back out, or --deck-h lands in scaled pixels and
// the CSS scales it a second time.
var k = parseFloat(getComputedStyle(slide).getPropertyValue('--deck-k')) || 1;
var top = page.getBoundingClientRect().top;
var max = H, all = page.querySelectorAll('*');
// An image reaching past 1.5x the frame is a decorative BLEED, not content: Slidev renders it
// oversized and clips it at the 552 frame ON PURPOSE (rustikon 17/23: a Ferris at ~1170px).
// Growing the card to contain it turns the slide into a giant portrait. A small image that
// overflows alongside the text (ontology's svg at ~660px) is NOT a bleed — it grows with the
// text. So: a bleeding image pins the slide to the native frame, and the card's overflow:hidden
// clips it exactly where Slidev does. Only TEXT overflow earns a taller card.
var BLEED = H * 1.5;
for (var i = 0; i < all.length; i++) {
var el = all[i];
var bottom = (el.getBoundingClientRect().bottom - top) / k;
if (bottom > max) max = bottom;
if (/^(img|svg|picture)$/i.test(el.tagName) && bottom > BLEED) return H;
}
return Math.ceil(max);
}
// Measure in whatever mode we are in. A display:none slide has no geometry, so force the
// stacked layout, measure, and restore — synchronously, so the browser never paints the
// intermediate state. Doing this by actually SWITCHING mode created a race: the markup
// ships stacked (the right no-JS fallback), the switch to slider waited on fonts, and a
// reader who pressed "Read all" inside that window was silently thrown back to slider.
function measureAll() {
var prev = deck.dataset.mode;
deck.dataset.mode = 'read';
slides.forEach(function (s) { s.style.setProperty('--deck-h', contentHeight(s)); });
deck.dataset.mode = prev;
}
// The scale CSS cannot express: box width / native width, as a plain number.
var ro = new ResizeObserver(function (entries) {
entries.forEach(function (e) {
// A display:none slide reports 0x0. Believing it writes --deck-k: 0 onto the fifteen
// hidden slides, and when the reader switches back to stacked their cards are zero-height
// until the observer fires again — asynchronously, so anything that measures in between
// sees an empty deck. An observer must not trust a measurement of an element that is not
// in the layout: keep the last good scale.
if (e.contentRect.width > 0) e.target.style.setProperty('--deck-k', e.contentRect.width / W);
});
});
slides.forEach(function (s) { ro.observe(s); });
deck.setAttribute('data-js', '');
// In slider mode the current slide is the one shown. Stacked, it is the one you are
// LOOKING at — which is not the same as the one you last clicked, so the rail follows
// the scroll rather than the click.
var visible = 0;
function mark() {
var cur = deck.dataset.mode === 'slider' ? i : visible;
links.forEach(function (l, k) { l.setAttribute('aria-current', String(k === cur)); });
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
var k = slides.indexOf(e.target);
if (k >= 0) { visible = k; if (deck.dataset.mode === 'read') mark(); }
}
});
}, { rootMargin: '-45% 0px -45% 0px' }); // whichever slide crosses the viewport's middle
slides.forEach(function (s) { io.observe(s); });
function show(k) {
i = (k + n) % n;
slides.forEach(function (s, j) { s.toggleAttribute('data-active', j === i); });
count.textContent = (i + 1) + ' / ' + n;
mark();
}
function setMode(m) {
deck.dataset.mode = m;
deck.querySelectorAll('[data-set-mode]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.dataset.setMode === m));
});
var slider = m === 'slider';
count.hidden = !slider; navs.hidden = !slider;
// Stacked, the rail is the navigation — a collapsed <details> above 16 slides is off
// screen by slide 5. Open it. In slider mode leave it as the reader left it.
if (slider) show(i); else mark();
}
function goto(k) {
if (deck.dataset.mode === 'slider') { show(k); deck.focus(); }
else { i = k; slides[k].scrollIntoView({ behavior: 'smooth', block: 'start' }); }
// Mobile only (< 60rem, where the index and the slides cannot sit side by side): after a jump,
// close the index so the target slide is not left buried beneath it. On desktop the rail is a
// permanent sidebar — leave it open.
if (!window.matchMedia('(min-width: 60rem)').matches) setToc(false);
}
// The rail shows by default where there is width for a column; narrow, it starts hidden
// so it does not push the deck below the fold. Either way the reader can toggle it.
var wide = window.matchMedia('(min-width: 60rem)');
var toggle = deck.querySelector('.deck-toc-toggle');
function setToc(on) {
deck.dataset.toc = on ? 'on' : 'off';
toggle.setAttribute('aria-expanded', String(on));
}
setToc(wide.matches);
toggle.addEventListener('click', function () { setToc(deck.dataset.toc !== 'on'); });
// The sticky bar has to sit on the page's own background, whatever it is. Measure it
// rather than guess it, and re-measure when the SITE switches theme — it has its own
// toggle, and a bar frozen to the old palette is worse than no bar.
function pageBg() {
var e = deck.parentElement;
while (e) {
var bg = getComputedStyle(e).backgroundColor;
// No regex here on purpose. Inside a template literal every backslash is eaten once
// before the regex engine sees it, and /^rgba(0,0,0,0)$/ silently became a capture
// group that matched nothing — syntactically valid, semantically wrong, invisible to
// a syntax check. String comparison needs no escapes and cannot be mis-escaped.
if (bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)') return bg;
e = e.parentElement;
}
return getComputedStyle(document.body).backgroundColor;
}
function syncPageBg() { deck.style.setProperty('--deck-page-bg', pageBg()); }
syncPageBg();
new MutationObserver(syncPageBg).observe(document.documentElement, {
attributes: true, attributeFilter: ['class', 'data-theme'],
});
// Help panel: a plain show/hide, and its own close button. Its content is server-rendered per
// language (from decks.ncl help_items); JS only toggles visibility.
var help = deck.querySelector('.deck-help');
var helpBtn = deck.querySelector('.deck-help-toggle');
function setHelp(on) {
help.hidden = !on;
helpBtn.setAttribute('aria-expanded', String(on));
}
helpBtn.addEventListener('click', function () { setHelp(help.hidden); });
deck.querySelector('.deck-help-close').addEventListener('click', function () { setHelp(false); });
var lastFocus = null;
function zoomOpen(k) {
lastFocus = document.activeElement;
show(k);
deck.setAttribute('data-zoom', '');
document.body.style.overflow = 'hidden';
deck.focus();
}
function zoomClose() {
if (!deck.hasAttribute('data-zoom')) return;
deck.removeAttribute('data-zoom');
document.body.style.overflow = '';
if (lastFocus && lastFocus.isConnected) lastFocus.focus();
}
// Slidev's copy buttons are in the captured DOM but inert — their handler was Vue.
// Rewire them. innerText, not textContent: it is the RENDERED code, so Shiki's per-line
// spans come back as actual newlines instead of one run-on string.
function copyCode(btn) {
var wrap = btn.closest('.slidev-code-wrapper');
var pre = wrap && wrap.querySelector('pre');
if (!pre) return;
var text = pre.innerText.replace(/\n$/, '');
var done = function () {
wrap.classList.add('deck-copied');
setTimeout(function () { wrap.classList.remove('deck-copied'); }, 1200);
};
function legacy() {
var ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); done(); } finally { ta.remove(); }
}
// The Clipboard API REJECTS when the document is not focused — a real case, not a
// theoretical one. Without the catch, the copy fails in complete silence.
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(done, legacy);
} else {
legacy();
}
}
deck.addEventListener('click', function (e) {
var cp = e.target.closest('.slidev-code-copy');
if (cp) { e.preventDefault(); return copyCode(cp); }
if (e.target.closest('.deck-backdrop')) return zoomClose();
// data-zoom-slide (the button) is deliberately NOT data-zoom (the deck's open state).
var z = e.target.closest('[data-zoom-slide]');
if (z) return deck.hasAttribute('data-zoom') ? zoomClose() : zoomOpen(+z.dataset.zoomSlide);
var m = e.target.closest('[data-set-mode]');
if (m) return setMode(m.dataset.setMode);
var t = e.target.closest('[data-goto]');
if (t) return goto(+t.dataset.goto);
var nav = e.target.closest('.deck-nav');
if (nav) show(i + (+nav.dataset.dir));
});
// Keys are scoped to the deck holding focus. A document-wide Space handler would steal
// page scrolling from every reader who never asked the deck for anything.
deck.addEventListener('keydown', function (e) {
var zoomed = deck.hasAttribute('data-zoom');
if (zoomed && e.key === 'Escape') { e.preventDefault(); return zoomClose(); }
// Zoomed, the keys must drive even stacked: a lightbox you can leave but not walk is
// a dead end.
if (!zoomed && deck.dataset.mode !== 'slider') return;
if (e.target.closest('.deck-toc')) return;
var fwd = e.key === 'ArrowRight' || e.key === 'ArrowDown' || (e.key === ' ' && !e.shiftKey);
var back = e.key === 'ArrowLeft' || e.key === 'ArrowUp' || (e.key === ' ' && e.shiftKey);
if (fwd) { e.preventDefault(); show(i + 1); }
else if (back) { e.preventDefault(); show(i - 1); }
else if (e.key === 'Home') { e.preventDefault(); show(0); }
else if (e.key === 'End') { e.preventDefault(); show(n - 1); }
});
// Printing in slider mode would emit exactly one slide.
window.addEventListener('beforeprint', function () { setMode('read'); });
// ── DÓNDE ATERRIZA UN ENLACE ────────────────────────────────────────────────────────
//
// Dos formas, y el lector no tiene por qué saber cuál le tocó:
// #slide-7 posicional — toda slide la tiene, es la URL de «lo que estoy viendo».
// #ncl nombrada — declarada en el frontmatter de la slide; es la que se escribe
// en el léxico, porque sobrevive a que el deck se reordene.
// La nombrada se resuelve por el DOM (getElementById → la .deck-slide que la contiene) en
// vez de por una tabla en este script: el ancla ya está en el marcado, y una segunda copia
// aquí es una segunda cosa que se puede desincronizar de la primera.
//
// El navegador NO puede hacer este salto solo. En modo slider la slide destino está en
// display:none y un ancla dentro de ella no tiene geometría: el scroll nativo no va a
// ninguna parte. Por eso el destino se ACTIVA antes de mirarlo, y por eso el id en el
// marcado no basta por sí solo.
function slideFromHash() {
var h = location.hash;
if (!h || h === '#') return -1;
var num = h.match(/^#slide-(\d+)$/);
if (num) {
var k = +num[1] - 1;
return (k >= 0 && k < n) ? k : -1;
}
// Un id cualquiera del deck: puede ser el ancla nombrada o algo dentro de una slide.
var el = null;
try { el = document.getElementById(decodeURIComponent(h.slice(1))); } catch (e) { el = null; }
var fig = el && el.closest ? el.closest('.deck-slide') : null;
return fig ? slides.indexOf(fig) : -1;
}
// LA BARRA SE MIDE; NO SE ADIVINA. Es sticky en top:0, así que tapa la cabecera de la slide a
// la que acabas de saltar — el título cortado justo debajo. El CSS deja 4rem de suelo, pero la
// barra crece con el tipo de letra del sitio y con el ancho (los botones se envuelven), y un
// número escrito a mano está bien el día que se escribe. scroll-margin es lo único que
// scrollIntoView respeta, así que la medida va ahí.
function syncScrollMargin() {
var bar = deck.querySelector('.deck-bar');
var g = (bar ? Math.ceil(bar.getBoundingClientRect().height) : 48) + 12;
slides.forEach(function (s) { s.style.scrollMarginTop = g + 'px'; });
}
syncScrollMargin();
window.addEventListener('resize', syncScrollMargin);
function gotoHash(smooth) {
var k = slideFromHash();
if (k < 0) return false;
// No es goto(k): aquí no queremos cerrar el índice ni robar el foco de la página a la
// que el lector acaba de llegar. Solo poner delante la slide que pidió la URL.
if (deck.dataset.mode === 'slider') show(k);
else i = k;
// Y SE SCROLLEA EN LOS DOS MODOS. En slider esto faltaba: show(k) solo activa la slide, así
// que lo único que movía la página era el scroll NATIVO del navegador al ancla — hecho antes
// de que este script corra y con el deck todavía apilado. Al cambiar a slider el documento
// encoge de 16 tarjetas a una, ese scroll queda apuntando a un layout que ya no existe, y el
// lector aterriza con el título medio tapado por la barra. La slide correcta, mal puesta.
slides[k].scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'start' });
return true;
}
// Measure AFTER the webfonts land. With the fallback font the text is shorter and every
// slide "fits"; once Nunito Sans arrives it grows and spills. Measured before fonts, every
// --deck-h came back 552 and nine slides were still cut.
// And measure while still stacked: a display:none slide has no geometry at all. So the
// switch to slider waits for the measurement, not the other way round.
// Take the mode immediately — no waiting, no race with the reader.
measureAll();
setMode('slider');
gotoHash(false);
// Un enlace al MISMO deck con otro fragmento no recarga la página: sin esto, pulsar NCL y
// luego DAG desde otro tooltip cambia la URL y deja la slide donde estaba. La URL diría una
// cosa y la pantalla otra — que es la deriva de siempre, servida a 200.
window.addEventListener('hashchange', function () { gotoHash(true); });
// Then measure again once the webfonts land: with the fallback face the text is shorter
// and every slide "fits", so a pre-font measurement returns 552 for all of them and nine
// slides stay cut. This pass changes no mode and interrupts nothing.
if (document.fonts && document.fonts.ready) document.fonts.ready.then(measureAll);
window.addEventListener('resize', measureAll);
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 247.73 83.02">
<defs>
<style>
.cls-1 {
fill: #f1f2f2;
font-family: SignPainter-HouseScript, SignPainter;
font-size: 62px;
}
.cls-2 {
letter-spacing: .02em;
}
.cls-3 {
letter-spacing: .03em;
}
.cls-4 {
letter-spacing: .02em;
}
.cls-5 {
letter-spacing: .02em;
}
.cls-6 {
letter-spacing: .02em;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<text class="cls-1" transform="translate(.38 52.7)"><tspan class="cls-5" x="0" y="0">Je</tspan><tspan class="cls-2" x="49.41" y="0">s</tspan><tspan class="cls-4" x="69.69" y="0">ú</tspan><tspan class="cls-2" x="96.04" y="0">s </tspan><tspan class="cls-6" x="131.5" y="0"></tspan><tspan class="cls-3" x="179.55" y="0">rez</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View file

@ -0,0 +1,19 @@
<svg viewBox="0 0 400 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
.fi3{animation:fi3 1s ease-out .5s both}
</style>
</defs>
<!-- Wordmark -->
<g class="fi3">
<text x="200" y="85" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="92" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="200" y="145" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="22" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why.
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 878 B

View file

@ -0,0 +1,197 @@
<svg viewBox="0 0 600 700" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ PAKUA FRAME ═══ -->
<!-- Outer octagon R=218 -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<!-- Inner decorative ring R=208 -->
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks at 8 positions -->
<!-- Top (☰ heaven: 3 solid) — silver -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<!-- Bottom (☷ earth: 3 broken) — amber -->
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<!-- Right (☲ fire) — amber -->
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- Left (☵ water) — silver -->
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- TR, TL, BR, BL — alternating -->
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- ═══ MAIN SYMBOL ═══ -->
<g class="fl3" transform="scale(0.65) translate(64, 0)">
<!-- Octagon outline R=195 -->
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION (Ref / Structure) -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<!-- Grid blocks (two columns) -->
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<!-- ★ SEED: amber circle in silver -->
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION (Onto / Graph) -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<!-- DAG -->
<g filter="url(#nGl3)">
<!-- Line 1: 395,155 → 355,236 (nodo 2, r=14) termina antes del nodo -->
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<!-- Line 2: 395,155 → 440,236 (nodo 3, r=14) termina antes del nodo -->
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<!-- Line 3: 355,244 → 398,316 (nodo 4, r=15) termina antes del nodo -->
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<!-- Line 4: 440,244 → 398,316 (nodo 4, r=15) termina antes del nodo -->
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<!-- Line 5: 398,324 → 385,390 (nodo 5, r=12) termina antes del nodo -->
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<!-- Arrowheads -->
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<!-- Nodes (silver gradient) -->
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<!-- ★ SEED: silver square in amber -->
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,340 @@
(function () {
var deck = document.getElementById('deck-view');
if (!deck) return;
var W = 980, H = 552;
// Strings come from the markup, not from the script: one deck.js serves every language and
// stays cacheable, while the chrome around the deck speaks the page's language.
var T_SLIDE = deck.dataset.tSlide || 'Slide';
var T_ZOOM = deck.dataset.tZoom || 'Zoom';
var n = +deck.dataset.count;
var slides = [].slice.call(deck.querySelectorAll('.deck-slide'));
var count = deck.querySelector('.deck-count');
var navs = deck.querySelector('.deck-navs');
var list = deck.querySelector('.deck-toc-list');
var i = 0;
// The index is DERIVED from each slide's own H1, never authored — a hand-written TOC
// is one more surface to drift out of sync with the deck, which is the exact disease
// this projection exists to cure.
slides.forEach(function (s, k) {
s.setAttribute('data-slide-label', (k + 1) + ' / ' + n);
// El id ya viene EN EL MARCADO (build-body). Asignarlo aquí es asignarlo tarde: el
// navegador ya resolvió el hash y no encontró nada.
var h = s.querySelector('h1, h2, h3');
var title = (h && h.textContent.trim()) || (T_SLIDE + ' ' + (k + 1));
var z = document.createElement('button');
z.type = 'button'; z.className = 'deck-zoom'; z.dataset.zoomSlide = k;
z.setAttribute('aria-label', T_ZOOM + ' ' + (k + 1));
z.textContent = '\u2921';
s.appendChild(z);
var li = document.createElement('li');
var b = document.createElement('button');
b.type = 'button'; b.className = 'deck-toc-link'; b.dataset.goto = k;
b.innerHTML = '<span class="deck-toc-num"></span><span class="deck-toc-text"></span>';
b.querySelector('.deck-toc-num').textContent = (k + 1) + '.';
b.querySelector('.deck-toc-text').textContent = title;
li.appendChild(b); list.appendChild(li);
});
var links = [].slice.call(list.querySelectorAll('.deck-toc-link'));
// Each slide's REAL content height. Slidev's frame is 552px tall and 12 of this deck's 16
// slides hold more than that — up to 672px — so a rigid frame silently cuts three quarters
// of the deck. Slidev clips them too; nobody noticed, because until now the slides were
// PNGs of themselves. A reading surface may not lose content.
function contentHeight(slide) {
var page = slide.querySelector('.slidev-page') || slide.querySelector('.print-slide-container');
if (!page) return H;
// The container is already scaled by --deck-k, and getBoundingClientRect reports
// POST-transform coordinates. Divide it back out, or --deck-h lands in scaled pixels and
// the CSS scales it a second time.
var k = parseFloat(getComputedStyle(slide).getPropertyValue('--deck-k')) || 1;
var top = page.getBoundingClientRect().top;
var max = H, all = page.querySelectorAll('*');
// An image reaching past 1.5x the frame is a decorative BLEED, not content: Slidev renders it
// oversized and clips it at the 552 frame ON PURPOSE (rustikon 17/23: a Ferris at ~1170px).
// Growing the card to contain it turns the slide into a giant portrait. A small image that
// overflows alongside the text (ontology's svg at ~660px) is NOT a bleed — it grows with the
// text. So: a bleeding image pins the slide to the native frame, and the card's overflow:hidden
// clips it exactly where Slidev does. Only TEXT overflow earns a taller card.
var BLEED = H * 1.5;
for (var i = 0; i < all.length; i++) {
var el = all[i];
var bottom = (el.getBoundingClientRect().bottom - top) / k;
if (bottom > max) max = bottom;
if (/^(img|svg|picture)$/i.test(el.tagName) && bottom > BLEED) return H;
}
return Math.ceil(max);
}
// Measure in whatever mode we are in. A display:none slide has no geometry, so force the
// stacked layout, measure, and restore — synchronously, so the browser never paints the
// intermediate state. Doing this by actually SWITCHING mode created a race: the markup
// ships stacked (the right no-JS fallback), the switch to slider waited on fonts, and a
// reader who pressed "Read all" inside that window was silently thrown back to slider.
function measureAll() {
var prev = deck.dataset.mode;
deck.dataset.mode = 'read';
slides.forEach(function (s) { s.style.setProperty('--deck-h', contentHeight(s)); });
deck.dataset.mode = prev;
}
// The scale CSS cannot express: box width / native width, as a plain number.
var ro = new ResizeObserver(function (entries) {
entries.forEach(function (e) {
// A display:none slide reports 0x0. Believing it writes --deck-k: 0 onto the fifteen
// hidden slides, and when the reader switches back to stacked their cards are zero-height
// until the observer fires again — asynchronously, so anything that measures in between
// sees an empty deck. An observer must not trust a measurement of an element that is not
// in the layout: keep the last good scale.
if (e.contentRect.width > 0) e.target.style.setProperty('--deck-k', e.contentRect.width / W);
});
});
slides.forEach(function (s) { ro.observe(s); });
deck.setAttribute('data-js', '');
// In slider mode the current slide is the one shown. Stacked, it is the one you are
// LOOKING at — which is not the same as the one you last clicked, so the rail follows
// the scroll rather than the click.
var visible = 0;
function mark() {
var cur = deck.dataset.mode === 'slider' ? i : visible;
links.forEach(function (l, k) { l.setAttribute('aria-current', String(k === cur)); });
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
var k = slides.indexOf(e.target);
if (k >= 0) { visible = k; if (deck.dataset.mode === 'read') mark(); }
}
});
}, { rootMargin: '-45% 0px -45% 0px' }); // whichever slide crosses the viewport's middle
slides.forEach(function (s) { io.observe(s); });
function show(k) {
i = (k + n) % n;
slides.forEach(function (s, j) { s.toggleAttribute('data-active', j === i); });
count.textContent = (i + 1) + ' / ' + n;
mark();
}
function setMode(m) {
deck.dataset.mode = m;
deck.querySelectorAll('[data-set-mode]').forEach(function (b) {
b.setAttribute('aria-pressed', String(b.dataset.setMode === m));
});
var slider = m === 'slider';
count.hidden = !slider; navs.hidden = !slider;
// Stacked, the rail is the navigation — a collapsed <details> above 16 slides is off
// screen by slide 5. Open it. In slider mode leave it as the reader left it.
if (slider) show(i); else mark();
}
function goto(k) {
if (deck.dataset.mode === 'slider') { show(k); deck.focus(); }
else { i = k; slides[k].scrollIntoView({ behavior: 'smooth', block: 'start' }); }
// Mobile only (< 60rem, where the index and the slides cannot sit side by side): after a jump,
// close the index so the target slide is not left buried beneath it. On desktop the rail is a
// permanent sidebar — leave it open.
if (!window.matchMedia('(min-width: 60rem)').matches) setToc(false);
}
// The rail shows by default where there is width for a column; narrow, it starts hidden
// so it does not push the deck below the fold. Either way the reader can toggle it.
var wide = window.matchMedia('(min-width: 60rem)');
var toggle = deck.querySelector('.deck-toc-toggle');
function setToc(on) {
deck.dataset.toc = on ? 'on' : 'off';
toggle.setAttribute('aria-expanded', String(on));
}
setToc(wide.matches);
toggle.addEventListener('click', function () { setToc(deck.dataset.toc !== 'on'); });
// The sticky bar has to sit on the page's own background, whatever it is. Measure it
// rather than guess it, and re-measure when the SITE switches theme — it has its own
// toggle, and a bar frozen to the old palette is worse than no bar.
function pageBg() {
var e = deck.parentElement;
while (e) {
var bg = getComputedStyle(e).backgroundColor;
// No regex here on purpose. Inside a template literal every backslash is eaten once
// before the regex engine sees it, and /^rgba(0,0,0,0)$/ silently became a capture
// group that matched nothing — syntactically valid, semantically wrong, invisible to
// a syntax check. String comparison needs no escapes and cannot be mis-escaped.
if (bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)') return bg;
e = e.parentElement;
}
return getComputedStyle(document.body).backgroundColor;
}
function syncPageBg() { deck.style.setProperty('--deck-page-bg', pageBg()); }
syncPageBg();
new MutationObserver(syncPageBg).observe(document.documentElement, {
attributes: true, attributeFilter: ['class', 'data-theme'],
});
// Help panel: a plain show/hide, and its own close button. Its content is server-rendered per
// language (from decks.ncl help_items); JS only toggles visibility.
var help = deck.querySelector('.deck-help');
var helpBtn = deck.querySelector('.deck-help-toggle');
function setHelp(on) {
help.hidden = !on;
helpBtn.setAttribute('aria-expanded', String(on));
}
helpBtn.addEventListener('click', function () { setHelp(help.hidden); });
deck.querySelector('.deck-help-close').addEventListener('click', function () { setHelp(false); });
var lastFocus = null;
function zoomOpen(k) {
lastFocus = document.activeElement;
show(k);
deck.setAttribute('data-zoom', '');
document.body.style.overflow = 'hidden';
deck.focus();
}
function zoomClose() {
if (!deck.hasAttribute('data-zoom')) return;
deck.removeAttribute('data-zoom');
document.body.style.overflow = '';
if (lastFocus && lastFocus.isConnected) lastFocus.focus();
}
// Slidev's copy buttons are in the captured DOM but inert — their handler was Vue.
// Rewire them. innerText, not textContent: it is the RENDERED code, so Shiki's per-line
// spans come back as actual newlines instead of one run-on string.
function copyCode(btn) {
var wrap = btn.closest('.slidev-code-wrapper');
var pre = wrap && wrap.querySelector('pre');
if (!pre) return;
var text = pre.innerText.replace(/\n$/, '');
var done = function () {
wrap.classList.add('deck-copied');
setTimeout(function () { wrap.classList.remove('deck-copied'); }, 1200);
};
function legacy() {
var ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); done(); } finally { ta.remove(); }
}
// The Clipboard API REJECTS when the document is not focused — a real case, not a
// theoretical one. Without the catch, the copy fails in complete silence.
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(done, legacy);
} else {
legacy();
}
}
deck.addEventListener('click', function (e) {
var cp = e.target.closest('.slidev-code-copy');
if (cp) { e.preventDefault(); return copyCode(cp); }
if (e.target.closest('.deck-backdrop')) return zoomClose();
// data-zoom-slide (the button) is deliberately NOT data-zoom (the deck's open state).
var z = e.target.closest('[data-zoom-slide]');
if (z) return deck.hasAttribute('data-zoom') ? zoomClose() : zoomOpen(+z.dataset.zoomSlide);
var m = e.target.closest('[data-set-mode]');
if (m) return setMode(m.dataset.setMode);
var t = e.target.closest('[data-goto]');
if (t) return goto(+t.dataset.goto);
var nav = e.target.closest('.deck-nav');
if (nav) show(i + (+nav.dataset.dir));
});
// Keys are scoped to the deck holding focus. A document-wide Space handler would steal
// page scrolling from every reader who never asked the deck for anything.
deck.addEventListener('keydown', function (e) {
var zoomed = deck.hasAttribute('data-zoom');
if (zoomed && e.key === 'Escape') { e.preventDefault(); return zoomClose(); }
// Zoomed, the keys must drive even stacked: a lightbox you can leave but not walk is
// a dead end.
if (!zoomed && deck.dataset.mode !== 'slider') return;
if (e.target.closest('.deck-toc')) return;
var fwd = e.key === 'ArrowRight' || e.key === 'ArrowDown' || (e.key === ' ' && !e.shiftKey);
var back = e.key === 'ArrowLeft' || e.key === 'ArrowUp' || (e.key === ' ' && e.shiftKey);
if (fwd) { e.preventDefault(); show(i + 1); }
else if (back) { e.preventDefault(); show(i - 1); }
else if (e.key === 'Home') { e.preventDefault(); show(0); }
else if (e.key === 'End') { e.preventDefault(); show(n - 1); }
});
// Printing in slider mode would emit exactly one slide.
window.addEventListener('beforeprint', function () { setMode('read'); });
// ── DÓNDE ATERRIZA UN ENLACE ────────────────────────────────────────────────────────
//
// Dos formas, y el lector no tiene por qué saber cuál le tocó:
// #slide-7 posicional — toda slide la tiene, es la URL de «lo que estoy viendo».
// #ncl nombrada — declarada en el frontmatter de la slide; es la que se escribe
// en el léxico, porque sobrevive a que el deck se reordene.
// La nombrada se resuelve por el DOM (getElementById → la .deck-slide que la contiene) en
// vez de por una tabla en este script: el ancla ya está en el marcado, y una segunda copia
// aquí es una segunda cosa que se puede desincronizar de la primera.
//
// El navegador NO puede hacer este salto solo. En modo slider la slide destino está en
// display:none y un ancla dentro de ella no tiene geometría: el scroll nativo no va a
// ninguna parte. Por eso el destino se ACTIVA antes de mirarlo, y por eso el id en el
// marcado no basta por sí solo.
function slideFromHash() {
var h = location.hash;
if (!h || h === '#') return -1;
var num = h.match(/^#slide-(\d+)$/);
if (num) {
var k = +num[1] - 1;
return (k >= 0 && k < n) ? k : -1;
}
// Un id cualquiera del deck: puede ser el ancla nombrada o algo dentro de una slide.
var el = null;
try { el = document.getElementById(decodeURIComponent(h.slice(1))); } catch (e) { el = null; }
var fig = el && el.closest ? el.closest('.deck-slide') : null;
return fig ? slides.indexOf(fig) : -1;
}
// LA BARRA SE MIDE; NO SE ADIVINA. Es sticky en top:0, así que tapa la cabecera de la slide a
// la que acabas de saltar — el título cortado justo debajo. El CSS deja 4rem de suelo, pero la
// barra crece con el tipo de letra del sitio y con el ancho (los botones se envuelven), y un
// número escrito a mano está bien el día que se escribe. scroll-margin es lo único que
// scrollIntoView respeta, así que la medida va ahí.
function syncScrollMargin() {
var bar = deck.querySelector('.deck-bar');
var g = (bar ? Math.ceil(bar.getBoundingClientRect().height) : 48) + 12;
slides.forEach(function (s) { s.style.scrollMarginTop = g + 'px'; });
}
syncScrollMargin();
window.addEventListener('resize', syncScrollMargin);
function gotoHash(smooth) {
var k = slideFromHash();
if (k < 0) return false;
// No es goto(k): aquí no queremos cerrar el índice ni robar el foco de la página a la
// que el lector acaba de llegar. Solo poner delante la slide que pidió la URL.
if (deck.dataset.mode === 'slider') show(k);
else i = k;
// Y SE SCROLLEA EN LOS DOS MODOS. En slider esto faltaba: show(k) solo activa la slide, así
// que lo único que movía la página era el scroll NATIVO del navegador al ancla — hecho antes
// de que este script corra y con el deck todavía apilado. Al cambiar a slider el documento
// encoge de 16 tarjetas a una, ese scroll queda apuntando a un layout que ya no existe, y el
// lector aterriza con el título medio tapado por la barra. La slide correcta, mal puesta.
slides[k].scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'start' });
return true;
}
// Measure AFTER the webfonts land. With the fallback font the text is shorter and every
// slide "fits"; once Nunito Sans arrives it grows and spills. Measured before fonts, every
// --deck-h came back 552 and nine slides were still cut.
// And measure while still stacked: a display:none slide has no geometry at all. So the
// switch to slider waits for the measurement, not the other way round.
// Take the mode immediately — no waiting, no race with the reader.
measureAll();
setMode('slider');
gotoHash(false);
// Un enlace al MISMO deck con otro fragmento no recarga la página: sin esto, pulsar NCL y
// luego DAG desde otro tooltip cambia la URL y deja la slide donde estaba. La URL diría una
// cosa y la pantalla otra — que es la deriva de siempre, servida a 200.
window.addEventListener('hashchange', function () { gotoHash(true); });
// Then measure again once the webfonts land: with the fallback face the text is shorter
// and every slide "fits", so a pre-font measurement returns 552 for all of them and nine
// slides stay cut. This pass changes no mode and interrupts nothing.
if (document.fonts && document.fonts.ready) document.fonts.ready.then(measureAll);
window.addEventListener('resize', measureAll);
})();

Some files were not shown because too many files have changed in this diff Show more