mixdog 1.0.4 → 1.0.6
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.
- package/README.md +117 -434
- package/package.json +18 -17
- package/scripts/gate-local.mjs +93 -0
- package/scripts/lib/gate-scheduler.mjs +24 -0
- package/scripts/lib/run-node-tests.mjs +5 -1
- package/scripts/release-paths.mjs +22 -1
- package/scripts/test-direct.mjs +2 -1
- package/scripts/test.mjs +3 -1
- package/src/defaults/agents.json +0 -12
- package/src/defaults/mixdog-config.template.json +1 -2
- package/src/defaults/skills/docx/SKILL.md +7 -4
- package/src/defaults/skills/docx/references/html.md +46 -0
- package/src/defaults/skills/docx/references/native-authoring.md +18 -4
- package/src/defaults/skills/pdf/SKILL.md +10 -2
- package/src/defaults/skills/pdf/references/html.md +64 -0
- package/src/defaults/skills/pptx/references/html.md +1 -1
- package/src/defaults/skills/xlsx/SKILL.md +7 -3
- package/src/defaults/skills/xlsx/references/html.md +40 -0
- package/src/defaults/skills/xlsx/references/report-design.md +21 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +6 -5
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +50 -9
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +28 -1
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +34 -8
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +3 -1
- package/src/runtime/attachments/pdf-extract.mjs +7 -3
- package/src/runtime/computer-bridge/client.mjs +1 -1
- package/src/runtime/memory/index.mjs +4 -15
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +0 -2
- package/src/runtime/memory/lib/cycle-scheduler/backlog-probe.mjs +3 -11
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +1 -3
- package/src/runtime/memory/lib/cycle-scheduler/scheduled-enqueue.mjs +1 -11
- package/src/runtime/memory/lib/cycle-scheduler/tick-loop.mjs +0 -2
- package/src/runtime/memory/lib/cycle-scheduler.mjs +4 -13
- package/src/runtime/memory/lib/cycle-signatures.mjs +0 -6
- package/src/runtime/memory/lib/cycle1/cycle1-rows.mjs +1 -1
- package/src/runtime/memory/lib/cycle1/cycle1-window.mjs +2 -2
- package/src/runtime/memory/lib/embedding-reindex.mjs +1 -1
- package/src/runtime/memory/lib/http-router/lifecycle-routes.mjs +0 -1
- package/src/runtime/memory/lib/memory-action-handlers/cycle-actions.mjs +7 -52
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +6 -22
- package/src/runtime/memory/lib/memory-action-handlers/cycle-rebuild-action.mjs +10 -17
- package/src/runtime/memory/lib/memory-action-handlers/maintenance-actions.mjs +1 -3
- package/src/runtime/memory/lib/memory-action-handlers/manage-actions.mjs +2 -4
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -4
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +1 -1
- package/src/runtime/memory/lib/{memory-cycle2-shared.mjs → memory-cycle-shared.mjs} +14 -16
- package/src/runtime/memory/lib/memory-cycle.mjs +3 -2
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-embed.mjs +14 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +8 -11
- package/src/runtime/memory/lib/memory-schema/entries.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +2 -6
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +1 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +1 -1
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +0 -6
- package/src/runtime/memory/lib/recall-format.mjs +1 -1
- package/src/runtime/office/authoring/docx-html-build.mjs +375 -0
- package/src/runtime/office/authoring/docx-html-measure.mjs +561 -0
- package/src/runtime/office/authoring/docx-html-runner.mjs +102 -0
- package/src/runtime/office/authoring/html-browser.mjs +61 -0
- package/src/runtime/office/authoring/html-document-author.mjs +181 -0
- package/src/runtime/office/authoring/html-source-drift.mjs +36 -0
- package/src/runtime/office/authoring/pdf-author-action.mjs +266 -0
- package/src/runtime/office/authoring/pdf-html-charts.mjs +379 -0
- package/src/runtime/office/authoring/pdf-html-runner.mjs +317 -0
- package/src/runtime/office/authoring/pptx-author-action.mjs +44 -22
- package/src/runtime/office/authoring/pptx-author-session.mjs +12 -6
- package/src/runtime/office/authoring/pptx-html-build.mjs +23 -4
- package/src/runtime/office/authoring/pptx-html-measure.mjs +38 -44
- package/src/runtime/office/authoring/pptx-html-runner.mjs +18 -3
- package/src/runtime/office/authoring/pptx-receipt.mjs +39 -31
- package/src/runtime/office/authoring/pptx-review-artifacts.mjs +9 -1
- package/src/runtime/office/authoring/pptx-script-runner.mjs +6 -2
- package/src/runtime/office/authoring/xlsx-html-build.mjs +365 -0
- package/src/runtime/office/authoring/xlsx-html-measure.mjs +257 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +139 -0
- package/src/runtime/office/capabilities-catalog.mjs +8 -0
- package/src/runtime/office/capabilities-signatures.mjs +18 -6
- package/src/runtime/office/capabilities.mjs +26 -1
- package/src/runtime/office/com/office-com-host.ps1 +133 -17
- package/src/runtime/office/core/office-actions-batch.mjs +16 -1
- package/src/runtime/office/core/office-actions-inspect.mjs +3 -2
- package/src/runtime/office/core/office-actions-lifecycle.mjs +6 -2
- package/src/runtime/office/core/office-actions-render.mjs +4 -1
- package/src/runtime/office/core/office-finalize/review-stage.mjs +15 -2
- package/src/runtime/office/core/office-qa/design-review-stage.mjs +9 -0
- package/src/runtime/office/core/office-session-dispatch.mjs +3 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +7 -2
- package/src/runtime/office/core/office-sessions.mjs +19 -2
- package/src/runtime/office/pdf/pdf-writer.mjs +1 -1
- package/src/runtime/office/portable/docx-formatting.mjs +21 -0
- package/src/runtime/office/portable/ooxml-validator.mjs +22 -1
- package/src/runtime/office/portable/portable-chart.mjs +10 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +12 -6
- package/src/runtime/office/portable/portable-docx-operations.mjs +75 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +49 -6
- package/src/runtime/office/portable/portable-docx.mjs +2 -0
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +2 -0
- package/src/runtime/office/portable/portable-validation.mjs +6 -3
- package/src/runtime/office/portable/portable-xlsx-cell-values.mjs +87 -0
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +55 -13
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +1 -1
- package/src/runtime/office/portable/xlsx-contract.mjs +82 -3
- package/src/runtime/office/quality/assurance-checklist.mjs +2 -2
- package/src/runtime/office/quality/assurance-rendered.mjs +27 -1
- package/src/runtime/office/quality/assurance-structure-docx.mjs +44 -0
- package/src/runtime/office/quality/assurance-structure-pptx.mjs +24 -6
- package/src/runtime/office/quality/assurance-structure-xlsx.mjs +50 -1
- package/src/runtime/office/quality/design-review-critique.mjs +3 -2
- package/src/runtime/office/quality/document-brief.mjs +44 -0
- package/src/runtime/office/quality/inline-audit.mjs +7 -2
- package/src/runtime/office/quality/quality-pipeline.mjs +9 -1
- package/src/runtime/office/tool-defs.mjs +2 -2
- package/src/runtime/shared/llm/quota-value-estimate.mjs +317 -0
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +266 -63
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +35 -0
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +10 -1
- package/src/runtime/shared/provider-accounts.mjs +11 -1
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -4
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/wait-until.test-support.mjs +33 -0
- package/src/session-runtime/boot/apis.mjs +1 -1
- package/src/session-runtime/services/channel-admin.mjs +4 -3
- package/src/session-runtime/services/usage-stats-model.mjs +22 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +6 -1
- package/src/session-runtime/turn/session-ops.mjs +15 -9
- package/src/session-runtime/usage-stats-api.mjs +34 -2
- package/src/standalone/agent-dispatch-broker.mjs +1 -1
- package/src/standalone/local-session-runtime.mjs +488 -0
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-runtime-inline-host.mjs +1 -1
- package/src/tui/hooks/useSession.mjs +1 -1
- package/src/tui/session/session-api/commands.mjs +1 -1
- package/src/tui/session/session-api/integrations.mjs +3 -0
- package/src/tui/session/session-flow/auto-clear.mjs +6 -7
- package/src/tui/session/tool-card-results.mjs +1 -1
- package/src/tui/session/turn.mjs +5 -0
- package/src/tui/session-local.mjs +3 -488
- package/src/tui/session.mjs +1 -1
- package/src/ui/statusline-agents.mjs +0 -1
- package/src/rules/agent/41-cycle2-agent.md +0 -24
- package/src/runtime/memory/lib/cycle-scheduler/cycle2-runs.mjs +0 -120
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +0 -110
- package/src/runtime/memory/lib/memory-cycle2-quarantine.mjs +0 -99
- package/src/runtime/memory/lib/memory-cycle2-review.mjs +0 -240
- package/src/runtime/memory/lib/memory-cycle2.mjs +0 -191
|
@@ -8,14 +8,16 @@ import { writeFile } from 'node:fs/promises';
|
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import { startChildGuardian } from '../../shared/child-guardian.mjs';
|
|
11
|
+
import { launchHtmlBrowser, runInBrowser } from './html-browser.mjs';
|
|
13
12
|
import { iconGlobal } from './pptx-icons.mjs';
|
|
14
13
|
import { GEOMETRY_TOLERANCE_PX, NEAR_MISS_PX, readGeometry } from './pptx-html-geometry.mjs';
|
|
15
14
|
|
|
16
15
|
const require = createRequire(import.meta.url);
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
/** The deck canvas in CSS px and the page it lands on in inches; a PDF's designed pages pass their sheet instead. */
|
|
18
|
+
export const HTML_CANVAS = { width: 1920, height: 1080, inchWidth: 13.333, inchHeight: 7.5 };
|
|
19
|
+
|
|
20
|
+
const viewport = (canvas, deviceScaleFactor) => ({ width: canvas.width, height: canvas.height, deviceScaleFactor });
|
|
19
21
|
|
|
20
22
|
// Runs inside the page: everything it needs is defined here. Returns one
|
|
21
23
|
// slide's items in paint order (a parent's box before its children).
|
|
@@ -464,7 +466,7 @@ function showSlide(index) {
|
|
|
464
466
|
|
|
465
467
|
// <i data-icon="name" data-sw="1.75"> becomes the offline Lucide SVG, sized by the <i>'s CSS box
|
|
466
468
|
// and stroked in its CSS color.
|
|
467
|
-
function injectIcons(table, viewBox) {
|
|
469
|
+
export function injectIcons(table, viewBox) {
|
|
468
470
|
const missing = [];
|
|
469
471
|
for (const i of document.querySelectorAll('i[data-icon]')) {
|
|
470
472
|
const markup = table[String(i.dataset.icon || '').toLowerCase()];
|
|
@@ -488,18 +490,18 @@ const CAPTURE_SCALE = 2;
|
|
|
488
490
|
|
|
489
491
|
// Each marked element photographed alone at twice the canvas density on a transparent page, trimmed to
|
|
490
492
|
// what it painted (shadows, pseudo-elements and turned corners included): id → { png, box } in CSS px.
|
|
491
|
-
async function captureRichPaint(page, slideBox, ids) {
|
|
493
|
+
async function captureRichPaint(page, slideBox, ids, canvas) {
|
|
492
494
|
const out = new Map();
|
|
493
495
|
if (!ids.length) return out;
|
|
494
496
|
const sharp = require('sharp');
|
|
495
|
-
await page.setViewport(
|
|
497
|
+
await page.setViewport(viewport(canvas, CAPTURE_SCALE));
|
|
496
498
|
try {
|
|
497
499
|
for (const id of ids) {
|
|
498
500
|
await page.evaluate(isolateCapture, id);
|
|
499
501
|
// Runtimes with Uint8Array.fromBase64 get a plain Uint8Array back, whose toString is not base64.
|
|
500
502
|
const shot = Buffer.from(
|
|
501
503
|
await page.screenshot({
|
|
502
|
-
clip: { x: slideBox.x, y: slideBox.y, width:
|
|
504
|
+
clip: { x: slideBox.x, y: slideBox.y, width: canvas.width, height: canvas.height },
|
|
503
505
|
omitBackground: true,
|
|
504
506
|
type: 'png',
|
|
505
507
|
})
|
|
@@ -511,7 +513,7 @@ async function captureRichPaint(page, slideBox, ids) {
|
|
|
511
513
|
if (!alpha || alpha.min === 255) {
|
|
512
514
|
out.set(id, {
|
|
513
515
|
png: shot.toString('base64'),
|
|
514
|
-
draw: { x: 0, y: 0, w:
|
|
516
|
+
draw: { x: 0, y: 0, w: canvas.width, h: canvas.height },
|
|
515
517
|
});
|
|
516
518
|
continue;
|
|
517
519
|
}
|
|
@@ -530,39 +532,40 @@ async function captureRichPaint(page, slideBox, ids) {
|
|
|
530
532
|
}
|
|
531
533
|
} finally {
|
|
532
534
|
await page.evaluate(isolateCapture, null);
|
|
533
|
-
await page.setViewport(
|
|
535
|
+
await page.setViewport(viewport(canvas, 1));
|
|
534
536
|
}
|
|
535
537
|
return out;
|
|
536
538
|
}
|
|
537
539
|
|
|
540
|
+
/** The offline Lucide table `injectIcons` takes, keyed by icon name. */
|
|
541
|
+
export function iconTable() {
|
|
542
|
+
const icons = iconGlobal();
|
|
543
|
+
return { icons, table: Object.fromEntries(icons.names.map((name) => [name, icons(name)])) };
|
|
544
|
+
}
|
|
545
|
+
|
|
538
546
|
/**
|
|
539
547
|
* Lays out an HTML deck in a local browser and measures it.
|
|
540
548
|
* @param {string} html the deck
|
|
541
|
-
* @param {{ sourcePath: string, shotPath: (page: number) => string, timeoutMs?: number, signal?: AbortSignal
|
|
549
|
+
* @param {{ sourcePath: string, shotPath: (page: number) => string, timeoutMs?: number, signal?: AbortSignal,
|
|
550
|
+
* prepare?: (page: object) => Promise<object> }} options
|
|
542
551
|
* sourcePath: where the HTML is written before loading (beside the deck, so relative <img> paths resolve)
|
|
543
|
-
*
|
|
552
|
+
* prepare: run on the loaded page; returns the canvas (`HTML_CANVAS` shape) the slides are measured on
|
|
553
|
+
* @returns {Promise<{ width: number, height: number, inchWidth: number, inchHeight: number, slides: object[], shots: string[], notes: string[], geometry: object[] }>}
|
|
544
554
|
* geometry: per slide with findings, `{ slide, findings }` from pptx-html-geometry.mjs
|
|
545
555
|
*/
|
|
546
|
-
export async function measureHtmlDeck(
|
|
556
|
+
export async function measureHtmlDeck(
|
|
557
|
+
html,
|
|
558
|
+
{ sourcePath, shotPath, timeoutMs = 90_000, signal = null, prepare = null }
|
|
559
|
+
) {
|
|
547
560
|
await writeFile(sourcePath, html, 'utf8');
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
headless: true,
|
|
551
|
-
...resolveBrowserLaunchOptions(),
|
|
552
|
-
args: buildPuppeteerLaunchArgs(['--font-render-hinting=none']),
|
|
553
|
-
});
|
|
554
|
-
try {
|
|
555
|
-
startChildGuardian({ childPid: browser.process?.()?.pid, label: 'pptx-html-browser' });
|
|
556
|
-
} catch {}
|
|
557
|
-
const abort = () => browser.close().catch(() => {});
|
|
558
|
-
signal?.addEventListener('abort', abort, { once: true });
|
|
559
|
-
let timer = null;
|
|
560
|
-
const work = (async () => {
|
|
561
|
+
const browser = await launchHtmlBrowser('pptx-html-browser');
|
|
562
|
+
const work = async () => {
|
|
561
563
|
const page = await browser.newPage();
|
|
562
|
-
await page.setViewport(
|
|
564
|
+
await page.setViewport(viewport(HTML_CANVAS, 1));
|
|
563
565
|
await page.goto(pathToFileURL(sourcePath).href, { waitUntil: 'load' });
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
+
const canvas = prepare ? await prepare(page) : HTML_CANVAS;
|
|
567
|
+
if (canvas !== HTML_CANVAS) await page.setViewport(viewport(canvas, 1));
|
|
568
|
+
const { icons, table } = iconTable();
|
|
566
569
|
const missing = await page.evaluate(injectIcons, table, '0 0 24 24');
|
|
567
570
|
if (missing.length) {
|
|
568
571
|
const hint = missing.map((name) => {
|
|
@@ -578,7 +581,7 @@ export async function measureHtmlDeck(html, { sourcePath, shotPath, timeoutMs =
|
|
|
578
581
|
const count = await page.$$eval('section.slide', (sections) => sections.length);
|
|
579
582
|
if (!count)
|
|
580
583
|
throw new Error(
|
|
581
|
-
|
|
584
|
+
`The HTML holds no <section class="slide">; every page is one section.slide on a ${canvas.width}×${canvas.height} canvas.`
|
|
582
585
|
);
|
|
583
586
|
const slides = [];
|
|
584
587
|
const shots = [];
|
|
@@ -589,9 +592,9 @@ export async function measureHtmlDeck(html, { sourcePath, shotPath, timeoutMs =
|
|
|
589
592
|
await page.evaluate(() => document.fonts.ready);
|
|
590
593
|
const element = (await page.$$('section.slide'))[index];
|
|
591
594
|
const box = await element.boundingBox();
|
|
592
|
-
if (Math.round(box.width) !==
|
|
595
|
+
if (Math.round(box.width) !== canvas.width || Math.round(box.height) !== canvas.height) {
|
|
593
596
|
throw new Error(
|
|
594
|
-
`slide ${index + 1} measures ${Math.round(box.width)}×${Math.round(box.height)} px; every section.slide is exactly
|
|
597
|
+
`slide ${index + 1} measures ${Math.round(box.width)}×${Math.round(box.height)} px; every section.slide is exactly ${canvas.width}×${canvas.height}.`
|
|
595
598
|
);
|
|
596
599
|
}
|
|
597
600
|
const shot = shotPath(index + 1);
|
|
@@ -601,7 +604,7 @@ export async function measureHtmlDeck(html, { sourcePath, shotPath, timeoutMs =
|
|
|
601
604
|
for (const note of measured.notes) notes.push(`slide ${index + 1}: ${note}`);
|
|
602
605
|
const findings = await page.evaluate(readGeometry, index, GEOMETRY_TOLERANCE_PX, NEAR_MISS_PX);
|
|
603
606
|
if (findings.length) geometry.push({ slide: index + 1, findings });
|
|
604
|
-
const drawn = await captureRichPaint(page, box, measured.captures);
|
|
607
|
+
const drawn = await captureRichPaint(page, box, measured.captures, canvas);
|
|
605
608
|
const items = measured.items
|
|
606
609
|
.map((item) => {
|
|
607
610
|
if (item.kind !== 'capture') return item;
|
|
@@ -611,16 +614,7 @@ export async function measureHtmlDeck(html, { sourcePath, shotPath, timeoutMs =
|
|
|
611
614
|
.filter(Boolean);
|
|
612
615
|
slides.push({ bg: measured.bg, items, notes: measured.speakerNotes });
|
|
613
616
|
}
|
|
614
|
-
return { ...
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
timer = setTimeout(() => reject(new Error(`HTML layout exceeded ${timeoutMs} ms`)), timeoutMs);
|
|
618
|
-
});
|
|
619
|
-
try {
|
|
620
|
-
return await Promise.race([work, timeout]);
|
|
621
|
-
} finally {
|
|
622
|
-
clearTimeout(timer);
|
|
623
|
-
signal?.removeEventListener('abort', abort);
|
|
624
|
-
await browser.close().catch(() => {});
|
|
625
|
-
}
|
|
617
|
+
return { ...canvas, slides, shots, notes, geometry };
|
|
618
|
+
};
|
|
619
|
+
return runInBrowser(browser, { signal, timeoutMs }, work);
|
|
626
620
|
}
|
|
@@ -41,9 +41,18 @@ function failure(error, startedAt) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/** Whether the HTML is written as pages under the deck contract: one <section class="slide"> per page. */
|
|
45
|
+
export function hasSlideSections(html) {
|
|
46
|
+
return /<section[^>]*class=["'][^"']*\bslide\b/i.test(String(html || ''));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {{ target: string, signal?: AbortSignal, prepare?: (page: object) => Promise<object> }} options
|
|
51
|
+
* prepare: names the canvas on the loaded page (pptx-html-measure.mjs); the deck's 1920 × 1080 without it
|
|
52
|
+
*/
|
|
53
|
+
export async function runPptxHtmlAuthoring(html, output, { target, signal = null, prepare = null }) {
|
|
45
54
|
const source = String(html || '');
|
|
46
|
-
if (
|
|
55
|
+
if (!hasSlideSections(source)) {
|
|
47
56
|
throw new Error('author html requires <section class="slide"> pages; see the pptx skill html reference.');
|
|
48
57
|
}
|
|
49
58
|
const startedAt = performance.now();
|
|
@@ -52,7 +61,12 @@ export async function runPptxHtmlAuthoring(html, output, { target, signal = null
|
|
|
52
61
|
await rm(output, { force: true });
|
|
53
62
|
let measure;
|
|
54
63
|
try {
|
|
55
|
-
measure = await measureHtmlDeck(source, {
|
|
64
|
+
measure = await measureHtmlDeck(source, {
|
|
65
|
+
sourcePath: artifacts.source,
|
|
66
|
+
shotPath: artifacts.shot,
|
|
67
|
+
signal,
|
|
68
|
+
prepare,
|
|
69
|
+
});
|
|
56
70
|
} catch (error) {
|
|
57
71
|
if (signal?.aborted) throw error;
|
|
58
72
|
return failure(error, startedAt);
|
|
@@ -70,6 +84,7 @@ export async function runPptxHtmlAuthoring(html, output, { target, signal = null
|
|
|
70
84
|
bytes: info.size,
|
|
71
85
|
logs: measure.notes.map((text) => ({ level: 'warn', text })),
|
|
72
86
|
kit: 'html',
|
|
87
|
+
slideCount: measure.slides.length,
|
|
73
88
|
normalizedParagraphs: normalized.removed,
|
|
74
89
|
htmlShots: measure.shots,
|
|
75
90
|
htmlText: measure.slides.map((slide) => {
|
|
@@ -8,7 +8,13 @@ import { plannedCarrierGaps } from './pptx-brief.mjs';
|
|
|
8
8
|
import { isPictureShape, relativeLuminance } from '../design/design-discipline.mjs';
|
|
9
9
|
import { rectangleGap } from '../portable/pptx-relations.mjs';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// The page the shapes sit on, in points: the deck's 13.33 × 7.5 in unless the document names its slide size
|
|
12
|
+
// (a PDF's designed pages are a sheet).
|
|
13
|
+
const WIDE_CANVAS = Object.freeze({ width: 960, height: 540 });
|
|
14
|
+
const canvasOf = (document) =>
|
|
15
|
+
Number(document?.slideWidth) > 0 && Number(document?.slideHeight) > 0
|
|
16
|
+
? { width: Number(document.slideWidth), height: Number(document.slideHeight) }
|
|
17
|
+
: WIDE_CANVAS;
|
|
12
18
|
|
|
13
19
|
function backgroundRole(slide) {
|
|
14
20
|
const l = relativeLuminance(slide?.background?.color);
|
|
@@ -27,8 +33,8 @@ function saturation(hex) {
|
|
|
27
33
|
|
|
28
34
|
// A field that owns the page: a dark or saturated surface over three fifths of the canvas is what a reader
|
|
29
35
|
// sees as a beat (a cover, a section mark, a claim on a field), whatever the slide's own background says.
|
|
30
|
-
function isBeatField(fill, area) {
|
|
31
|
-
if (!fill || area <
|
|
36
|
+
function isBeatField(fill, area, canvas) {
|
|
37
|
+
if (!fill || area < canvas.width * canvas.height * 0.6) return false;
|
|
32
38
|
const l = relativeLuminance(fill);
|
|
33
39
|
return (l !== null && l < 0.35) || saturation(fill) > 0.35;
|
|
34
40
|
}
|
|
@@ -48,8 +54,6 @@ function surfaceColor(shape) {
|
|
|
48
54
|
// Air, quadrant air, the largest object's share, the visual footprint, text
|
|
49
55
|
// left edges, fill areas, and where the title sits. Read from shape footprints
|
|
50
56
|
// on a 5 pt raster; no threshold turns any of them into a verdict.
|
|
51
|
-
const CANVAS_W = 960;
|
|
52
|
-
const CANVAS_H = 540;
|
|
53
57
|
const CELL = 5;
|
|
54
58
|
|
|
55
59
|
function footprint(shape) {
|
|
@@ -72,9 +76,9 @@ function extent(shape) {
|
|
|
72
76
|
return { left, top, width, height };
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
function raster(shapes) {
|
|
76
|
-
const cols = Math.ceil(
|
|
77
|
-
rows = Math.ceil(
|
|
79
|
+
function raster(shapes, canvas) {
|
|
80
|
+
const cols = Math.ceil(canvas.width / CELL),
|
|
81
|
+
rows = Math.ceil(canvas.height / CELL);
|
|
78
82
|
const cells = new Uint8Array(cols * rows);
|
|
79
83
|
for (const box of shapes) {
|
|
80
84
|
const x0 = Math.max(0, Math.floor(box.left / CELL)),
|
|
@@ -211,20 +215,20 @@ function noteSpec(specs, shape) {
|
|
|
211
215
|
// in canvas units [0, 1]; offset is its ellipse-normalized distance from the canvas center with
|
|
212
216
|
// a horizontal tolerance of 0.05 and a vertical one of 0.15 (a sideways drift reads first, as in
|
|
213
217
|
// AeSlides' imbalance metric). Both are numbers the author weighs; a breathing slide may sit off center on purpose.
|
|
214
|
-
function centroidOf(boxes) {
|
|
218
|
+
function centroidOf(boxes, canvas) {
|
|
215
219
|
let area = 0,
|
|
216
220
|
sx = 0,
|
|
217
221
|
sy = 0;
|
|
218
222
|
for (const box of boxes) {
|
|
219
223
|
const a = box.width * box.height;
|
|
220
|
-
if (a >=
|
|
224
|
+
if (a >= canvas.width * canvas.height * 0.9) continue;
|
|
221
225
|
area += a;
|
|
222
226
|
sx += a * (box.left + box.width / 2);
|
|
223
227
|
sy += a * (box.top + box.height / 2);
|
|
224
228
|
}
|
|
225
229
|
if (!area) return null;
|
|
226
|
-
const x = sx / area /
|
|
227
|
-
y = sy / area /
|
|
230
|
+
const x = sx / area / canvas.width,
|
|
231
|
+
y = sy / area / canvas.height;
|
|
228
232
|
const offset = Math.sqrt(((x - 0.5) / 0.05) ** 2 + ((y - 0.5) / 0.15) ** 2);
|
|
229
233
|
return { centroid: [Number(x.toFixed(2)), Number(y.toFixed(2))], centroidOffset: Number(offset.toFixed(1)) };
|
|
230
234
|
}
|
|
@@ -233,16 +237,17 @@ function centroidOf(boxes) {
|
|
|
233
237
|
// content zone begins. Read across the deck it shows whether content slides share one body top.
|
|
234
238
|
// Body fill: how much of the zone under the title (down to the lower safe margin) the content spans,
|
|
235
239
|
// as a share — a dense slide whose content stops halfway reads as a hollow; a breathing slide is meant to.
|
|
236
|
-
function bodyTopOf(boxes, titleBox) {
|
|
240
|
+
function bodyTopOf(boxes, titleBox, canvas) {
|
|
237
241
|
if (!titleBox) return { bodyTop: null, bodyFill: null };
|
|
238
242
|
const titleBottom = titleBox.top + titleBox.height;
|
|
239
243
|
const below = boxes.filter(
|
|
240
|
-
(box) =>
|
|
244
|
+
(box) =>
|
|
245
|
+
box !== titleBox && box.top >= titleBottom - 2 && box.width * box.height < canvas.width * canvas.height * 0.9
|
|
241
246
|
);
|
|
242
247
|
if (!below.length) return { bodyTop: null, bodyFill: null };
|
|
243
248
|
const top = Math.min(...below.map((box) => box.top)),
|
|
244
249
|
bottom = Math.max(...below.map((box) => box.top + box.height));
|
|
245
|
-
const zone =
|
|
250
|
+
const zone = canvas.height - 0.5 * 72 - top;
|
|
246
251
|
return {
|
|
247
252
|
bodyTop: Number((top / 72).toFixed(2)),
|
|
248
253
|
bodyFill: zone > 0 ? Number(Math.min(1, (bottom - top) / zone).toFixed(2)) : null,
|
|
@@ -253,8 +258,8 @@ function bodyTopOf(boxes, titleBox) {
|
|
|
253
258
|
// fieldFill is the share of each field of 6% of the canvas or more (a full-page background is not one)
|
|
254
259
|
// that content actually covers, lowest first. Neither `air` nor `quadrantAir` can say this — the field's
|
|
255
260
|
// own footprint fills the very quadrant it leaves empty (user: 이 사각은 밸런스가 망가진 것 같은데).
|
|
256
|
-
function fieldFillOf(surfaces, grid) {
|
|
257
|
-
const canvas =
|
|
261
|
+
function fieldFillOf(surfaces, grid, page) {
|
|
262
|
+
const canvas = page.width * page.height;
|
|
258
263
|
return surfaces
|
|
259
264
|
.filter((box) => box.width * box.height >= canvas * 0.06 && box.width * box.height < canvas * 0.9)
|
|
260
265
|
.map(
|
|
@@ -275,27 +280,28 @@ function fieldFillOf(surfaces, grid) {
|
|
|
275
280
|
|
|
276
281
|
function observe(
|
|
277
282
|
shapes,
|
|
278
|
-
{ textBoxes, visuals, content, blocks, surfaces = [], constructs = [], labels = [], fills, titleBox }
|
|
283
|
+
{ textBoxes, visuals, content, blocks, surfaces = [], constructs = [], labels = [], fills, titleBox, page }
|
|
279
284
|
) {
|
|
280
285
|
const boxes = shapes.map(footprint).filter(Boolean);
|
|
281
286
|
if (!boxes.length) return null;
|
|
282
287
|
const authored = shapes.filter((shape) => !isChrome(shape));
|
|
283
288
|
const typeSet = [...new Set(authored.flatMap(typeSizesOf))].sort((a, b) => a - b);
|
|
284
289
|
const textColors = [...new Set(authored.filter((shape) => String(shape.text || '').trim()).flatMap(textColorsOf))];
|
|
285
|
-
const all = raster(boxes);
|
|
286
|
-
const inner = raster(content); // the same canvas read from content alone: where a surface is carrying nothing
|
|
287
|
-
const centroid = centroidOf(boxes);
|
|
290
|
+
const all = raster(boxes, page);
|
|
291
|
+
const inner = raster(content, page); // the same canvas read from content alone: where a surface is carrying nothing
|
|
292
|
+
const centroid = centroidOf(boxes, page);
|
|
288
293
|
// Body top and gaps read the content (text, charts, tables, pictures, contours), never a surface field or a rule.
|
|
289
294
|
const { bodyTop, bodyFill } = bodyTopOf(
|
|
290
295
|
content,
|
|
291
296
|
titleBox
|
|
292
297
|
? content.find((box) => box.left === titleBox.left && box.top === titleBox.top && box.width === titleBox.width) ||
|
|
293
298
|
titleBox
|
|
294
|
-
: null
|
|
299
|
+
: null,
|
|
300
|
+
page
|
|
295
301
|
);
|
|
296
302
|
const midX = Math.floor(all.cols / 2),
|
|
297
303
|
midY = Math.floor(all.rows / 2);
|
|
298
|
-
const canvas =
|
|
304
|
+
const canvas = page.width * page.height;
|
|
299
305
|
const fillShares = [...fills.entries()]
|
|
300
306
|
.map(([color, area]) => ({ color, share: Number((area / canvas).toFixed(2)) }))
|
|
301
307
|
.filter((entry) => entry.share > 0)
|
|
@@ -327,9 +333,9 @@ function observe(
|
|
|
327
333
|
airOf(inner, 0, midY, midX, inner.rows),
|
|
328
334
|
airOf(inner, midX, midY, inner.cols, inner.rows),
|
|
329
335
|
],
|
|
330
|
-
fieldFill: fieldFillOf(surfaces, inner),
|
|
336
|
+
fieldFill: fieldFillOf(surfaces, inner, page),
|
|
331
337
|
largestShare: Number((Math.max(...boxes.map((box) => box.width * box.height)) / canvas).toFixed(2)),
|
|
332
|
-
visualShare: visuals.length ? Number((1 - airOf(raster(visuals))).toFixed(2)) : 0,
|
|
338
|
+
visualShare: visuals.length ? Number((1 - airOf(raster(visuals, page))).toFixed(2)) : 0,
|
|
333
339
|
presence,
|
|
334
340
|
textColumns: { ...leftEdges(textBoxes.filter((box) => !labels.includes(box))), labels: labels.length },
|
|
335
341
|
fills: fillShares,
|
|
@@ -343,7 +349,7 @@ function observe(
|
|
|
343
349
|
};
|
|
344
350
|
}
|
|
345
351
|
|
|
346
|
-
export function slideReceipt(slide) {
|
|
352
|
+
export function slideReceipt(slide, page = WIDE_CANVAS) {
|
|
347
353
|
const shapes = Array.isArray(slide?.shapes) ? slide.shapes : [];
|
|
348
354
|
const receipt = {
|
|
349
355
|
slide: Number(slide?.index) || 0,
|
|
@@ -362,10 +368,10 @@ export function slideReceipt(slide) {
|
|
|
362
368
|
coverage: 0,
|
|
363
369
|
grammar: 'text',
|
|
364
370
|
};
|
|
365
|
-
const read = readShapes(shapes, receipt);
|
|
371
|
+
const read = readShapes(shapes, receipt, page);
|
|
366
372
|
receipt.presets = [...read.presets];
|
|
367
373
|
if (Object.keys(read.specs).length) receipt.specs = read.specs;
|
|
368
|
-
receipt.coverage = Math.min(1, Number((read.covered /
|
|
374
|
+
receipt.coverage = Math.min(1, Number((read.covered / (page.width * page.height)).toFixed(2)));
|
|
369
375
|
receipt.grammar = pageGrammar(receipt, read);
|
|
370
376
|
const observed = observe(read.seen, {
|
|
371
377
|
textBoxes: read.textBoxes,
|
|
@@ -377,6 +383,7 @@ export function slideReceipt(slide) {
|
|
|
377
383
|
labels: diagramLabels(read.textBoxes, read.constructs),
|
|
378
384
|
fills: read.fills,
|
|
379
385
|
titleBox: read.titleBox,
|
|
386
|
+
page,
|
|
380
387
|
});
|
|
381
388
|
if (observed) receipt.observe = observed;
|
|
382
389
|
return receipt;
|
|
@@ -388,7 +395,7 @@ export function slideReceipt(slide) {
|
|
|
388
395
|
* content, spacing blocks, surfaces, drawn constructs — plus the covered
|
|
389
396
|
* area, the fill weights and the largest text box.
|
|
390
397
|
*/
|
|
391
|
-
function readShapes(shapes, receipt) {
|
|
398
|
+
function readShapes(shapes, receipt, page) {
|
|
392
399
|
let covered = 0;
|
|
393
400
|
let beatField = false;
|
|
394
401
|
const presets = new Set();
|
|
@@ -481,7 +488,7 @@ function readShapes(shapes, receipt) {
|
|
|
481
488
|
receipt.fields += 1;
|
|
482
489
|
covered += area;
|
|
483
490
|
if (box && fill) surfaces.push(box);
|
|
484
|
-
if (isBeatField(fill, area)) beatField = true;
|
|
491
|
+
if (isBeatField(fill, area, page)) beatField = true;
|
|
485
492
|
} else if (geometry) {
|
|
486
493
|
presets.add(geometry);
|
|
487
494
|
covered += area;
|
|
@@ -540,7 +547,8 @@ function diagramLabels(textBoxes, constructs) {
|
|
|
540
547
|
// The whole deck: per-slide receipts, totals, the families that never appear,
|
|
541
548
|
// and the plan lines whose named carriers the snapshot cannot see.
|
|
542
549
|
export function compositionReceipt(document, brief = null) {
|
|
543
|
-
const
|
|
550
|
+
const page = canvasOf(document);
|
|
551
|
+
const slides = (Array.isArray(document?.slides) ? document.slides : []).map((slide) => slideReceipt(slide, page));
|
|
544
552
|
const deck = {
|
|
545
553
|
slides: slides.length,
|
|
546
554
|
charts: 0,
|
|
@@ -21,7 +21,15 @@ export async function pptxReviewArtifacts(session, preview) {
|
|
|
21
21
|
// The receipt reads an authored deck against the brief it was written to. A deck opened for editing has no
|
|
22
22
|
// brief, and its pages are reviewed as edits, so its render carries the pages alone — a template's receipt
|
|
23
23
|
// ran to fifteen thousand characters beside three page images.
|
|
24
|
-
|
|
24
|
+
// A PDF of designed pages keeps the receipt its working deck was read into at author time (the PDF
|
|
25
|
+
// itself holds no shapes); each render reads its own copy against the printed pixels.
|
|
26
|
+
const receipt = session.authoredFrame
|
|
27
|
+
? session.frameReceipt
|
|
28
|
+
? structuredClone(session.frameReceipt)
|
|
29
|
+
: null
|
|
30
|
+
: session.authored === true
|
|
31
|
+
? await readCompositionReceipt(session)
|
|
32
|
+
: null;
|
|
25
33
|
if (receipt) {
|
|
26
34
|
const air = await renderedAirByPage(result._images).catch(() => null);
|
|
27
35
|
if (air) attachRenderedAir(receipt, air);
|
|
@@ -98,7 +98,11 @@ async function pathExists(path) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
export async function runPptxAuthoringScript(
|
|
101
|
+
export async function runPptxAuthoringScript(
|
|
102
|
+
script,
|
|
103
|
+
output,
|
|
104
|
+
{ timeoutMs = PPTX_SCRIPT_CONTRACT.timeoutMs, cwd = dirname(output) } = {}
|
|
105
|
+
) {
|
|
102
106
|
const source = String(script || '');
|
|
103
107
|
if (!source.trim()) throw new Error('author requires a non-empty pptxgenjs script');
|
|
104
108
|
await mkdir(dirname(output), { recursive: true });
|
|
@@ -126,7 +130,7 @@ export async function runPptxAuthoringScript(script, output, { timeoutMs = PPTX_
|
|
|
126
130
|
} catch (error) {
|
|
127
131
|
return { ok: false, error: scriptError(error, source, preludeLines), logs, elapsedMs: 0 };
|
|
128
132
|
}
|
|
129
|
-
const scopedProcess = { env: { ...process.env }, cwd: () =>
|
|
133
|
+
const scopedProcess = { env: { ...process.env }, cwd: () => cwd, platform: process.platform };
|
|
130
134
|
let timer = null;
|
|
131
135
|
const timeout = new Promise((_, reject) => {
|
|
132
136
|
timer = setTimeout(() => reject(new Error(`Authoring script exceeded ${timeoutMs} ms`)), timeoutMs);
|