Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/capture-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ on:
- src/**
- data/**
- tools/screenshots/**
- tools/news/**
- news/**
- archive/development/**
- .github/workflows/capture-screenshots.yml
- .github/workflows/prepare-release-news.yml

permissions:
contents: read
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/prepare-release-news.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ jobs:
root / manifest['patchnote'],
root / manifest['image'],
manifest_path,
release_dir / 'README.md',
release_dir / 'SHA256SUMS.internal.txt',
release_dir / 'historical',
]

Expand All @@ -151,6 +153,26 @@ jobs:
print(f'Created {archive_path.relative_to(root)} with {len(seen)} files')
PY

- name: Create SHA-256 release asset record
if: steps.marker.outputs.exists == 'true'
shell: bash
run: |
python3 - <<'PY'
import hashlib
import json
import pathlib

root = pathlib.Path('.').resolve()
release_dir = root / 'archive' / 'development' / '${{ env.RELEASE_VERSION }}'
manifest = json.loads((release_dir / 'release.json').read_text(encoding='utf-8'))
assets = [root / manifest['image'], root / manifest['archive']]
lines = []
for asset in assets:
digest = hashlib.sha256(asset.read_bytes()).hexdigest()
lines.append(f"{digest} {asset.relative_to(root).as_posix()}")
(release_dir / 'SHA256SUMS.txt').write_text('\n'.join(lines) + '\n', encoding='utf-8')
PY

- name: Commit news and permanent development archive
if: steps.marker.outputs.exists == 'true'
shell: bash
Expand Down
69 changes: 69 additions & 0 deletions archive/development/0.7.0-alpha.5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# uImposition 0.7.0-alpha.5 evidence

Generated: 2026-07-27T04:18:53Z

This permanent archive records the M7.5 release checkpoint and its focused browser evidence.

## Exact release boundary

- Functional M7.5 baseline: `009451cce94d5cde05ee72305f30447aa65a646c`
- Version checkpoint release commit: `195d6496a291095a69cc9089a64154561ffbb1fa`
- Focused screenshot source head: `a8db529c29e7b71a7809bb5f857d48cfde115597`
- Version PR: `#49`
- Version: `0.7.0-alpha.5`
- Recovery branch: `release/v0.7.0-alpha.5`
- Immutable tag: `v0.7.0-alpha.5`

The recovery branch and immutable tag must point to the version checkpoint commit. The later publication merge contains only release tooling, patchnote, image, manifest, hashes, and evidence.

## Confirmed M7.5 scope

- verified user production-plan catalog inside one shared product format, uniform grids, fitting 0°/90°, paper-minimum and dedicated-pair strategies, separate front/back forms, one shared duplex color specification, and complete page pairs;
- all feasible variants inside that documented scope stay in the catalog;
- filters change only the view;
- recommendation and explicit operator selection remain separate;
- selected-plan details and scheme/report PDF export;
- objective priority reranking reuses validated plans without rebuilding geometry.

The checkpoint does not claim a globally complete solver, a lower-bound proof outside explicitly proven cases, or compatibility with a particular press.

## Production invariants

- underproduction is forbidden;
- back layouts derive only from a validated front;
- missing cost never becomes zero;
- layout forms and color plates remain separate;
- recommendations never replace the operator's choice;
- fixtures and bounded searches are not described as automatic global solvers;
- geometric symmetry does not prove machine compatibility.

## Exact checks

- Quality run: `30236119913` — 173/173 tests, 0 failures;
- Quality artifact: `8641678651`, `uimposition-quality-49-1`;
- Quality digest: `sha256:7570c0a0c529d9994ea416d28cfefdbcfd688c8701fed09213b9c12642315f27`;
- Full Chromium/PDF run: `30236119914` — 16/16 scenarios;
- Full Chromium/PDF artifact: `8641691735`, `uimposition-screenshots-49-1`;
- Full Chromium/PDF digest: `sha256:1cf047532caf18e7d49a6464a8f4b339dcc2ea15c37eb6f6ca0581491b0d33df`;
- scheme PDF: 8 A4 pages, verified by pdfinfo and rendered with Poppler;
- production report PDF: 6 A4 pages, verified by pdfinfo and rendered with Poppler;
- focused evidence workflow run: `30236703966`;
- focused image captured at: `2026-07-27T04:18:46.632Z`.

The two exact-head Action archives are preserved under `historical/`.

## Focused screenshot

- Scenario: `m7-objective-priority-editor`
- Original: `uimposition-m7-5-objective-priority-editor.png`
- Release image: `news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png`
- Viewport: 1440 × 1180
- Selector: `#userObjectivePriorityEditor`

Desktop and mobile focused PNGs were visually reviewed. Text is readable without clipping or overflow; estimated cost is first; the panel reports four reused plans and zero regenerated plans; `uniform-r90-dedicated-pairs` remains a recommendation rather than an operator selection. No secrets or private data are visible.

## Integrity

- `SHA256SUMS.internal.txt` records the PNG, patchnote, release manifest, and this README inside the ZIP;
- `SHA256SUMS.txt` records the release PNG and the final evidence ZIP;
- the ZIP includes the six focused M7.5 Chromium scenarios, capture log and NDJSON manifest, patchnote, release manifest, README, internal hashes, and both historical exact-head Action archives.
4 changes: 4 additions & 0 deletions archive/development/0.7.0-alpha.5/SHA256SUMS.internal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bb5686efa1d0f75990885b6f5e8736c1c4c93233eae181c3ae68f81397cf5a6e news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png
6113f35f62ee5ab03b85094ba8582895a82d98e49cbf4f9374906e91d8f01126 news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md
9dd91f438cbc8485b6fe5844ce44f354d2b5edf7cc3d2219052569bf9aa4f105 archive/development/0.7.0-alpha.5/release.json
4c1d9d85c4af7cae0a01d81ff72bfd929b5cfed2c4a5b1bbf49c5dac8c6ff5b5 archive/development/0.7.0-alpha.5/README.md
2 changes: 2 additions & 0 deletions archive/development/0.7.0-alpha.5/SHA256SUMS.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bb5686efa1d0f75990885b6f5e8736c1c4c93233eae181c3ae68f81397cf5a6e news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png
9ac0cfb4c01c34e530b4f734e499e6184cf2d6ab78f2157ea1216e6ee5742ec9 archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip
Binary file not shown.
Binary file not shown.
34 changes: 34 additions & 0 deletions archive/development/0.7.0-alpha.5/release.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"project": "uImposition",
"version": "0.7.0-alpha.5",
"tag": "v0.7.0-alpha.5",
"title": "uImposition v0.7.0-alpha.5",
"prerelease": true,
"sourceCommit": "a8db529c29e7b71a7809bb5f857d48cfde115597",
"releaseCommit": "195d6496a291095a69cc9089a64154561ffbb1fa",
"createdAt": "2026-07-27T04:18:53Z",
"patchnote": "news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md",
"image": "news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png",
"archive": "archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip",
"screenshotScenario": "m7-objective-priority-editor",
"archiveScenarios": [
"m7-objective-priority-editor",
"m7-objective-priority-editor-mobile",
"m7-user-order-plan-catalog",
"m7-user-order-plan-catalog-mobile",
"m7-user-selected-plan-details",
"m7-user-selected-plan-details-mobile"
],
"historicalArtifacts": [
{
"artifactId": 8641678651,
"fileName": "uimposition-quality-49-1.zip"
},
{
"artifactId": 8641691735,
"fileName": "uimposition-screenshots-49-1.zip"
}
],
"telegramText": "uImposition 0.7.0-alpha.5: M7.5 выпускает проверенные производственные планы для пользовательского заказа. Внутри текущей ограниченной области каталог сохраняет все допустимые варианты; фильтры меняют только представление. Оператор выбирает план явно и выгружает схемы/отчёт PDF. Приоритеты переоценивают 4 готовых плана без перестроения геометрии; рекомендация не заменяет выбор."
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
type: release
project: uImposition
series: uimposition
title: uImposition 0.7.0-alpha.5: проверенные производственные планы
version: 0.7.0-alpha.5
queued_at: 2026-07-27T04:18:53Z
repo_url: https://github.com/sunpole/uImposition
web_url: https://sunpole.github.io/uImposition/
image: 2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png
image_text: Редактор приоритетов M7.5: расчётная стоимость поставлена первой; 4 готовых плана переоценены без перестроения геометрии; рекомендация показана отдельно от выбора оператора.
image_source: playwright
image_target: scenario/m7-objective-priority-editor
image_commit: a8db529c29e7b71a7809bb5f857d48cfde115597
image_captured_at: 2026-07-27T04:18:46.632Z
---

# uImposition 0.7.0-alpha.5: проверенные производственные планы

M7.5 выводит проверенные варианты производства для пользовательского заказа, сохраняет полный допустимый каталог внутри явно ограниченной области, позволяет оператору выбрать план и выгрузить схемы и отчёт PDF. Приоритеты меняют рекомендацию без перестроения геометрии.

Что добавлено:

- полный каталог допустимых планов внутри текущей ограниченной области, без удаления вариантов фильтрами;
- отдельные рекомендация и явный выбор оператора;
- детали выбранного плана, схемы и производственный отчёт PDF;
- редактор приоритетов целей с переоценкой уже проверенного каталога без регенерации геометрии.

Проверенный контрольный результат:

- в focused-сценарии доступно 4 проверенных плана;
- при приоритете стоимости повторно использовано 4 плана и построено заново 0;
- рекомендован uniform-r90-dedicated-pairs; операторский выбор остаётся независимым;
- недопечатка запрещена; отсутствие стоимости не превращается в ноль.

Следующий самостоятельный этап — M7.6 / 0.7.0-alpha.6. Он не входит в этот релиз; текущий поиск нельзя считать глобально полным вне задокументированной области.

Короткий текст для Telegram:

uImposition 0.7.0-alpha.5: M7.5 выпускает проверенные производственные планы для пользовательского заказа. Внутри текущей ограниченной области каталог сохраняет все допустимые варианты; фильтры меняют только представление. Оператор выбирает план явно и выгружает схемы/отчёт PDF. Приоритеты переоценивают 4 готовых плана без перестроения геометрии; рекомендация не заменяет выбор.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 34 additions & 3 deletions tools/news/prepare-release.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { createHash } from "node:crypto";
import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, "../..");
const markerPath = path.join(root, "news/.prepare-release.json");
const manifestPath = path.join(root, "artifacts/screenshots/manifest.json");
const manifestPath = path.join(root, "artifacts/screenshots/manifest.ndjson");

const marker = JSON.parse(await readFile(markerPath, "utf8"));
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
const entry = manifest.entries.find((item) => item.scenario === marker.scenario);
const manifestEntries = (await readFile(manifestPath, "utf8"))
.split(/\r?\n/)
.filter(Boolean)
.map((line) => JSON.parse(line));
const entry = manifestEntries.find((item) => item.scenario === marker.scenario);
const expectedCommit = process.env.SCREENSHOT_COMMIT || process.env.GITHUB_SHA;
const releaseCommit = String(marker.releaseCommit ?? "");

if (!entry) throw new Error(`Screenshot scenario not found: ${marker.scenario}`);
if (!expectedCommit) throw new Error("Expected screenshot commit is not available");
if (!/^[0-9a-f]{40}$/i.test(releaseCommit)) throw new Error("releaseCommit must be a full 40-character commit SHA");
if (!entry.commit || entry.commit !== expectedCommit) {
throw new Error(`Screenshot commit ${entry.commit} does not match expected commit ${expectedCommit}`);
}
Expand All @@ -30,6 +36,8 @@ const targetPatchnote = path.join(root, "news", patchnoteName);
const releaseDirectory = path.join(root, "archive", "development", marker.version);
const archiveName = `uimposition-v${safeVersion}-evidence.zip`;
const releaseManifestPath = path.join(releaseDirectory, "release.json");
const evidenceReadmePath = path.join(releaseDirectory, "README.md");
const internalSumsPath = path.join(releaseDirectory, "SHA256SUMS.internal.txt");

function bulletList(items) {
return items.map((item) => `- ${item};`).join("\n").replace(/;$/, ".");
Expand Down Expand Up @@ -88,6 +96,7 @@ const releaseManifest = {
title: `uImposition v${marker.version}`,
prerelease,
sourceCommit: entry.commit,
releaseCommit,
createdAt: queuedAt,
patchnote: path.relative(root, targetPatchnote).replaceAll("\\", "/"),
image: path.relative(root, targetImage).replaceAll("\\", "/"),
Expand All @@ -101,12 +110,34 @@ const releaseManifest = {
};

await writeFile(releaseManifestPath, `${JSON.stringify(releaseManifest, null, 2)}\n`, "utf8");

const evidenceReadmeTemplate = String(marker.evidenceReadme ?? "");
if (!evidenceReadmeTemplate.trim()) throw new Error("evidenceReadme is required");
const evidenceReadme = evidenceReadmeTemplate
.replaceAll("{{SOURCE_COMMIT}}", entry.commit)
.replaceAll("{{RELEASE_COMMIT}}", releaseCommit)
.replaceAll("{{QUEUED_AT}}", queuedAt)
.replaceAll("{{CAPTURED_AT}}", entry.capturedAt)
.replaceAll("{{PREPARE_RUN_ID}}", String(process.env.GITHUB_RUN_ID ?? "local"));
await writeFile(evidenceReadmePath, evidenceReadme.endsWith("\n") ? evidenceReadme : `${evidenceReadme}\n`, "utf8");

async function sha256(filePath) {
return createHash("sha256").update(await readFile(filePath)).digest("hex");
}
const internalInputs = [targetImage, targetPatchnote, releaseManifestPath, evidenceReadmePath];
const internalSums = [];
for (const filePath of internalInputs) {
internalSums.push(`${await sha256(filePath)} ${path.relative(root, filePath).replaceAll("\\\\", "/")}`);
}
await writeFile(internalSumsPath, `${internalSums.join("\n")}\n`, "utf8");
await rm(markerPath);
console.log(JSON.stringify({
imageName,
patchnoteName,
queuedAt,
imageCommit: entry.commit,
releaseManifest: path.relative(root, releaseManifestPath),
evidenceReadme: path.relative(root, evidenceReadmePath),
internalSums: path.relative(root, internalSumsPath),
archive: releaseManifest.archive,
}, null, 2));
Loading