chore: update site content
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
182
site/site/content/adr/en/accepted/adr-020.md
Normal 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/<id>/
|
||||
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:
|
||||
"ontoref carries ADRs and modes about its own site, doors, campaigns and CV —
|
||||
what use are those to a project that merely adopts ontoref?" 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 "what of me is yours". 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 "a named region of the repo with visibility rules per
|
||||
mode", and ontoref's own manifest already named a `protocol` layer ("the
|
||||
contract layer that projects implement") beside a `self-description` layer
|
||||
("ontoref consuming ontoref"). 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 "layer" sat in the
|
||||
schema while this ADR worried only about colliding with "level" (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 <caller>/extensions/<this-project>/
|
||||
<caller>/catalog/components/<...>/ (when consuming)
|
||||
<workspace>/infra/<ws>/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
|
||||
"additive" 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 "what of me is yours" 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
|
||||
"of what use are ontoref's own site ADRs to provisioning?": 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.
|
||||
"Forgejo always canonical · GitHub always a one-way mirror · vault never
|
||||
mirrored" 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/<self>/`, 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>
|
||||
12
site/site/content/adr/en/accepted/adr-020.ncl
Normal 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"],
|
||||
},
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <-> site/public/r mirror MUST be typed by file ownership, never by direction: content indexes flow r -> public/r with --delete, and the four nu artifacts flow public/r -> 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 <-> site/public/r mirror MUST be typed by file ownership, never by direction: content indexes flow r -> public/r with --delete, and the four nu artifacts flow public/r -> 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>
|
||||
|
||||
|
|
|
|||
134
site/site/content/adr/en/accepted/adr-071.md
Normal 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 "reflexión" (3×)
|
||||
`gate` glossary kept the English (3×) · positioning wrote "compuertas"</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` → "puerta" (the funnel entry,
|
||||
ADR-064). Its OWN Spanish definition of `testigo-verificado` was rendering `gate` → "puerta" 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 ("mergear"). 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, "hacer un commit" — 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>· "puerta" 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`
|
||||
· "build system", "un fork", "el deploy" are BORROWED COMPOUNDS, entering the language as units.
|
||||
Demanding «compilar» inside «cada build system usa DAGs» is nonsense. → `gate_source = false`
|
||||
· `tags = ["witness", "adr-070"]` 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. → `<!-- lang:en -->`</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 "Nota sobre el
|
||||
lenguaje" 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 (`<!-- lang:xx -->`, 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>
|
||||
12
site/site/content/adr/en/accepted/adr-071.ncl
Normal 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"],
|
||||
},
|
||||
}
|
||||
168
site/site/content/adr/en/accepted/adr-072.md
Normal 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 "does this route have a title?"
|
||||
instead of "does this route have a body renderer?" — 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 "DOES THE
|
||||
FILE EXIST?" 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. "Write it in the howto." "Remember
|
||||
to update the check." 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 "the check should be updated" — 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 "does the file
|
||||
exist?" — 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 "5
|
||||
cards" 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. "Is the disk right?" is
|
||||
answerable offline, always. "Is the site right?" 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 "I found no cards" 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 "I do not recognise what I am watching" 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>"I did not look." — 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.
|
||||
"I cannot know this." — 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.
|
||||
"I am no longer complete." — THIS ADR. The extent grew and my coverage did not.
|
||||
"I no longer recognise — THIS ADR, turned on the witness. The thing I watch was re-skinned
|
||||
what I am watching." and I match nothing in it. Without this sentence the check reports
|
||||
"no cards found", 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 "the check passes" but "the
|
||||
check has been SEEN TO REFUSE the new capability's failure mode".</p>
|
||||
<p>AND A CHECK MEASURES THE CAPABILITY, NOT ITS PROXY. "Has a title key" is a proxy for "renders its
|
||||
content", 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 "trivial".</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(|_| <the path that just failed>)` — 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>
|
||||
12
site/site/content/adr/en/accepted/adr-072.ncl
Normal 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"],
|
||||
},
|
||||
}
|
||||
|
|
@ -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/<id>/
|
||||
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 <caller>/extensions/<this-project>/
|
||||
<caller>/catalog/components/<...>/ (when consuming)
|
||||
<workspace>/infra/<ws>/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/<self>/`, 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>
|
||||
|
|
@ -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"],
|
||||
},
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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 —
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 —
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
169
site/site/content/resources/en/decks/ai-infra.md
Normal file
162
site/site/content/resources/en/decks/rustikon.md
Normal file
169
site/site/content/resources/es/presentaciones/ai-infra.md
Normal file
162
site/site/content/resources/es/presentaciones/rustikon.md
Normal 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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
10
site/site/i18n/locales/en/pages/conceptos.ftl
Normal 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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
4
site/site/i18n/locales/en/pages/home_taglines.ftl
Normal 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?
|
||||
55
site/site/i18n/locales/en/pages/lenguaje.ftl
Normal 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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
10
site/site/i18n/locales/es/pages/conceptos.ftl
Normal 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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
4
site/site/i18n/locales/es/pages/home_taglines.ftl
Normal 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?
|
||||
56
site/site/i18n/locales/es/pages/lenguaje.ftl
Normal 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.
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">Pé</tspan><tspan class="cls-3" x="179.55" y="0">rez</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 987 B |
1478
site/site/public/images/decks/ai-infra-en/deck.css
Normal file
340
site/site/public/images/decks/ai-infra-en/deck.js
Normal 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);
|
||||
})();
|
||||
|
|
@ -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">Pé</tspan><tspan class="cls-3" x="179.55" y="0">rez</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 987 B |
1477
site/site/public/images/decks/ai-infra-es/deck.css
Normal file
340
site/site/public/images/decks/ai-infra-es/deck.js
Normal 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);
|
||||
})();
|
||||
BIN
site/site/public/images/decks/ai-infra/thumb-en.avif
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
site/site/public/images/decks/ai-infra/thumb-en.png
Normal file
|
After Width: | Height: | Size: 658 KiB |
BIN
site/site/public/images/decks/ai-infra/thumb-en.webp
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
site/site/public/images/decks/ai-infra/thumb-es.avif
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
site/site/public/images/decks/ai-infra/thumb-es.png
Normal file
|
After Width: | Height: | Size: 650 KiB |
BIN
site/site/public/images/decks/ai-infra/thumb-es.webp
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
|
@ -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">Pé</tspan><tspan class="cls-3" x="179.55" y="0">rez</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 987 B |
|
|
@ -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&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 |
|
|
@ -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&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 |
|
|
@ -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);
|
||||
})();
|
||||