unaltraweb 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Makefile +57 -17
- data/README.md +47 -14
- data/_plugins/figure_captions.rb +47 -10
- data/_sass/_documentation.scss +7 -5
- data/_sass/_manual.scss +7 -0
- data/docs/_documentation/en/02-tools.md +4 -4
- data/docs/_documentation/en/03-usage.md +62 -1
- data/docs/_documentation/en/06-github-web-editing.md +1 -1
- data/docs/_documentation/en/13-unaltremanual.md +12 -5
- data/docs/_documentation/en/20-syntax.md +14 -0
- data/docs/_documentation/en/25-caption-credits.md +120 -0
- data/docs/_documentation/en/26-image-backgrounds.md +103 -0
- data/docs/_documentation/en/31-template.md +1 -1
- data/docs/_documentation/en/32-development.md +1 -1
- data/docs/_documentation/en/40-distribution.md +60 -23
- data/docs/_documentation/en/42-docker-image.md +21 -11
- data/docs/_documentation/en/43-workspace-path-policies.md +232 -0
- data/docs/_documentation/en/44-editorial-review.md +237 -0
- data/docs/agents/action-prompts/00-start-site-session.txt +14 -7
- data/docs/agents/action-prompts/22-manual-style-audit.txt +3 -1
- data/docs/agents/manual-authoring-components.md +38 -0
- data/docs/agents/mcp-contract.md +112 -14
- data/docs/agents/visual-companions-0.4.0.md +74 -0
- data/docs/assets/img/caption-credits-demo.svg +19 -0
- data/scripts/editorial_check.py +12 -0
- data/scripts/image_background_check.py +12 -0
- data/scripts/manual/build_pdf.py +94 -16
- data/scripts/manual/filters/figure-captions.lua +65 -10
- data/scripts/manual/templates/manual.tex +6 -1
- data/scripts/test_gem_build.py +32 -2
- data/scripts/test_reproducible_jekyll_build.py +1 -1
- data/scripts/test_wheel_install.py +75 -5
- data/scripts/unaltraweb-mcp-bootstrap.sh +19 -1
- data/scripts/validate_distribution.py +19 -4
- data/scripts/validate_workflows.py +290 -5
- data/scripts/verify_package_publish.py +414 -0
- data/scripts/web_captures/render.py +1 -1
- data/src/unaltraweb_mcp/component-contract.json +37 -37
- data/src/unaltraweb_mcp/editorial.py +495 -0
- data/src/unaltraweb_mcp/editorial_sources.py +504 -0
- data/src/unaltraweb_mcp/image_backgrounds.py +334 -0
- data/src/unaltraweb_mcp/image_probe.py +149 -0
- data/src/unaltraweb_mcp/processes.py +146 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9b1d06c849d3893c28fa69a879e93f56acef7a742aa8e2d8ae7df78fa53d4a2
|
|
4
|
+
data.tar.gz: '0995fe545e4466b531edc89ed8c8a37ca707e847cc31a8a3acd3647d6b77ecdd'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06dd87a7428129eb5ab81655c892abddf159d223aefec27cbdf5e7b5512a72fe7940061313cec5ccee5032ac1b0343cc6b8390617106c59b76d979b109d73cfc
|
|
7
|
+
data.tar.gz: e2a1cc1fe578e4df0968d1f9b9fbc1abbb0cfed254d38c192e7ec24a55e254adf14c9ed57128255f5a18cbaacf68b4fc64a8c519c99d5f87e1a98798db42a1ca
|
data/Makefile
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
PYTHON ?= python3
|
|
2
2
|
override PROJECT := $${MCP_CONSUMER_WORKSPACE:?MCP_CONSUMER_WORKSPACE is required}
|
|
3
3
|
override PROJECT_ROOT := $(PROJECT)
|
|
4
|
-
MCP_RUNTIME_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.
|
|
5
|
-
MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.
|
|
4
|
+
MCP_RUNTIME_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.5.0
|
|
5
|
+
MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.5.0
|
|
6
|
+
MCP_RELEASE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:389bc585cdb4fc89d3372f4896a55fe26e15df38b46bc114ce44fdb3f1c8deb9
|
|
6
7
|
MCP_DOCKER_BUILD_NETWORK ?= default
|
|
7
8
|
INIT_SITE_PROFILE ?= unaltreselfie
|
|
8
9
|
NEW_WEB_PROFILE ?= unaltreselfie
|
|
@@ -35,22 +36,35 @@ WEB_CAPTURE_DOCKER_NETWORK ?=
|
|
|
35
36
|
WEB_CAPTURE_SERVICE_HOST ?=
|
|
36
37
|
WEB_CAPTURE_CONFIRM_OVERWRITE ?= 0
|
|
37
38
|
WEB_CAPTURE_SCRIPT := $(CURDIR)/scripts/web_captures/render.py
|
|
38
|
-
WEB_CAPTURE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-web-capture:
|
|
39
|
+
WEB_CAPTURE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-web-capture@sha256:0bf1bc67fe63e1440bffe708a168beefa11c54441650a871ab99380d362f7c1e
|
|
39
40
|
WEB_CAPTURE_DEV_IMAGE ?= unaltraweb-web-capture:dev
|
|
40
41
|
WEB_CAPTURE_DOCKER_BUILD_NETWORK ?= default
|
|
41
42
|
VEGAVISUALS_CLI ?=
|
|
42
|
-
DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.
|
|
43
|
-
MANUAL_PDF_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf:
|
|
43
|
+
DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.5.0
|
|
44
|
+
MANUAL_PDF_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:9e0b3a45753c170b795e9a9d6df61580085c113436beac5bf6c8de69b6562097
|
|
44
45
|
MANUAL_PDF_DEV_IMAGE ?= unaltraweb-manual-pdf:dev
|
|
46
|
+
MCP_SMOKE_MANUAL_PDF_IMAGE ?= $(MANUAL_PDF_IMAGE)
|
|
45
47
|
MANUAL_PDF_LANG ?=
|
|
46
48
|
MANUAL_PDF_PUBLISH_DRY_RUN ?= 1
|
|
49
|
+
MANUAL_PDF_CONFIRM_PUBLISH ?= 0
|
|
50
|
+
MANUAL_PDF_PUBLICATION_INTENT_SHA256 ?=
|
|
51
|
+
MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN ?= 1
|
|
52
|
+
MANUAL_PDF_PREVIEW_CONFIRM_CLEAN ?= 0
|
|
53
|
+
MANUAL_PDF_PREVIEW_RECEIPT_SHA256 ?=
|
|
47
54
|
MANUAL_RELEASE_SELECTOR ?= latest
|
|
48
55
|
MANUAL_RELEASE_DRY_RUN ?= 1
|
|
49
56
|
MANUAL_RELEASE_CONFIRM_PREPARE ?= 0
|
|
50
57
|
override MANUAL_RELEASE_SELECTOR := $(value MANUAL_RELEASE_SELECTOR)
|
|
51
58
|
override MANUAL_RELEASE_DRY_RUN := $(value MANUAL_RELEASE_DRY_RUN)
|
|
52
59
|
override MANUAL_RELEASE_CONFIRM_PREPARE := $(value MANUAL_RELEASE_CONFIRM_PREPARE)
|
|
53
|
-
|
|
60
|
+
override MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN := $(value MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN)
|
|
61
|
+
override MANUAL_PDF_PREVIEW_CONFIRM_CLEAN := $(value MANUAL_PDF_PREVIEW_CONFIRM_CLEAN)
|
|
62
|
+
override MANUAL_PDF_PREVIEW_RECEIPT_SHA256 := $(value MANUAL_PDF_PREVIEW_RECEIPT_SHA256)
|
|
63
|
+
override MANUAL_PDF_LANG := $(value MANUAL_PDF_LANG)
|
|
64
|
+
override MANUAL_PDF_PUBLISH_DRY_RUN := $(value MANUAL_PDF_PUBLISH_DRY_RUN)
|
|
65
|
+
override MANUAL_PDF_CONFIRM_PUBLISH := $(value MANUAL_PDF_CONFIRM_PUBLISH)
|
|
66
|
+
override MANUAL_PDF_PUBLICATION_INTENT_SHA256 := $(value MANUAL_PDF_PUBLICATION_INTENT_SHA256)
|
|
67
|
+
export MANUAL_RELEASE_SELECTOR MANUAL_PDF_LANG MANUAL_PDF_PUBLISH_DRY_RUN MANUAL_PDF_CONFIRM_PUBLISH MANUAL_PDF_PUBLICATION_INTENT_SHA256 MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN MANUAL_PDF_PREVIEW_CONFIRM_CLEAN MANUAL_PDF_PREVIEW_RECEIPT_SHA256
|
|
54
68
|
UNALTRAWEB_WORKER_ROLE ?=
|
|
55
69
|
UNALTRAWEB_WORKER_PROJECT ?=
|
|
56
70
|
UNALTRAWEB_WORKER_TOKEN ?=
|
|
@@ -79,8 +93,8 @@ ifneq ($(strip $(SCIMAGO_INPUT)),)
|
|
|
79
93
|
SCIMAGO_ARGS += --input "$(SCIMAGO_INPUT)"
|
|
80
94
|
endif
|
|
81
95
|
|
|
82
|
-
.PHONY: distribution-check distribution-release-check distribution-doctor workflow-check wheel-check gem-check reproducible-site-check docs-build docs-serve docs-publish docs-down metrics-scimago-fetch metrics-update metrics-update-all metrics-check manual-pdf-image manual-pdf-image-dev manual-pdf-preflight manual-pdf-status manual-pdf-check manual-pdf-build manual-pdf-publish manual-pdf-sync manual-release-status manual-release-check manual-release-prepare manual-compute-status manual-compute-check manual-compute-render manual-compute-render-figures manual-compute-image-python manual-compute-image-r manual-compute-images manual-compute-rstudio compute-base-image-python compute-base-image-r web-capture-status web-capture-check web-capture-render web-capture-image visualization-status visualization-check visualization-render
|
|
83
|
-
.PHONY: mcp-runtime-image mcp-image mcp-build mcp-check mcp-smoke mcp-smoke-prebuilt mcp-stdio mcp-down mcp-down-all mcp-list-tools mcp-starter-templates mcp-new-web mcp-initialize-site mcp-site-context mcp-profile-check mcp-manual-source-quality-check mcp-manual-editorial-quality-check mcp-manual-authoring-capabilities mcp-manual-computation-status mcp-manual-computation-check mcp-manual-computation-render mcp-manual-computation-render-figures mcp-web-capture-status mcp-web-capture-check mcp-web-capture-render mcp-manual-pdf-status mcp-manual-pdf-build mcp-manual-pdf-publish mcp-manual-release-status mcp-manual-release-check mcp-manual-release-prepare mcp-profile-prune-plan mcp-profile-prune mcp-content-inventory mcp-language-policy mcp-content-approval-inventory mcp-translation-plan mcp-bibliography-inventory mcp-bibliometrics-check mcp-build-health
|
|
96
|
+
.PHONY: distribution-check distribution-release-check distribution-doctor workflow-check wheel-check gem-check reproducible-site-check docs-build docs-serve docs-publish docs-down metrics-scimago-fetch metrics-update metrics-update-all metrics-check manual-pdf-image manual-pdf-image-dev manual-pdf-preflight manual-pdf-status manual-pdf-check manual-pdf-build manual-pdf-publish manual-pdf-publish-worker manual-pdf-sync manual-release-status manual-release-check manual-release-prepare manual-compute-status manual-compute-check manual-compute-render manual-compute-render-figures manual-compute-image-python manual-compute-image-r manual-compute-images manual-compute-rstudio compute-base-image-python compute-base-image-r web-capture-status web-capture-check web-capture-render web-capture-image visualization-status visualization-check visualization-render
|
|
97
|
+
.PHONY: mcp-runtime-image mcp-image mcp-build mcp-check mcp-smoke mcp-smoke-prebuilt mcp-stdio mcp-down mcp-down-all mcp-list-tools mcp-starter-templates mcp-new-web mcp-initialize-site mcp-site-context mcp-profile-check mcp-manual-source-quality-check mcp-manual-editorial-quality-check mcp-manual-authoring-capabilities mcp-manual-computation-status mcp-manual-computation-check mcp-manual-computation-render mcp-manual-computation-render-figures mcp-web-capture-status mcp-web-capture-check mcp-web-capture-render mcp-manual-pdf-status mcp-manual-pdf-build mcp-manual-pdf-preview-prepare mcp-manual-pdf-preview-clean mcp-manual-pdf-publish mcp-manual-release-status mcp-manual-release-check mcp-manual-release-prepare mcp-profile-prune-plan mcp-profile-prune mcp-content-inventory mcp-language-policy mcp-content-approval-inventory mcp-translation-plan mcp-bibliography-inventory mcp-bibliometrics-check mcp-build-health
|
|
84
98
|
|
|
85
99
|
REPOSITORY_CONTEXT_TARGETS := distribution-doctor manual-compute-status manual-compute-check manual-compute-render manual-compute-render-figures manual-compute-image-python manual-compute-image-r manual-compute-images manual-compute-rstudio web-capture-status web-capture-check web-capture-render visualization-status visualization-check visualization-render manual-pdf-preflight manual-pdf-status manual-pdf-check manual-pdf-build manual-pdf-publish manual-pdf-sync manual-release-status manual-release-check manual-release-prepare metrics-scimago-fetch metrics-update metrics-update-all metrics-check
|
|
86
100
|
$(REPOSITORY_CONTEXT_TARGETS): override PROJECT = $${MCP_CONSUMER_WORKSPACE:-$$PWD}
|
|
@@ -107,19 +121,24 @@ gem-check: ## Build the gem and verify its package-owned contract files
|
|
|
107
121
|
reproducible-site-check: ## Build the same fixed-epoch Jekyll fixture twice and compare every output
|
|
108
122
|
@DOCKER_IMAGE="$(DOCKER_IMAGE)" $(PYTHON) scripts/test_reproducible_jekyll_build.py
|
|
109
123
|
|
|
110
|
-
mcp-runtime-image
|
|
124
|
+
mcp-runtime-image mcp-image mcp-check mcp-smoke: MCP_RUNTIME_IMAGE = unaltraweb:dev
|
|
125
|
+
mcp-runtime-image mcp-image mcp-check mcp-smoke: MCP_IMAGE = unaltraweb-mcp:dev
|
|
126
|
+
mcp-smoke: MCP_SMOKE_MANUAL_PDF_IMAGE = $(MANUAL_PDF_DEV_IMAGE)
|
|
127
|
+
|
|
128
|
+
mcp-runtime-image: ## Build the local development Jekyll runtime used by the MCP
|
|
111
129
|
docker build --network "$(MCP_DOCKER_BUILD_NETWORK)" -t "$(MCP_RUNTIME_IMAGE)" .
|
|
112
130
|
|
|
113
|
-
mcp-image: mcp-runtime-image ## Build the
|
|
131
|
+
mcp-image: mcp-runtime-image ## Build the local development FastMCP control plane
|
|
114
132
|
docker build --network "$(MCP_DOCKER_BUILD_NETWORK)" --build-arg "UNALTRAWEB_RUNTIME_IMAGE=$(MCP_RUNTIME_IMAGE)" -t "$(MCP_IMAGE)" -f Dockerfile.mcp .
|
|
115
133
|
|
|
116
|
-
mcp-build:
|
|
134
|
+
mcp-build: ## Prepare the reviewed release image used by MCP sessions, builds, and previews
|
|
135
|
+
@docker image inspect "$(MCP_RELEASE_IMAGE)" >/dev/null 2>&1 || docker pull "$(MCP_RELEASE_IMAGE)" >/dev/null
|
|
117
136
|
|
|
118
137
|
mcp-check: mcp-image ## Verify the Dockerized MCP CLI contract
|
|
119
138
|
docker run --rm --entrypoint unaltraweb-mcp "$(MCP_IMAGE)" version
|
|
120
139
|
|
|
121
|
-
mcp-smoke: mcp-
|
|
122
|
-
@$(MAKE) --silent --no-print-directory mcp-smoke-prebuilt MCP_IMAGE="$(MCP_IMAGE)"
|
|
140
|
+
mcp-smoke: mcp-image manual-pdf-image-dev ## Build and prove a real MCP stdio connection
|
|
141
|
+
@$(MAKE) --silent --no-print-directory mcp-smoke-prebuilt MCP_IMAGE="$(MCP_IMAGE)" MCP_SMOKE_MANUAL_PDF_IMAGE="$(MCP_SMOKE_MANUAL_PDF_IMAGE)"
|
|
123
142
|
|
|
124
143
|
mcp-smoke-prebuilt: ## Prove a real MCP stdio connection using the selected prebuilt MCP image
|
|
125
144
|
docker run --rm --user "$(LOCAL_UID):$(LOCAL_GID)" -e HOME=/tmp --entrypoint python3 "$(MCP_IMAGE)" /opt/unaltraweb/test/mcp_smoke.py
|
|
@@ -128,16 +147,18 @@ mcp-smoke-prebuilt: ## Prove a real MCP stdio connection using the selected preb
|
|
|
128
147
|
image_id=$$(docker image inspect --format '{{.Id}}' "$(MCP_IMAGE)"); \
|
|
129
148
|
socket_mount=$$(/bin/sh "$(DOCKER_MOUNT_SCRIPT)" "$$docker_socket" /var/run/docker.sock); \
|
|
130
149
|
project_mount=$$(/bin/sh "$(DOCKER_MOUNT_SCRIPT)" "$(CURDIR)/tmp/mcp-preview-smoke" /workspace); \
|
|
150
|
+
mirror_mount=$$(/bin/sh "$(DOCKER_MOUNT_SCRIPT)" "$(CURDIR)/tmp/mcp-preview-smoke" "$(CURDIR)/tmp/mcp-preview-smoke"); \
|
|
131
151
|
docker run --rm --user "$(LOCAL_UID):$(LOCAL_GID)" --group-add "$$socket_group" \
|
|
132
152
|
-e HOME=/tmp -e "UNALTRAWEB_DOCKER_ROOT=$(CURDIR)/tmp/mcp-preview-smoke" \
|
|
133
153
|
-e "UNALTRAWEB_PROJECT_USER=$(LOCAL_UID):$(LOCAL_GID)" -e "UNALTRAWEB_MCP_IMAGE=$$image_id" \
|
|
154
|
+
-e "MANUAL_PDF_IMAGE=$(MCP_SMOKE_MANUAL_PDF_IMAGE)" \
|
|
134
155
|
--mount "$$socket_mount" \
|
|
135
|
-
--mount "$$project_mount" -w /workspace \
|
|
156
|
+
--mount "$$project_mount" --mount "$$mirror_mount" -w /workspace \
|
|
136
157
|
--entrypoint python3 "$$image_id" /opt/unaltraweb/test/mcp_preview_smoke.py
|
|
137
158
|
|
|
138
159
|
mcp-stdio: ## Serve MCP_CONSUMER_WORKSPACE through the Dockerized stdio MCP
|
|
139
160
|
@test -n "$${MCP_CONSUMER_WORKSPACE:-}" || { printf '%s\n' 'MCP_CONSUMER_WORKSPACE is required' >&2; exit 2; }
|
|
140
|
-
@exec "$(CURDIR)/scripts/unaltraweb-mcp-bootstrap.sh" --image "$(
|
|
161
|
+
@exec "$(CURDIR)/scripts/unaltraweb-mcp-bootstrap.sh" --image "$(MCP_RELEASE_IMAGE)"
|
|
141
162
|
|
|
142
163
|
mcp-down: ## Remove MCP resources owned by MCP_CONSUMER_WORKSPACE or MCP_PROJECT_ID
|
|
143
164
|
@exec "$(CURDIR)/scripts/unaltraweb-mcp-cleanup.sh"
|
|
@@ -202,6 +223,12 @@ mcp-manual-pdf-status: ## Inspect manual PDF state for PROJECT
|
|
|
202
223
|
mcp-manual-pdf-build: ## Build the manual PDF for PROJECT
|
|
203
224
|
@PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-build --language "$(MANUAL_PDF_LANG)"
|
|
204
225
|
|
|
226
|
+
mcp-manual-pdf-preview-prepare: ## Stage ignored PDF and cover copies for local Jekyll review
|
|
227
|
+
@PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-preview-prepare
|
|
228
|
+
|
|
229
|
+
mcp-manual-pdf-preview-clean: ## Dry-run cleanup of receipt-owned PDF preview copies
|
|
230
|
+
@PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-preview-clean $(if $(filter 0 false FALSE no NO n N,$(MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN)),--apply,) $(if $(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PREVIEW_CONFIRM_CLEAN)),--confirm-clean,) --expected-receipt-sha256 "$${MANUAL_PDF_PREVIEW_RECEIPT_SHA256}"
|
|
231
|
+
|
|
205
232
|
mcp-manual-pdf-publish: ## Dry-run manual PDF publication for PROJECT
|
|
206
233
|
@PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-publish --language "$(MANUAL_PDF_LANG)"
|
|
207
234
|
|
|
@@ -365,10 +392,23 @@ manual-pdf-check: ## Reject stale or unpublished manual PDF artefacts without Do
|
|
|
365
392
|
manual-pdf-build: manual-pdf-preflight ## Build manual PDFs and cover previews under tmp
|
|
366
393
|
$(call run_manual_pdf_worker,build)
|
|
367
394
|
|
|
368
|
-
manual-pdf-publish:
|
|
395
|
+
manual-pdf-publish-worker: ## Internal worker target used by provenance-aware publication
|
|
396
|
+
@if test -z "$(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PUBLISH_DRY_RUN))"; then \
|
|
397
|
+
test "$(MANUAL_PDF_CONFIRM_PUBLISH)" = 1 || { printf '%s\n' 'Real publication workers require MANUAL_PDF_CONFIRM_PUBLISH=1.' >&2; exit 2; }; \
|
|
398
|
+
test -n "$(UNALTRAWEB_WORKER_TOKEN)" || { printf '%s\n' 'Real publication workers must be launched by the provenance-aware controller.' >&2; exit 2; }; \
|
|
399
|
+
test -n "$(MANUAL_PDF_PUBLICATION_INTENT_SHA256)" || { printf '%s\n' 'Real publication workers require a controller publication intent.' >&2; exit 2; }; \
|
|
400
|
+
PYTHONPATH="$(CURDIR)/src" /usr/bin/python3 -c 'import sys; from pathlib import Path; from unaltraweb_mcp.manual_pdf_preview import validate_publication_worker; validate_publication_worker(Path(sys.argv[1]), sys.argv[2], sys.argv[3])' "$(PROJECT_ROOT)" "$(MANUAL_PDF_PUBLICATION_INTENT_SHA256)" "$(MANUAL_PDF_LANG)"; \
|
|
401
|
+
fi
|
|
402
|
+
@$(MAKE) --silent --no-print-directory manual-pdf-preflight
|
|
403
|
+
@if test -z "$(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PUBLISH_DRY_RUN))"; then \
|
|
404
|
+
PYTHONPATH="$(CURDIR)/src" /usr/bin/python3 -c 'import sys; from pathlib import Path; from unaltraweb_mcp.manual_pdf_preview import validate_publication_worker; validate_publication_worker(Path(sys.argv[1]), sys.argv[2], sys.argv[3])' "$(PROJECT_ROOT)" "$(MANUAL_PDF_PUBLICATION_INTENT_SHA256)" "$(MANUAL_PDF_LANG)"; \
|
|
405
|
+
fi
|
|
369
406
|
$(call run_manual_pdf_worker,publish,$(if $(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PUBLISH_DRY_RUN)),--dry-run,))
|
|
370
407
|
|
|
371
|
-
manual-pdf-
|
|
408
|
+
manual-pdf-publish: ## Copy built PDF artefacts through provenance-aware MCP orchestration
|
|
409
|
+
@PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT_ROOT)" mcp manual-pdf-publish --language "$${MANUAL_PDF_LANG}" --release-selector "$${MANUAL_RELEASE_SELECTOR}" $(if $(filter 0 false FALSE no NO n N,$(MANUAL_PDF_PUBLISH_DRY_RUN)),--apply,) $(if $(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_CONFIRM_PUBLISH)),--confirm-publish,)
|
|
410
|
+
|
|
411
|
+
manual-pdf-sync: manual-pdf-preflight ## Build and copy only changed manual PDFs; disabled configurations remain a no-op
|
|
372
412
|
$(call run_manual_pdf_worker,sync)
|
|
373
413
|
|
|
374
414
|
define run_manual_release_worker
|
data/README.md
CHANGED
|
@@ -26,17 +26,20 @@ See [Edit Safely In GitHub Web](docs/_documentation/en/06-github-web-editing.md)
|
|
|
26
26
|
|
|
27
27
|
## Technical Overview
|
|
28
28
|
|
|
29
|
-
The core packages shared layouts, includes, Sass, assets, Jekyll plugins, bibliometric tooling, multilingual behaviour, theme modes and reusable GitHub Actions workflows. Child sites
|
|
29
|
+
The core packages shared layouts, includes, Sass, assets, Jekyll plugins, bibliometric tooling, multilingual behaviour, theme modes and reusable GitHub Actions workflows. Child sites stay thin: they keep content, configuration and local assets while reusable implementation remains in this repository.
|
|
30
|
+
|
|
31
|
+
The default distribution is Docker-first. Generated sites run their normal build, serve and test commands in the published `unaltraweb-mcp` image, which contains the Python control plane and the reviewed core at `/opt/unaltraweb`. The `unaltraweb` gem on RubyGems and the `unaltraweb-mcp` wheel on PyPI remain supported native interoperability channels, not additional requirements for the Docker path.
|
|
30
32
|
|
|
31
33
|
`unaltremanual` sites can also build language-specific PDF editions and matching web-cover images in an isolated Pandoc/XeLaTeX container. PDF status is offline; build and local review are available through Make and the MCP control plane. When PDF output is enabled, default generated PDF and cover outputs are not versioned. The selected `latest` or stable selector is rendered into both the website and PDF metadata.
|
|
32
34
|
|
|
33
35
|
## Current Status
|
|
34
36
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
- The
|
|
37
|
+
- Public release `v0.4.0` provides the Docker images, Ruby gem and Python wheel as one receipt-bound distribution.
|
|
38
|
+
- `ghcr.io/dosquartsdedocs/unaltraweb-mcp` is the canonical normal local runtime for generated sites; `ghcr.io/dosquartsdedocs/unaltraweb` is its lower-level Jekyll runtime base.
|
|
39
|
+
- The Ruby gem supports native Bundler/Jekyll consumers. The modular MCP wheel supports native creation and inspection, and does not bundle the gem, factory checkout, worker images or companion renderers.
|
|
40
|
+
- PDF, browser-capture and computation environments remain separate images so the normal site image does not carry every heavy toolchain.
|
|
38
41
|
- The companion `../unaltraweb-template` repository remains the full-profile integration fixture and visual demo.
|
|
39
|
-
- The project is
|
|
42
|
+
- The project is in its early `0.x` release series. Some inherited `al-folio` implementation details remain while the core is being generalized.
|
|
40
43
|
|
|
41
44
|
## Repository Roles
|
|
42
45
|
|
|
@@ -65,7 +68,19 @@ unaltraweb:
|
|
|
65
68
|
|
|
66
69
|
## Quick Start
|
|
67
70
|
|
|
68
|
-
Create a child site with the
|
|
71
|
+
Create a child site directly with the public Docker image:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
mkdir my-site && \
|
|
75
|
+
docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp \
|
|
76
|
+
--mount "type=bind,src=${PWD}/my-site,dst=/workspace" \
|
|
77
|
+
ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:389bc585cdb4fc89d3372f4896a55fe26e15df38b46bc114ce44fdb3f1c8deb9 \
|
|
78
|
+
--project /workspace new-web --site-profile unaltreselfie --title "My site" --default-lang en
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The chained `mkdir` requires a new destination, the digest binds creation to the reviewed `v0.4.0` receipt, and `--network none` keeps scaffold generation offline.
|
|
82
|
+
|
|
83
|
+
If Python package tooling is already available, the PyPI adapter exposes the equivalent native command:
|
|
69
84
|
|
|
70
85
|
```bash
|
|
71
86
|
unaltraweb-mcp --project ./my-site new-web --site-profile unaltreselfie --title "My site" --default-lang en
|
|
@@ -77,7 +92,9 @@ From this factory checkout, the equivalent command is:
|
|
|
77
92
|
MCP_CONSUMER_WORKSPACE=./my-site make mcp-new-web NEW_WEB_PROFILE=unaltreselfie SITE_TITLE="My site" DEFAULT_LANG=en
|
|
78
93
|
```
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
All three creation paths use only assets shipped in `unaltraweb_mcp`, either in the image, wheel or factory checkout. They preflight all managed paths, write `.unaltraweb/scaffold.json`, and never overwrite differing files. Later `scaffold_sync` calls can update unchanged baseline controls or adopt exact current package bytes, including the collaboration contract, Dependabot policy, pull-request template, dependency pins, and deploy caller. Local customizations are preserved when the package has not changed that file since the baseline; conflicting local/upstream changes remain blocked. Synchronization never touches config, README prose, agent guidance, or content. `dosquartsdedocs/unaltraweb-template` remains available when a full multi-profile demo with Playwright tests is more useful than a clean profile-specific site.
|
|
96
|
+
|
|
97
|
+
The guided update flow reports `site_context.update_status` at session start so the agent can explain available package/scaffold changes and ask whether to apply them. Accepted updates use the existing `scaffold_sync` transaction with the reviewed `plan_sha256` as `expected_plan_sha256`; an older MCP cannot downgrade known newer consumer pins. See [Guided Consumer Updates](docs/_documentation/en/03-usage.md#guided-consumer-updates). This functionality needs a reviewed MCP/package release containing it: updating a discovery checkout alone does not replace the already-running or digest-pinned public image.
|
|
81
98
|
|
|
82
99
|
After creation, there are two supported editing paths:
|
|
83
100
|
|
|
@@ -112,11 +129,13 @@ make docs-serve DOCKER_IMAGE=unaltraweb:dev
|
|
|
112
129
|
make docs-build DOCKER_IMAGE=unaltraweb:dev
|
|
113
130
|
```
|
|
114
131
|
|
|
115
|
-
The
|
|
132
|
+
The public distribution contract selects `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.4.0` for normal generated-site commands. `MCP_RELEASE_IMAGE` pins the reviewed MCP digest from the same `v0.4.0` receipt for gContExt (formerly ContExt), the GNOME Shell extension. The mutable `:main` channel is reserved for explicit maintainer testing; locally built core images use the `:dev` name.
|
|
133
|
+
|
|
134
|
+
The lower-level `unaltraweb` image supplies Ruby, Jekyll and runtime dependencies. The MCP image layers the full reviewed factory and installed Python package on top; generated Make targets load the theme as a path gem from `/opt/unaltraweb`. Native Bundler consumers can instead resolve the independently published gem, and native Python users can install the wheel.
|
|
116
135
|
|
|
117
136
|
The independent manual PDF runtime is built from `scripts/manual/Dockerfile`; it is deliberately separate from the Jekyll image so normal site builds do not carry Pandoc and TeX Live.
|
|
118
137
|
|
|
119
|
-
|
|
138
|
+
Publish images only through the manual Docker workflows. A core-code change requires a new MCP image, while a base-runtime change also requires a new lower-level runtime image. Candidate builds receive package-write authority only after preflight and never execute their outputs; broad aliases move only after a separate read-only job tests the exact digests, and the final package-write job executes no candidate.
|
|
120
139
|
|
|
121
140
|
When running the core and the template profiles together, keep `unaltraweb` on port `4000` and the template profile servers on `4001` through `4004`.
|
|
122
141
|
|
|
@@ -148,14 +167,16 @@ Doctor is offline. The optional `--docker` mode only calls local Docker version/
|
|
|
148
167
|
|
|
149
168
|
## Global Dockerized MCP
|
|
150
169
|
|
|
151
|
-
`unaltraweb` provides one global, on-demand stdio MCP whose containers are scoped to the current consumer workspace. Each client session gets an independent Docker-generated container name plus stable factory, role, and project labels, so concurrent
|
|
170
|
+
`unaltraweb` provides one global, on-demand stdio MCP whose containers are scoped to the current consumer workspace. Each client session gets an independent Docker-generated container name plus stable factory, role, and project labels, so concurrent processes for the same project do not collide. This runtime capability does not authorize overlapping editors: the collaboration control plane uses one primary mutable checkout and one active editing session per repository. gContExt runs `mcp-build` to prepare the exact public image selected by `MCP_RELEASE_IMAGE`, and `mcp-stdio` launches that same digest. To build and test a development image explicitly from this checkout instead:
|
|
152
171
|
|
|
153
172
|
```bash
|
|
154
|
-
make mcp-
|
|
155
|
-
make mcp-smoke
|
|
173
|
+
make mcp-image
|
|
174
|
+
make mcp-smoke-prebuilt MCP_IMAGE=unaltraweb-mcp:dev
|
|
156
175
|
```
|
|
157
176
|
|
|
158
|
-
|
|
177
|
+
Source builds use the explicit local names `unaltraweb:dev` and `unaltraweb-mcp:dev` by default, avoiding shadowed public references. After each coordinated release, `MCP_RELEASE_IMAGE` advances to its recorded digest in a separate post-release change; candidate source never embeds its unknown future self-digest.
|
|
178
|
+
|
|
179
|
+
gContExt reads the canonical manifest transport `make -C ${factoryRoot} mcp-stdio` and supplies `MCP_CONSUMER_WORKSPACE=${workspaceFolder}` through the process environment. The manifest continues to use `make`, an allowed container host launcher, but no consumer path is parsed by Make or interpolated into shell source. The collaboration control plane requests one top-level MCP, selects its declared dependency closure, preserves unrelated user registrations, and runs a read-only checkout preflight before editing. When a process-held cooperative lease is required, it launches the editing command through its `exec` wrapper; it never manipulates Git worktrees implicitly. An equivalent direct MCP launch is:
|
|
159
180
|
|
|
160
181
|
```bash
|
|
161
182
|
MCP_CONSUMER_WORKSPACE="$PWD" make --silent --no-print-directory -C /path/to/unaltraweb mcp-stdio
|
|
@@ -163,7 +184,7 @@ MCP_CONSUMER_WORKSPACE="$PWD" make --silent --no-print-directory -C /path/to/una
|
|
|
163
184
|
|
|
164
185
|
Replace `/path/to/unaltraweb` with this checkout's absolute path and restart the client after changing its configuration. Each session canonicalizes the inherited workspace after launch, then mounts it at `/workspace` and at its canonical host path, so Docker-backed authoring tools pass valid bind paths to the host daemon. `build_site` runs Jekyll directly in that MCP runtime and returns the offline HTML audit. `preview_start`, `preview_status`, and `preview_stop` manage one labelled preview container per project. By default, Docker publishes container port `4000` on a free loopback host port, so previews from distinct workspaces can run concurrently; pass a nonzero `port` only when a fixed host port is required. A running preview created by the former fixed-port default remains idempotently usable until stopped, after which the dynamic default applies. `http_check` derives its origin only from that owned preview and never accepts an arbitrary URL.
|
|
165
186
|
|
|
166
|
-
Dependency preparation
|
|
187
|
+
Dependency preparation ensures the selected release image and prepares required companions only; it does not initialize a consumer website, and companion `init` aggregation is disabled. Create a site explicitly with the `new_web` MCP tool. To clean up one consumer project, pass the same canonical project path used at launch:
|
|
167
188
|
|
|
168
189
|
```bash
|
|
169
190
|
MCP_CONSUMER_WORKSPACE=/path/to/consumer make mcp-down
|
|
@@ -176,6 +197,16 @@ env -u MCP_CONSUMER_WORKSPACE MCP_PROJECT_ID=0123456789abcdef make mcp-down
|
|
|
176
197
|
When the workspace is live, a supplied `MCP_PROJECT_ID` must match its canonical path. A retained ID is accepted only without a live workspace, making stale-resource cleanup explicit. Cleanup selects only resources carrying both `io.context.mcp-factory=unaltraweb` and that project's stable `io.context.mcp-project` label. Maintainers can deliberately clean every labelled unaltraweb MCP resource with `make mcp-down-all`; neither target deletes images or touches unlabelled containers and networks.
|
|
177
198
|
Replace the example retained ID with the 16-hex value from that project's `io.context.mcp-project` Docker label.
|
|
178
199
|
|
|
200
|
+
## Workspace Path Policies
|
|
201
|
+
|
|
202
|
+
For consumer filesystem ownership, the discovery manifest declares literal
|
|
203
|
+
`workspace_rule.path_policies`. The central manager's read-only `workspace-check`
|
|
204
|
+
checks the selected consumer plus the installable `diavisuals` and `vegavisuals`
|
|
205
|
+
dependency closure. It does not run provider commands or clean files. In
|
|
206
|
+
particular, ignored PDF recovery state and `tmp` require explicit review, not
|
|
207
|
+
blanket deletion. See [Workspace Path Policies And Consumer Updates](docs/_documentation/en/43-workspace-path-policies.md)
|
|
208
|
+
for the audited paths, package/scaffold compatibility and migration procedure.
|
|
209
|
+
|
|
179
210
|
## Bibliometrics
|
|
180
211
|
|
|
181
212
|
Normal Jekyll builds must stay static. External metrics are fetched only through explicit update commands and written back to local data files before build time.
|
|
@@ -221,6 +252,8 @@ CodeQL separately analyzes JavaScript/TypeScript, Python and Ruby on pull reques
|
|
|
221
252
|
|
|
222
253
|
Core artifact workflows run `distribution-check` while selected candidates are truthfully `pending`, validate the selected ref against the BOM version, and keep their preflight jobs credential-free. Once the final source and release intent are reviewed, mark its components `ready` and commit that state. Runtime, MCP, and manual PDF publication separates authority across a signing/package-write build job that never runs candidates, a read-only test job that verifies GitHub-signed digest/source provenance and removes GHCR credentials before execution, and a package-write promotion job that executes no candidate. Every image is built once under only its SHA tag; only the exact digests that pass all Ruby, PDF, reproducibility, MCP, and docs gates can reach verified `sha-*`, `main`, and `latest` aliases. GHCR does not make the initial absence lookup and later tag write atomic, so the signed tested digest, not a claim of compare-and-swap no-clobber, is the trust anchor. Record those digests and package checksums in a versioned `release-candidates.json` child commit; validation requires that receipt to be the only change. After `distribution-release-check`, tag the receipt commit. The tag-only job verifies SHA-tag equality, signed provenance and the revision label against the receipt's `source_commit`, then promotes only the receipt's manifests to checked semver aliases without rebuilding or executing them. `released` remains available for already-published components; released containers other than the self-describing MCP must be digest-pinned. Package preparation never uploads to RubyGems or PyPI, creates a GitHub release, tags the repository or publishes an image; those operations require separate explicit maintainer approval.
|
|
223
254
|
|
|
255
|
+
After tagging, maintainers can use the manual `Publish language packages` workflow with PyPI and RubyGems Trusted Publishing. Its read-only job binds a successful package-preparation run and immutable artifact to the release receipt, verifies the exact file inventory and SHA-256 values, and only then passes one wheel and one gem to separate environment-protected OIDC jobs. Those jobs do not checkout source, rebuild candidates, receive repository write authority, or use stored registry tokens. See [Distribution](docs/_documentation/en/40-distribution.md) for the one-time publisher setup and dispatch inputs.
|
|
256
|
+
|
|
224
257
|
## Attribution
|
|
225
258
|
|
|
226
259
|
`unaltraweb` started from the open-source `al-folio` Jekyll theme and is being refactored into a self-owned reusable core for `dosquartsdedocs` sites. Retain upstream attribution where inherited code remains relevant.
|
data/_plugins/figure_captions.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Unaltraweb
|
|
|
8
8
|
|
|
9
9
|
PANEL_SEQUENCE = ("a".."z").to_a.freeze
|
|
10
10
|
FENCED_CODE_BLOCK = /^[ \t]*(`{3,}|~{3,})[^\n]*\n.*?^[ \t]*\1[ \t]*$/m.freeze
|
|
11
|
+
CAPTION_SOURCE_ATTRIBUTE = /(?:\A|\s)data-caption-source\s*=\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s}]+)/.freeze
|
|
11
12
|
|
|
12
13
|
def enabled?(site)
|
|
13
14
|
config = config_for(site)
|
|
@@ -104,6 +105,7 @@ module Unaltraweb
|
|
|
104
105
|
out << figure_html(
|
|
105
106
|
img: %(<img src="#{h(url.strip)}" alt="#{h(strip_liquid(alt))}"#{attrs.empty? ? "" : " #{attrs}"}>),
|
|
106
107
|
caption: render_inline_markdown(caption),
|
|
108
|
+
source: render_inline_markdown(kramdown_attr_value(attrs_raw, "data-caption-source")),
|
|
107
109
|
label: label,
|
|
108
110
|
lang: lang,
|
|
109
111
|
count: count,
|
|
@@ -196,7 +198,7 @@ module Unaltraweb
|
|
|
196
198
|
|
|
197
199
|
line_end = source.index("\n", start_index) || source.length
|
|
198
200
|
opening = source[start_index...line_end]
|
|
199
|
-
match = opening.match(/\A:::\s*subfigures(?:\s+([^\s"]+))?(?:\s+"([^"]+)")?\s*\z/)
|
|
201
|
+
match = opening.match(/\A:::\s*subfigures(?:\s+([^\s"{]+))?(?:\s+"([^"]+)")?(?:\s+(\{:[^\n]*\}))?\s*\z/)
|
|
200
202
|
return nil unless match
|
|
201
203
|
|
|
202
204
|
body_start = line_end == source.length ? line_end : line_end + 1
|
|
@@ -206,6 +208,7 @@ module Unaltraweb
|
|
|
206
208
|
{
|
|
207
209
|
layout: match[1].to_s,
|
|
208
210
|
caption: match[2].to_s,
|
|
211
|
+
attrs: match[3],
|
|
209
212
|
body: source[body_start...closing.begin(0)],
|
|
210
213
|
raw: source[start_index...closing.end(0)],
|
|
211
214
|
end_idx: closing.end(0)
|
|
@@ -217,7 +220,7 @@ module Unaltraweb
|
|
|
217
220
|
|
|
218
221
|
line_end = source.index("\n", start_index) || source.length
|
|
219
222
|
opening = source[start_index...line_end]
|
|
220
|
-
match = opening.match(/\A:::\s*table(?:\s+"([^"]+)")?\s*\z/)
|
|
223
|
+
match = opening.match(/\A:::\s*table(?:\s+"([^"]+)")?(?:\s+(\{:[^\n]*\}))?\s*\z/)
|
|
221
224
|
return nil unless match
|
|
222
225
|
|
|
223
226
|
body_start = line_end == source.length ? line_end : line_end + 1
|
|
@@ -226,6 +229,7 @@ module Unaltraweb
|
|
|
226
229
|
|
|
227
230
|
{
|
|
228
231
|
caption: match[1].to_s,
|
|
232
|
+
attrs: match[2],
|
|
229
233
|
body: source[body_start...closing.begin(0)],
|
|
230
234
|
raw: source[start_index...closing.end(0)],
|
|
231
235
|
end_idx: closing.end(0)
|
|
@@ -271,7 +275,7 @@ module Unaltraweb
|
|
|
271
275
|
image = images[slot[:index]]
|
|
272
276
|
attrs = kramdown_attrs_to_html(image[:attrs])
|
|
273
277
|
panel_attrs = subfigure_panel_attrs(image[:attrs])
|
|
274
|
-
caption = render_inline_markdown(image[:caption])
|
|
278
|
+
caption = caption_content(render_inline_markdown(image[:caption]), render_inline_markdown(kramdown_attr_value(image[:attrs], "data-caption-source")))
|
|
275
279
|
panel = h(slot[:label])
|
|
276
280
|
caption_html = caption.to_s.strip.empty? ? "" : %Q{<p class="md-subfigure-caption"><span class="md-subfigure-label">#{panel}</span> #{caption}</p>}
|
|
277
281
|
%Q{<div class="md-subfigure" data-panel="#{panel}"#{panel_attrs}>#{caption_html}<img src="#{h(image[:url].strip)}" alt="#{h(strip_liquid(image[:alt]))}"#{attrs.empty? ? "" : " #{attrs}"}></div>}
|
|
@@ -279,7 +283,7 @@ module Unaltraweb
|
|
|
279
283
|
%Q{<div class="md-subfigure-row" data-count="#{row.length}">\n#{cells}\n</div>}
|
|
280
284
|
end.join("\n")
|
|
281
285
|
|
|
282
|
-
clean_caption = render_inline_markdown(block[:caption])
|
|
286
|
+
clean_caption = caption_content(render_inline_markdown(block[:caption]), render_inline_markdown(kramdown_attr_value(block[:attrs], "data-caption-source")))
|
|
283
287
|
figcaption = clean_caption.empty? ? "" : %(<figcaption class="md-figcaption"><span class="figlabel">#{h(label)} #{count}.</span> #{clean_caption}</figcaption>)
|
|
284
288
|
|
|
285
289
|
html = <<~HTML.strip
|
|
@@ -301,7 +305,7 @@ module Unaltraweb
|
|
|
301
305
|
return [block[:raw], count] unless parsed_table
|
|
302
306
|
|
|
303
307
|
count += 1
|
|
304
|
-
caption = render_inline_markdown(block[:caption])
|
|
308
|
+
caption = caption_content(render_inline_markdown(block[:caption]), render_inline_markdown(kramdown_attr_value(block[:attrs], "data-caption-source")))
|
|
305
309
|
caption_html = caption.empty? ? "" : %(<figcaption class="md-table-caption"><span class="figlabel">#{h(label)} #{count}.</span> #{caption}</figcaption>)
|
|
306
310
|
thead = table_row_html(parsed_table[:headers], "th", parsed_table[:alignments])
|
|
307
311
|
tbody = parsed_table[:rows].map { |row| table_row_html(row, "td", parsed_table[:alignments]) }.join("\n")
|
|
@@ -520,7 +524,7 @@ module Unaltraweb
|
|
|
520
524
|
|
|
521
525
|
attrs_raw = nil
|
|
522
526
|
if source[index] == "{"
|
|
523
|
-
close_index = source
|
|
527
|
+
close_index = attribute_block_end(source, index)
|
|
524
528
|
return nil unless close_index
|
|
525
529
|
|
|
526
530
|
attrs_raw = source[index..close_index].sub(/\A\{:\s*/, "{").strip
|
|
@@ -530,6 +534,28 @@ module Unaltraweb
|
|
|
530
534
|
{ alt: alt, url_and_title: url_and_title, attrs: attrs_raw, end_idx: index }
|
|
531
535
|
end
|
|
532
536
|
|
|
537
|
+
def attribute_block_end(source, index)
|
|
538
|
+
quote = nil
|
|
539
|
+
index += 1
|
|
540
|
+
while index < source.length
|
|
541
|
+
char = source[index]
|
|
542
|
+
if char == "\\"
|
|
543
|
+
index += 2
|
|
544
|
+
next
|
|
545
|
+
elsif quote
|
|
546
|
+
quote = nil if char == quote
|
|
547
|
+
elsif char == '"' || char == "'"
|
|
548
|
+
quote = char
|
|
549
|
+
elsif char == "}"
|
|
550
|
+
return index
|
|
551
|
+
elsif char == "\n"
|
|
552
|
+
return nil
|
|
553
|
+
end
|
|
554
|
+
index += 1
|
|
555
|
+
end
|
|
556
|
+
nil
|
|
557
|
+
end
|
|
558
|
+
|
|
533
559
|
def read_balanced(source, index, open_char, close_char)
|
|
534
560
|
depth = 1
|
|
535
561
|
start = index
|
|
@@ -594,10 +620,10 @@ module Unaltraweb
|
|
|
594
620
|
|
|
595
621
|
def kramdown_attr_value(raw, name)
|
|
596
622
|
source = raw.to_s.strip.sub(/\A\{:\s*/, "").sub(/\A\{\s*/, "").sub(/\s*\}\z/, "")
|
|
597
|
-
match = source.match(/(?:\A|\s)#{Regexp.escape(name)}\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s}]+))/)
|
|
623
|
+
match = source.match(/(?:\A|\s)#{Regexp.escape(name)}\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s}]+))/)
|
|
598
624
|
return nil unless match
|
|
599
625
|
|
|
600
|
-
match[1] || match[2] || match[3]
|
|
626
|
+
(match[1] || match[2] || match[3]).gsub(/\\(["'\\])/, '\1')
|
|
601
627
|
end
|
|
602
628
|
|
|
603
629
|
def split_url_and_title(value)
|
|
@@ -630,6 +656,7 @@ module Unaltraweb
|
|
|
630
656
|
figure_html(
|
|
631
657
|
img: img_tag,
|
|
632
658
|
caption: render_inline_markdown(CGI.unescapeHTML(caption)),
|
|
659
|
+
source: render_inline_markdown(CGI.unescapeHTML(extract_attr(img_tag, "data-caption-source").to_s)),
|
|
633
660
|
label: label,
|
|
634
661
|
lang: lang,
|
|
635
662
|
count: count,
|
|
@@ -639,10 +666,17 @@ module Unaltraweb
|
|
|
639
666
|
end
|
|
640
667
|
end
|
|
641
668
|
|
|
642
|
-
def
|
|
669
|
+
def caption_content(caption, source = "")
|
|
670
|
+
parts = []
|
|
671
|
+
parts << %(<span class="md-caption-text">#{caption.to_s.strip}</span>) unless caption.to_s.strip.empty?
|
|
672
|
+
parts << %(<span class="md-caption-source">#{source.to_s.strip}</span>) unless source.to_s.strip.empty?
|
|
673
|
+
parts.join(" ")
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
def figure_html(img:, caption:, label:, lang:, count:, classes: ["md-figure"], attrs: "", source: "")
|
|
643
677
|
classes = Array(classes)
|
|
644
678
|
classes = ["md-figure"] if classes.empty?
|
|
645
|
-
clean_caption = caption
|
|
679
|
+
clean_caption = caption_content(caption, source)
|
|
646
680
|
figcaption = if clean_caption.empty?
|
|
647
681
|
""
|
|
648
682
|
else
|
|
@@ -736,6 +770,9 @@ module Unaltraweb
|
|
|
736
770
|
source = raw.to_s.strip.sub(/\A\{:\s*/, "").sub(/\A\{\s*/, "").sub(/\s*\}\z/, "")
|
|
737
771
|
return "" if source.empty?
|
|
738
772
|
|
|
773
|
+
# Credits are visible caption content, not attributes that can acquire
|
|
774
|
+
# nested HTML quotes when Liquid citations are expanded later.
|
|
775
|
+
source = source.gsub(CAPTION_SOURCE_ATTRIBUTE, " ")
|
|
739
776
|
source = source.tr("“”’‘", %q{""''})
|
|
740
777
|
classes = []
|
|
741
778
|
id = nil
|
data/_sass/_documentation.scss
CHANGED
|
@@ -1461,7 +1461,9 @@ html[data-theme="cafe"] .site-profile-unaltredocs .documentation-sidebar-logo-ca
|
|
|
1461
1461
|
flex: 1 0 auto;
|
|
1462
1462
|
}
|
|
1463
1463
|
|
|
1464
|
-
|
|
1464
|
+
// Rouge uses a table for its gutter and code. Its preformatted whitespace and
|
|
1465
|
+
// sizing belong to .uw-code-block, not to prose/data-table wrapping rules.
|
|
1466
|
+
.documentation-content table:not(.rouge-table) {
|
|
1465
1467
|
display: block;
|
|
1466
1468
|
width: max-content;
|
|
1467
1469
|
min-width: 100%;
|
|
@@ -1470,8 +1472,8 @@ html[data-theme="cafe"] .site-profile-unaltredocs .documentation-sidebar-logo-ca
|
|
|
1470
1472
|
overflow-x: auto;
|
|
1471
1473
|
}
|
|
1472
1474
|
|
|
1473
|
-
.documentation-content table th,
|
|
1474
|
-
.documentation-content table td {
|
|
1475
|
+
.documentation-content table:not(.rouge-table) th,
|
|
1476
|
+
.documentation-content table:not(.rouge-table) td {
|
|
1475
1477
|
overflow-wrap: normal;
|
|
1476
1478
|
word-break: normal;
|
|
1477
1479
|
}
|
|
@@ -1485,8 +1487,8 @@ html[data-theme="cafe"] .site-profile-unaltredocs .documentation-sidebar-logo-ca
|
|
|
1485
1487
|
overflow: visible;
|
|
1486
1488
|
}
|
|
1487
1489
|
|
|
1488
|
-
.documentation-content table a,
|
|
1489
|
-
.documentation-content table code {
|
|
1490
|
+
.documentation-content table:not(.rouge-table) a,
|
|
1491
|
+
.documentation-content table:not(.rouge-table) code {
|
|
1490
1492
|
white-space: normal;
|
|
1491
1493
|
overflow-wrap: break-word;
|
|
1492
1494
|
word-break: normal;
|
data/_sass/_manual.scss
CHANGED
|
@@ -1201,6 +1201,13 @@ html[data-theme="dark"] .md-table {
|
|
|
1201
1201
|
text-align: center;
|
|
1202
1202
|
}
|
|
1203
1203
|
|
|
1204
|
+
.md-caption-source {
|
|
1205
|
+
font-size: 0.92em;
|
|
1206
|
+
font-style: italic;
|
|
1207
|
+
font-weight: normal;
|
|
1208
|
+
color: var(--global-text-color);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1204
1211
|
.figlabel {
|
|
1205
1212
|
display: inline-flex;
|
|
1206
1213
|
min-height: 1.45rem;
|
|
@@ -27,13 +27,13 @@ nav_title: Local Tools
|
|
|
27
27
|
|
|
28
28
|
## Local Runtime
|
|
29
29
|
|
|
30
|
-
The
|
|
30
|
+
The current public core runtime image is:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
ghcr.io/dosquartsdedocs/unaltraweb:0.
|
|
33
|
+
ghcr.io/dosquartsdedocs/unaltraweb:0.4.0
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
That image provides Ruby, Bundler, Jekyll system dependencies, ImageMagick, Node for ExecJS and Python tooling needed by local commands.
|
|
36
|
+
That image provides Ruby, Bundler, Jekyll system dependencies, ImageMagick, Node for ExecJS and Python tooling needed by local commands. Package-generated `v0.4.0` sites select the matching public MCP image for normal local commands. The external integration fixture can advance from its older release pin independently.
|
|
37
37
|
|
|
38
38
|
## Generated Site Commands
|
|
39
39
|
|
|
@@ -143,4 +143,4 @@ When the core docs and all template profiles are running together, use this conv
|
|
|
143
143
|
- Keep deploy workflows as thin `workflow_dispatch` wrappers pinned to a reviewed full commit SHA of `dosquartsdedocs/unaltraweb/.github/workflows/site-deploy.yml`.
|
|
144
144
|
- The optional integration template may retain its local `gh-pages` publishing target for testing that separate workflow.
|
|
145
145
|
- After the first Docker publish, make `ghcr.io/dosquartsdedocs/unaltraweb` public.
|
|
146
|
-
- Confirm `docker pull ghcr.io/dosquartsdedocs/unaltraweb:0.
|
|
146
|
+
- Confirm `docker pull ghcr.io/dosquartsdedocs/unaltraweb:0.4.0` works without `docker login`.
|