docs: publish complete product guide and UI fixes

Expand the English README into a practical guide covering installation, accounts, queue controls, automation, history, backups, updates, troubleshooting, and development. Add four sanitized workflow screenshots and enforce their exact public manifest. Restore the animated Backup submenu outside its parent menu, keep expanded account groups naturally sized in the scrolling Accounts list, and strengthen Electron UI regression exit codes.
This commit is contained in:
Sucukdeluxe
2026-08-10 21:06:10 +02:00
parent dc744ec6f3
commit 069440290b
11 changed files with 446 additions and 51 deletions
+59 -4
View File
@@ -17,8 +17,15 @@ const rootFiles = [
'preload-drop-target.js',
'preload.js'
];
const directoryRoots = ['assets', 'lib', 'renderer', 'services/backup-api', 'tests'];
const directoryRoots = ['assets', 'docs', 'lib', 'renderer', 'services/backup-api', 'tests'];
const scriptFiles = ['scripts/afterPack.cjs', 'scripts/dev-runner.cjs', 'scripts/release-plan.mjs', 'scripts/verify-public-release.mjs'];
const screenshotFiles = [
'assets/product-overview.png',
'docs/screenshots/upload-workspace.png',
'docs/screenshots/account-management.png',
'docs/screenshots/automation-settings.png',
'docs/screenshots/history.png'
];
const currentVersion = require('../package.json').version;
function copyDirectory(source, destination) {
@@ -32,6 +39,14 @@ function copyDirectory(source, destination) {
}
}
function copyDocumentationScreenshots(stage) {
for (const relativePath of screenshotFiles.slice(1)) {
const destination = path.join(stage, relativePath);
fs.mkdirSync(path.dirname(destination), { recursive: true });
fs.copyFileSync(path.join(root, screenshotFiles[0]), destination);
}
}
function createStage() {
const stage = fs.mkdtempSync(path.join(os.tmpdir(), 'mhu-public-verifier-'));
for (const relativePath of rootFiles) {
@@ -39,7 +54,10 @@ function createStage() {
fs.mkdirSync(path.dirname(destination), { recursive: true });
fs.copyFileSync(path.join(root, relativePath), destination);
}
for (const relativePath of directoryRoots) copyDirectory(path.join(root, relativePath), path.join(stage, relativePath));
for (const relativePath of directoryRoots) {
if (relativePath === 'docs') copyDocumentationScreenshots(stage);
else copyDirectory(path.join(root, relativePath), path.join(stage, relativePath));
}
for (const relativePath of scriptFiles) {
const destination = path.join(stage, relativePath);
fs.mkdirSync(path.dirname(destination), { recursive: true });
@@ -49,8 +67,11 @@ function createStage() {
return stage;
}
function verify(stage, version = currentVersion) {
return spawnSync(process.execPath, ['scripts/verify-public-release.mjs', '--source-only', '--version', version], {
function verify(stage, version = currentVersion, sourceOnly = true) {
const args = ['scripts/verify-public-release.mjs'];
if (sourceOnly) args.push('--source-only');
args.push('--version', version);
return spawnSync(process.execPath, args, {
cwd: stage,
encoding: 'utf8'
});
@@ -70,7 +91,41 @@ test('public release verifier accepts only the exact source manifest and target
assert.match(extra.stderr, /tests\/unexpected\.json\tsource-layout-allowlist/);
fs.rmSync(path.join(stage, 'tests', 'unexpected.json'));
fs.copyFileSync(path.join(stage, screenshotFiles[1]), path.join(stage, 'docs', 'screenshots', 'unexpected.png'));
const extraDocumentation = verify(stage);
assert.equal(extraDocumentation.status, 1);
assert.match(extraDocumentation.stderr, /docs\/screenshots\/unexpected\.png\tsource-layout-allowlist/);
fs.rmSync(path.join(stage, 'docs', 'screenshots', 'unexpected.png'));
const wrongVersion = verify(stage, '2.0.5');
assert.equal(wrongVersion.status, 1);
assert.match(wrongVersion.stderr, /package\.json\tpackage-version-target/);
});
test('public release verifier requires and validates every approved screenshot', (t) => {
const stage = createStage();
t.after(() => fs.rmSync(stage, { recursive: true, force: true }));
fs.copyFileSync(path.join(root, screenshotFiles[0]), path.join(stage, screenshotFiles[0]));
const baseline = verify(stage, currentVersion, false);
assert.equal(baseline.status, 0, baseline.stderr);
for (const relativePath of screenshotFiles) {
const absolutePath = path.join(stage, relativePath);
const original = fs.readFileSync(absolutePath);
const invalid = Buffer.from(original);
invalid.writeUInt32BE(999, 16);
fs.writeFileSync(absolutePath, invalid);
const invalidScreenshot = verify(stage, currentVersion, false);
assert.equal(invalidScreenshot.status, 1);
assert.ok(invalidScreenshot.stderr.includes(`${relativePath}\tproduct-screenshot`), invalidScreenshot.stderr);
fs.writeFileSync(absolutePath, original);
}
const missingPath = screenshotFiles.at(-1);
fs.rmSync(path.join(stage, missingPath));
const missingScreenshot = verify(stage, currentVersion, false);
assert.equal(missingScreenshot.status, 1);
assert.ok(missingScreenshot.stderr.includes(`${missingPath}\trequired-screenshot`), missingScreenshot.stderr);
});