diff --git a/.gitignore b/.gitignore
index 75c9363..fdcd584 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,13 @@ fsh-generated/
template/
input-cache/
output/
+
+*.bkp
+
+__pycache__
+
+# draw.io autosave files
+_diagrams/.$*
+
+# the publisher writes this to the root on every build
+Requirements-fromNarrative.json
diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc
index b06c803..9cbec6a 100644
--- a/.markdownlint.jsonc
+++ b/.markdownlint.jsonc
@@ -7,5 +7,10 @@
// kramdown attaches `{: .table ...}` to the table above it; a GFM parser
// reads that line as one more (malformed) row.
"MD055": false,
- "MD056": false
+ "MD056": false,
+ // IHE use cases repeat "Descripción del caso de uso" / "Flujo del proceso"
+ // under each case; only siblings must be unique.
+ "MD024": { "siblings_only": true },
+ // Bootstrap tabs (the template ships the plugin) are the only raw HTML.
+ "MD033": { "allowed_elements": ["ul", "li", "a", "div"] }
}
diff --git a/Makefile b/Makefile
index e2b2e78..102a699 100644
--- a/Makefile
+++ b/Makefile
@@ -5,12 +5,13 @@
# anywhere else.
.DEFAULT_GOAL := help
-.PHONY: help preview site docs all format tools clean
+.PHONY: help preview site docs diagrams all format tools clean
help:
@echo "make preview write, with live reload http://localhost:4000"
@echo "make site build the site dist/site/index.html"
@echo "make docs build PDF, DOCX, llms.txt dist/"
+ @echo "make diagrams render the PlantUML sources input/images/"
@echo "make all site + docs"
@echo "make format format the pages input/pagecontent/"
@echo "make tools download the IG Publisher .work/tools/"
@@ -25,6 +26,10 @@ site:
docs:
@_scripts/build-docs.sh
+# Needs the publisher jar (make tools): PlantUML ships inside it.
+diagrams:
+ @_scripts/build-diagrams.sh
+
all: site docs
# Fixes blank lines, list markers, heading and emphasis style, trailing spaces;
diff --git a/_diagrams/central-architecture-diagram.drawio b/_diagrams/central-architecture-diagram.drawio
new file mode 100644
index 0000000..33d6e23
--- /dev/null
+++ b/_diagrams/central-architecture-diagram.drawio
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_diagrams/hix-actores-hospital.plantuml b/_diagrams/hix-actores-hospital.plantuml
new file mode 100644
index 0000000..72ab4ee
--- /dev/null
+++ b/_diagrams/hix-actores-hospital.plantuml
@@ -0,0 +1,53 @@
+@startuml hix-actores-hospital
+!pragma layout smetana
+hide stereotype
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam nodesep 12
+skinparam ranksep 18
+skinparam rectangle {
+ BorderColor #555555
+ RoundCorner 12
+ FontStyle bold
+}
+skinparam rectangle<> {
+ BackgroundColor white
+ BorderColor #444444
+ BorderStyle dashed
+ RoundCorner 0
+ FontStyle plain
+ FontSize 10
+}
+skinparam arrow {
+ Color #333333
+ FontSize 9
+}
+left to right direction
+rectangle "Hospital (consumidor)" as HOSP #DAE8FC {
+ rectangle "IUA Authorization Client" <> as H1
+ rectangle "MHD Document Consumer" <> as H2
+ rectangle "PIXm / PDQm Consumer" <> as H3
+}
+rectangle "Authorization Server (STS)" as STS #F5F5F5 {
+ rectangle "IUA Authorization Server" <> as STS1
+ rectangle "OpenID Provider" <> as STS2
+ rectangle "PIXm Consumer" <> as STS3
+}
+rectangle "Record Locator Service (Mediador)" as RLS #D5E8D4 {
+ rectangle "IUA Resource Server" <> as RLS1
+ rectangle "IUA Authorization Client" <> as RLS0
+ rectangle "MHD Document Responder" <> as RLS2
+ rectangle "MHD Document Consumer (delegated)" <> as RLS3
+ rectangle "PIXm / PDQm Consumer (delegated)" <> as RLS4
+}
+rectangle "Custodio" as CUST #FFE6CC {
+ rectangle "IUA Resource Server" <> as CUST1
+ rectangle "MHD Document Responder" <> as CUST2
+}
+H1 --> STS1 : [ITI-71] Get Access Token\natención: purpose_of_use = TREAT\nemergencia: purpose_of_use = ETREAT
+H3 --> RLS4 : atención: [ITI-83] resuelve su identificador local\nemergencia: [ITI-78] busca por datos demográficos
+H2 --> RLS2 : [ITI-67] Find Document References\n[ITI-68] Retrieve Document
+STS1 <-- RLS0 : [HIX-1] Custodian Token Exchange
+RLS3 --> CUST2 : [ITI-68] Retrieve Document\ntoken derivado, aud = custodio
+@enduml
diff --git a/_diagrams/hix-actores-laboratorio.plantuml b/_diagrams/hix-actores-laboratorio.plantuml
new file mode 100644
index 0000000..7886c0b
--- /dev/null
+++ b/_diagrams/hix-actores-laboratorio.plantuml
@@ -0,0 +1,50 @@
+@startuml hix-actores-laboratorio
+!pragma layout smetana
+hide stereotype
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam nodesep 12
+skinparam ranksep 18
+skinparam rectangle {
+ BorderColor #555555
+ RoundCorner 12
+ FontStyle bold
+}
+skinparam rectangle<> {
+ BackgroundColor white
+ BorderColor #444444
+ BorderStyle dashed
+ RoundCorner 0
+ FontStyle plain
+ FontSize 10
+}
+skinparam arrow {
+ Color #333333
+ FontSize 9
+}
+left to right direction
+rectangle "Laboratorio (custodio)" as LAB #DAE8FC {
+ rectangle "IUA Authorization Client" <> as L1
+ rectangle "PIXm Patient Identity Source" <> as L2
+ rectangle "MHD Document Source" <> as L3
+ rectangle "MHD Document Responder" <> as L4
+ rectangle "IUA Resource Server" <> as L5
+}
+rectangle "Authorization Server (STS)" as STS #F5F5F5 {
+ rectangle "IUA Authorization Server" <> as STS1
+ rectangle "OpenID Provider" <> as STS2
+ rectangle "PIXm Consumer" <> as STS3
+}
+rectangle "Record Locator Service (Mediador)" as RLS #D5E8D4 {
+ rectangle "IUA Authorization Client" <> as RLS0
+ rectangle "IUA Resource Server" <> as RLS1
+ rectangle "MHD Document Consumer (delegated)" <> as RLS3
+}
+L1 --> STS1 : [ITI-71] Get Access Token\npurpose_of_use = TREAT
+L2 --> RLS : [ITI-104] Patient Identity Feed\nidentidad local + identificador nacional
+L3 --> RLS : [ITI-65] Provide Document Bundle\npuntero con custodio y securityLabel
+STS1 <-u- RLS0 : [HIX-1] Custodian Token Exchange
+RLS3 --> L4 : [ITI-68] Retrieve Document\ntoken derivado, aud = laboratorio\ncuando otro miembro recupera
+L4 -[hidden]-> RLS1
+@enduml
diff --git a/_diagrams/hix-actores-paciente.plantuml b/_diagrams/hix-actores-paciente.plantuml
new file mode 100644
index 0000000..4fd929f
--- /dev/null
+++ b/_diagrams/hix-actores-paciente.plantuml
@@ -0,0 +1,51 @@
+@startuml hix-actores-paciente
+!pragma layout smetana
+hide stereotype
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam nodesep 12
+skinparam ranksep 18
+skinparam rectangle {
+ BorderColor #555555
+ RoundCorner 12
+ FontStyle bold
+}
+skinparam rectangle<> {
+ BackgroundColor white
+ BorderColor #444444
+ BorderStyle dashed
+ RoundCorner 0
+ FontStyle plain
+ FontSize 10
+}
+skinparam arrow {
+ Color #333333
+ FontSize 9
+}
+left to right direction
+rectangle "Aplicación del paciente" as APP #DAE8FC {
+ rectangle "IUA Authorization Client" <> as APP1
+ rectangle "MHD Document Consumer" <> as APP2
+}
+rectangle "Authorization Server (STS)" as STS #F5F5F5 {
+ rectangle "IUA Authorization Server" <> as STS1
+ rectangle "OpenID Provider" <> as STS2
+ rectangle "PIXm Consumer" <> as STS3
+}
+rectangle "Record Locator Service (Mediador)" as RLS #D5E8D4 {
+ rectangle "IUA Resource Server" <> as RLS1
+ rectangle "IUA Authorization Client" <> as RLS0
+ rectangle "MHD Document Responder" <> as RLS2
+ rectangle "MHD Document Consumer (delegated)" <> as RLS3
+ rectangle "PIXm / PDQm Consumer (delegated)" <> as RLS4
+}
+rectangle "Custodio" as CUST #FFE6CC {
+ rectangle "IUA Resource Server" <> as CUST1
+ rectangle "MHD Document Responder" <> as CUST2
+}
+APP1 --> STS1 : [HIX-2] Patient Application Launch\npurpose_of_use = PATRQT, FAMRQT o PWATRNY\npaciente = identidad maestra
+APP2 --> RLS2 : [ITI-67] Find Document References\n[ITI-68] Retrieve Document\ntoken aud = RLS, confinado al paciente
+STS1 <-- RLS0 : [HIX-1] Custodian Token Exchange
+RLS3 --> CUST2 : [ITI-68] Retrieve Document\ntoken derivado, aud = custodio
+@enduml
diff --git a/_diagrams/hix-delegacion.plantuml b/_diagrams/hix-delegacion.plantuml
new file mode 100644
index 0000000..8a976c5
--- /dev/null
+++ b/_diagrams/hix-delegacion.plantuml
@@ -0,0 +1,48 @@
+@startuml hix-delegacion
+!pragma layout smetana
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam rectangle {
+ BackgroundColor white
+ BorderColor #555555
+ RoundCorner 8
+}
+skinparam arrow {
+ Color #333333
+ FontSize 9
+}
+skinparam note {
+ BackgroundColor #FFFDF0
+ BorderColor #999999
+ FontSize 9
+}
+left to right direction
+
+rectangle "Miembros" as MIEMBROS #F4F4F4 {
+ rectangle "Miembro\nsolicitante" as M
+}
+
+rectangle "Infraestructura central" as CENTRO #EEF5EE {
+ rectangle "Authorization\nServer" as AS
+ rectangle "Record Locator\nService (PEP)" as RLS
+ rectangle "Document\nRegistry" as REG
+}
+
+rectangle "Custodios" as CUSTODIOS #F4F4F4 {
+ rectangle "Custodio A" as A
+ rectangle "Custodio B" as B
+}
+
+M --> RLS : token del miembro\naud = RLS
+RLS --> AS : intercambio [HIX-1]\nresource = custodio X
+AS --> RLS : token derivado\naud = custodio X\nsub = miembro\nact = RLS
+RLS --> REG : token\naud = Document Registry
+RLS --> A : token\naud = Custodio A
+RLS --> B : token\naud = Custodio B
+note bottom of B
+ Cada destino valida el token por sí mismo,
+ con las claves públicas del Authorization Server,
+ sin llamar a nadie.
+end note
+@enduml
diff --git a/_diagrams/hix-flujo-identidad.plantuml b/_diagrams/hix-flujo-identidad.plantuml
new file mode 100644
index 0000000..9853f34
--- /dev/null
+++ b/_diagrams/hix-flujo-identidad.plantuml
@@ -0,0 +1,39 @@
+@startuml hix-flujo-identidad
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam sequenceMessageAlign center
+skinparam ParticipantPadding 10
+skinparam sequence {
+ ArrowColor #333333
+ LifeLineBorderColor #999999
+ ParticipantBorderColor #555555
+ ParticipantBackgroundColor white
+ GroupBorderColor #999999
+}
+
+participant "Fuente autoritativa\n(p. ej. EDUS)" as SRC
+participant "Registro de\nidentidad maestra" as PMIR
+participant "Record Locator\nService" as RLS
+participant "Custodio" as K
+participant "Consumidor" as C
+
+== Identidad maestra ==
+SRC -> PMIR : [ITI-93] Mobile Patient Identity Feed\n(identidad verificada)
+PMIR -> PMIR : crea o actualiza\nla identidad maestra
+PMIR --> SRC : 200
+
+== Identidad local de un miembro ==
+K -> RLS : [ITI-104] Patient Identity Feed\n(Patient local + identificador comunitario)
+RLS -> RLS : dominio = organización del token
+RLS -> PMIR : [ITI-104] (delegado)
+PMIR -> PMIR : vincula la identidad local\ncon la identidad maestra
+PMIR --> RLS : 200, o 422 si la persona\nno existe en la comunidad
+RLS --> K : resultado
+
+== Resolución ==
+C -> RLS : [ITI-83] Cross-reference Query\n(identificador local o comunitario)
+RLS -> PMIR : [ITI-83] (delegado)
+PMIR --> RLS : identificadores de la persona
+RLS --> C : identificadores permitidos\npor la política
+@enduml
diff --git a/_diagrams/hix-flujo-localizacion.plantuml b/_diagrams/hix-flujo-localizacion.plantuml
new file mode 100644
index 0000000..b7d65b4
--- /dev/null
+++ b/_diagrams/hix-flujo-localizacion.plantuml
@@ -0,0 +1,55 @@
+@startuml hix-flujo-localizacion
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam sequenceMessageAlign center
+skinparam ParticipantPadding 8
+skinparam sequence {
+ ArrowColor #333333
+ LifeLineBorderColor #999999
+ ParticipantBorderColor #555555
+ ParticipantBackgroundColor white
+ GroupBorderColor #999999
+}
+
+participant "Consumidor" as C
+participant "Authorization\nServer" as AS
+participant "Record Locator\nService" as RLS
+participant "Registro de\nidentidad maestra" as PMIR
+participant "Document\nRegistry" as REG
+participant "Directorio" as DIR
+participant "Custodio" as K
+
+== Localización ==
+C -> AS : [ITI-71] Get Access Token
+AS --> C : token (audiencia: RLS)
+C -> RLS : [ITI-67] Find Document References\n(patient) [ITI-72]
+RLS -> AS : [ITI-102] Introspect Token
+AS --> RLS : activo, organización, propósito
+RLS -> PMIR : [ITI-83] identidad maestra
+PMIR --> RLS : identidad maestra
+RLS -> AS : [HIX-1] intercambio\n(resource = Document Registry)
+AS --> RLS : JWT (aud = registro, sub = consumidor,\nact = RLS, vida ≤ 120 s)
+RLS -> REG : [ITI-67] [ITI-72]
+REG --> RLS : punteros
+RLS -> RLS : decisión de divulgación por puntero\nURL de contenido → RLS
+RLS --> C : Bundle de punteros divulgados
+
+== Recuperación ==
+C -> RLS : [ITI-68] Retrieve Document\n(URL del RLS) [ITI-72]
+RLS -> AS : [ITI-102] Introspect Token
+RLS -> RLS : relee el puntero\nrecomprueba la divulgación
+RLS -> DIR : [ITI-90] custodio → endpoint
+DIR --> RLS : endpoint activo del custodio
+RLS -> AS : [HIX-1] Custodian Token Exchange\n(resource = custodio)
+AS --> RLS : JWT (aud = custodio, sub = consumidor,\nact = RLS, vida ≤ 120 s)
+RLS -> K : [ITI-68] Retrieve Document [ITI-72]
+K -> K : validación local del JWT
+alt el custodio responde
+ K --> RLS : documento
+ RLS --> C : documento
+else el custodio no responde
+ RLS --> C : OperationOutcome (transitorio)
+end
+note over C, K : Cada llamada del RLS a un servicio central o a un custodio va precedida de un intercambio [HIX-1] a nombre del solicitante; se muestra solo donde importa.\nCada actor registra sus eventos [ITI-20] bajo el identificador de correlación de la operación.
+@enduml
diff --git a/_diagrams/hix-flujo-paciente.plantuml b/_diagrams/hix-flujo-paciente.plantuml
new file mode 100644
index 0000000..42e8e53
--- /dev/null
+++ b/_diagrams/hix-flujo-paciente.plantuml
@@ -0,0 +1,44 @@
+@startuml hix-flujo-paciente
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam sequenceMessageAlign center
+skinparam ParticipantPadding 10
+skinparam sequence {
+ ArrowColor #333333
+ LifeLineBorderColor #999999
+ ParticipantBorderColor #555555
+ ParticipantBackgroundColor white
+ ActorBorderColor #555555
+ ActorBackgroundColor white
+ GroupBorderColor #999999
+}
+
+actor "Persona" as P
+participant "Aplicación\ndel paciente" as APP
+participant "Authorization\nServer" as AS
+participant "Fuente autoritativa\n(p. ej. EDUS)" as SRC
+participant "Registro de\nidentidad maestra" as PMIR
+participant "Record Locator\nService" as RLS
+
+== Enrolamiento (una vez) ==
+P -> AS : crea su cuenta
+AS -> SRC : verificación de identidad
+SRC --> AS : identidad verificada
+AS --> P : cuenta enlazada a la\nidentidad verificada
+
+== Lanzamiento [HIX-2] ==
+APP -> AS : authorization code + PKCE,\ncontexto de lanzamiento de paciente
+AS -> P : autenticación y consentimiento
+P --> AS : autoriza
+AS -> PMIR : [ITI-83] identidad verificada → identidad maestra
+PMIR --> AS : identidad maestra
+AS --> APP : token (audiencia: RLS,\ncontexto de paciente = identidad maestra)
+
+== Acceso ==
+APP -> RLS : [ITI-67] / [ITI-68] [ITI-72]
+RLS -> AS : [ITI-102] Introspect Token
+AS --> RLS : activo, contexto de paciente
+RLS -> RLS : confina la operación\nal paciente del contexto
+RLS --> APP : punteros o documento
+@enduml
diff --git a/_diagrams/hix-flujo-publicacion.plantuml b/_diagrams/hix-flujo-publicacion.plantuml
new file mode 100644
index 0000000..d3e57a3
--- /dev/null
+++ b/_diagrams/hix-flujo-publicacion.plantuml
@@ -0,0 +1,40 @@
+@startuml hix-flujo-publicacion
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam sequenceMessageAlign center
+skinparam ParticipantPadding 10
+skinparam sequence {
+ ArrowColor #333333
+ LifeLineBorderColor #999999
+ ParticipantBorderColor #555555
+ ParticipantBackgroundColor white
+ GroupBorderColor #999999
+}
+
+participant "Custodio" as K
+participant "Authorization\nServer" as AS
+participant "Record Locator\nService" as RLS
+participant "Registro de\nidentidad maestra" as PMIR
+participant "Document\nRegistry" as REG
+
+K -> K : almacena el documento\nen su repositorio
+K -> AS : [ITI-71] Get Access Token
+AS --> K : token (audiencia: RLS)
+K -> RLS : [ITI-65] Provide Document Bundle\n(metadatos, URL relativa) [ITI-72]
+RLS -> AS : [ITI-102] Introspect Token
+AS --> RLS : activo, organización, propósito
+RLS -> RLS : custodio = organización del token\nURL relativa\netiqueta de confidencialidad presente
+RLS -> PMIR : [ITI-83] paciente local → identidad maestra
+PMIR --> RLS : identidad maestra
+RLS -> AS : [HIX-1] Token Exchange\n(resource = Document Registry)
+AS --> RLS : JWT (aud = registro, sub = custodio,\nact = RLS, vida ≤ 120 s)
+RLS -> REG : [ITI-65] puntero con\nsubject = identidad maestra [ITI-72]
+REG --> RLS : 200
+RLS --> K : 200
+alt la comunidad rechaza la publicación
+ RLS --> K : 4xx con OperationOutcome
+ K -> K : revierte el documento local
+end
+note over K, REG : Cada llamada del RLS a un servicio central o a un custodio va precedida de un intercambio [HIX-1] a nombre del solicitante.\nCada actor registra sus eventos [ITI-20] bajo el identificador de correlación de la operación.
+@enduml
diff --git a/_diagrams/hix-master-patient-index.plantuml b/_diagrams/hix-master-patient-index.plantuml
new file mode 100644
index 0000000..7666183
--- /dev/null
+++ b/_diagrams/hix-master-patient-index.plantuml
@@ -0,0 +1,31 @@
+@startuml hix-master-patient-index
+!pragma layout smetana
+skinparam shadowing false
+skinparam defaultFontName Verdana
+skinparam defaultFontSize 11
+skinparam rectangle {
+ BackgroundColor white
+ BorderColor #555555
+ RoundCorner 8
+}
+skinparam arrow {
+ Color #333333
+ FontSize 9
+}
+
+rectangle "Hospital A" as A #F4F4F4
+rectangle "Fuente autoritativa de identidad\n(p. ej. EDUS)" as SRC #F8F8F8
+rectangle "Clínica B" as B #F4F4F4
+
+rectangle "Registro de identidad maestra" as MPI #EEF5EE {
+ rectangle "Identidad local\n\nDominio del Hospital A\nPatient/349832" as LA
+ rectangle "Identidad maestra\nuna por persona\n\nDominio verificado cédula 1-0234-0567" as MASTER
+ rectangle "Identidad local\n\nDominio de la Clínica B\nPatient/123e4567" as LB
+}
+
+A --> LA : ITI-104\nPatient/349832 + cédula
+SRC --> MASTER : ITI-93\ncrea
+B --> LB : ITI-104\nPatient/123e4567 + cédula
+LA -right-> MASTER : enlace
+MASTER <-left- LB : enlace
+@enduml
diff --git a/_scripts/__pycache__/pageorder.cpython-314.pyc b/_scripts/__pycache__/pageorder.cpython-314.pyc
deleted file mode 100644
index a1f9032..0000000
Binary files a/_scripts/__pycache__/pageorder.cpython-314.pyc and /dev/null differ
diff --git a/_scripts/assemble.py b/_scripts/assemble.py
index 7acb281..2c5186f 100644
--- a/_scripts/assemble.py
+++ b/_scripts/assemble.py
@@ -10,10 +10,25 @@
work = Path(sys.argv[1])
cfg = pageorder.config()
+DEPENDENCY_TABLE = "\n".join(
+ ["| Paquete | Versión | Aporta |", "| --- | --- | --- |"]
+ + [f"| {name} | {version} | {reason} |" for name, version, reason in pageorder.dependencies(cfg)]
+)
+
names = []
for name, title, depth in pageorder.pages(cfg):
body = (ROOT / "input" / "pagecontent" / name).read_text()
body = re.sub(r"^\{:.*\}$", "", body, flags=re.M)
+ # kramdown abbreviations (`*[PMIR]: ...`) become tooltips on the site; pandoc has no equivalent
+ body = re.sub(r"^\*\[[^\]]+\]: .*$", "", body, flags=re.M)
+ # The dependency table is a fragment the publisher generates and Jekyll
+ # includes. Pandoc gets the same rows straight from sushi-config.yaml.
+ body = body.replace("{% include dependency-table.xhtml %}", DEPENDENCY_TABLE)
+
+ # The publisher copies input/images/ to the root of the site, so a page
+ # refers to a figure by its bare name. Pandoc runs from the repository and
+ # needs the real path.
+ body = re.sub(r"\]\((?!\w+:)([^)/]+\.svg)\)", r"](input/images/\1)", body)
# A page's content starts at ### because the site renders the title as the
# h2. A top-level page becomes an h1 here, so its content moves up one level
diff --git a/_scripts/build-diagrams.sh b/_scripts/build-diagrams.sh
new file mode 100755
index 0000000..8b390a6
--- /dev/null
+++ b/_scripts/build-diagrams.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# Renders the PlantUML figures into input/images/, where both consumers find
+# them: the IG Publisher copies that directory into the site, and pandoc reads
+# it from the source tree for the PDF and DOCX.
+#
+# PlantUML ships inside the IG Publisher jar, so there is nothing else to
+# install. Block diagrams set `!pragma layout smetana` because Graphviz is not
+# a dependency of this repo.
+#
+# .drawio sources are not rendered here: draw.io has no headless exporter on
+# this machine. Export them from the editor straight into input/images/, with
+# the labels set to plain text ("Formatted Text" off), so the SVG carries
+# ordinary elements that every renderer draws.
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+if [ ! -f .work/tools/publisher.jar ]; then
+ echo "The publisher is missing. Run 'make tools' first." >&2
+ exit 1
+fi
+
+mkdir -p input/images
+
+java -cp .work/tools/publisher.jar net.sourceforge.plantuml.Run \
+ -tsvg -o "$(pwd)/input/images" _diagrams/*.plantuml
+
+# Recent JDKs serialise PlantUML's DOM with xmlns="" on every child element
+# (the "not created as namespace-aware" warning). That drops the elements out
+# of the SVG namespace and browsers render a blank image. Strip it.
+for src in _diagrams/*.plantuml; do
+ sed -i 's/ xmlns=""//g' "input/images/$(basename "${src%.plantuml}").svg"
+done
+
+ls input/images/*.svg
diff --git a/_scripts/pageorder.py b/_scripts/pageorder.py
index f73ae16..30be024 100644
--- a/_scripts/pageorder.py
+++ b/_scripts/pageorder.py
@@ -26,6 +26,19 @@ def folded(key, cfg=None):
return " ".join(line.strip() for line in m.group(1).split("\n") if line.strip())
+def dependencies(cfg=None):
+ """(package, version, reason) for each entry under `dependencies:`."""
+ cfg = cfg if cfg is not None else config()
+ m = re.search(r"^dependencies:\n(.*?)(?=^\S)", cfg, re.S | re.M)
+
+ out = []
+ for name, body in re.findall(r"^ ([\w.-]+):\n((?: .*\n)+)", m.group(1) if m else "", re.M):
+ fields = dict(re.findall(r"^ (\w+):\s*(.+?)\s*$", body, re.M))
+ out.append((name, fields.get("version", ""), fields.get("reason", "").strip("\"'")))
+
+ return out
+
+
def pages(cfg=None):
"""(filename, title, depth) in declaration order; depth 1 = top level."""
cfg = cfg if cfg is not None else config()
diff --git a/_scripts/preview.py b/_scripts/preview.py
index e64e06f..cffbc1d 100755
--- a/_scripts/preview.py
+++ b/_scripts/preview.py
@@ -26,7 +26,8 @@
ROOT = Path(__file__).resolve().parent.parent
PAGES = ROOT / "input" / "pagecontent"
CONFIG = ROOT / "sushi-config.yaml"
-# The publisher writes the real stylesheets here; the preview borrows them.
+IMAGES = ROOT / "input" / "images"
+# The publisher writes the real stylesheets and scripts here; the preview borrows them.
ASSETS = ROOT / "dist" / "site"
PORT = 4000
@@ -48,6 +49,8 @@
def render(name, title, prefix, pages, numbers):
src = (PAGES / name).read_text()
src = re.sub(r"^\{:.*\}$", "", src, flags=re.M)
+ # kramdown abbreviations (`*[PMIR]: ...`) become tooltips on the site; pandoc has no equivalent
+ src = re.sub(r"^\*\[[^\]]+\]: .*$", "", src, flags=re.M)
offset = ",".join(prefix.split("."))
body = subprocess.run(
["pandoc", "--from=gfm", "--to=html", "--no-highlight",
@@ -86,13 +89,15 @@ def render(name, title, prefix, pages, numbers):
{prefix} {title}
{body}
-{RELOAD_JS}