Skip to content
Open
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
Binary file modified archify.zip
Binary file not shown.
8 changes: 4 additions & 4 deletions archify/bin/visual-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1966,10 +1966,10 @@ function observation({ width, height, theme, metrics }) {
&& readabilityOk
&& Number.isFinite(minimumProjectedNodeTextPx)
&& ((readerLayout === 'adaptive' && readerOverflow === 'authored' && (readerFit === 'intrinsic-height' || readerFit === 'width-first'))
|| (readerFit === 'authored-height' && metrics.diagramType === 'architecture'
|| (readerFit === 'authored-height' && (metrics.diagramType === 'architecture' || metrics.diagramType === 'workflow')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
&& metrics.documentScrollUnclipped === true))
);
const authoredClipped = readerFit === 'authored-height' && metrics.diagramType === 'architecture'
const authoredClipped = readerFit === 'authored-height' && (metrics.diagramType === 'architecture' || metrics.diagramType === 'workflow')
&& metrics.documentScrollUnclipped !== true;
const containmentOk = !authoredClipped && !overflowX && (!overflowY || verticalScrollAccepted);
const legendDockIntersectionArea = Number(metrics.legendDockIntersectionArea) || 0;
Expand Down Expand Up @@ -2183,15 +2183,15 @@ function observationDiagnostics({ artifact, allObservations, readabilityObservat
}));
}
if (!entry.ok) {
const authoredClipped = entry.readerFit === 'authored-height' && entry.diagramType === 'architecture'
const authoredClipped = entry.readerFit === 'authored-height' && (entry.diagramType === 'architecture' || entry.diagramType === 'workflow')
&& !entry.documentScrollUnclipped;
const budgetFixes = authoredClipped
? ['restore the full SVG inside the diagram panel and allow normal document scrolling; remove internal scrollers and clipping without changing authored geometry']
: verticalBudgetFixes(entry);
diagnostics.push(failureDiagnostic({
code: authoredClipped ? 'viewer/diagram-clipped' : 'viewer/viewport-overflow',
message: authoredClipped
? `The authored Architecture canvas is clipped or cannot scroll in the document at ${entry.width}x${entry.height} (${entry.theme}).`
? `The authored ${entry.diagramType === 'workflow' ? 'Workflow' : 'Architecture'} canvas is clipped or cannot scroll in the document at ${entry.width}x${entry.height} (${entry.theme}).`
: `The rendered artifact overflows the ${entry.width}x${entry.height} ${entry.theme} viewport.`,
subject: viewportSubject(artifact, entry),
evidence: {
Expand Down
5 changes: 3 additions & 2 deletions archify/references/delivery-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,9 @@ accepted only with a renderer-declared contract and measured readable text.
Automatic canvases declare `data-reader-fit="intrinsic-height"` (height fitting)
or `data-reader-fit="width-first"` (Sequence and Waterfall reading width); their adaptive
Reader must reach its readable width and expose `data-reader-overflow="authored"`.
Architecture with an explicit `meta.viewBox` instead declares
`data-diagram-type="architecture"` and `data-reader-fit="authored-height"`:
Architecture with an explicit `meta.viewBox`, and schema-v2 workflows with at
least two lanes and an explicit `meta.viewBox` whose height is greater than
800, declare `data-diagram-type="architecture"` (or `"workflow"`) and `data-reader-fit="authored-height"`:
its SVG coordinates, aspect ratio and existing Reader width behavior stay
unchanged. Its full SVG must remain inside the diagram panel without internal
scrolling or clipping, and the document must permit vertical scrolling.
Expand Down
2 changes: 1 addition & 1 deletion archify/renderers/shared/desktop-readability.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function predictedFixedWidthOverflow({
if (![viewBoxWidth, viewBoxHeight].every(Number.isFinite) || viewBoxWidth <= 0 || viewBoxHeight <= 0) return null;
const ratio = viewBoxWidth / viewBoxHeight;
if (readerFit === 'intrinsic-height' || readerFit === 'width-first'
|| (readerFit === 'authored-height' && diagramType === 'architecture')
|| (readerFit === 'authored-height' && (diagramType === 'architecture' || diagramType === 'workflow'))
|| ratio >= DECLARED_WIDE_READER_RATIO) return null;
const svgWidthPx = viewport.width - bodyHorizontalPx - diagramHorizontalPx;
const svgHeightPx = Math.round(svgWidthPx * viewBoxHeight / viewBoxWidth);
Expand Down
20 changes: 12 additions & 8 deletions archify/renderers/workflow/workflow-compiler.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5476,14 +5476,18 @@ function renderLegend() {
}

function renderSvg() {
// A renderer-sized canvas that would overflow the desktop page at full
// width reads at page width with vertical scroll, like a tall stack.
const readerFit = workflow.schema_version === 2
&& !workflow.meta?.viewBox
&& ((hasVerticalStack(workflow) && asArray(layout.laneHeights).some((height) => height > 104))
|| predictedFixedWidthOverflow({ viewBoxWidth: viewBox[0], viewBoxHeight: viewBox[1], diagramType: 'workflow' }))
? ' data-reader-fit="width-first"'
: '';
const isAuthoredHeight = Boolean(workflow.schema_version === 2
&& workflow.meta?.viewBox
&& workflow.lanes?.length >= 2
&& viewBox[1] > 800);
const readerFit = isAuthoredHeight
? ' data-diagram-type="workflow" data-reader-fit="authored-height"'
: (workflow.schema_version === 2
&& !workflow.meta?.viewBox
&& ((hasVerticalStack(workflow) && asArray(layout.laneHeights).some((height) => height > 104))
|| predictedFixedWidthOverflow({ viewBoxWidth: viewBox[0], viewBoxHeight: viewBox[1], diagramType: 'workflow' }))
? ' data-reader-fit="width-first"'
: '');
const contract = workflow.schema_version === 2 ? ' data-layout-contract="readable-v2"' : '';
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}"${readerFit}${contract} ${svgRootAttrs(workflow.meta, resolvedQualityProfile)} ${NODE_CARD_ATTR}>
${svgAccessibleText(workflow.meta, 'workflow')}
Expand Down
2 changes: 1 addition & 1 deletion test/delivery-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test('required browser evidence stays deterministic and capture-free', () => {
assert.match(browserSection, /creates one\s+`<output-stem>\.browser-check\.json` receipt and no screenshots or contact sheet/i);
assert.match(browserSection, /visualReview: "not-requested"/);
assert.match(browserSection, /Reader must reach its readable width/i);
assert.match(browserSection, /Architecture with an explicit `meta.viewBox`[\s\S]*data-reader-fit="authored-height"/);
assert.match(browserSection, /Architecture with an explicit `meta\.viewBox`.*schema-v2 workflows[\s\S]*data-reader-fit="authored-height"/);
assert.match(browserSection, /explicit viewBoxes in other modes[\s\S]*remain failures/);
});

Expand Down
19 changes: 19 additions & 0 deletions test/desktop-reader-browser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -618,4 +618,23 @@ test('authored Architecture canvas keeps its scale and accepts readable document
assert.equal(failed.exitCode, 1);
assert.ok(failed.receipt.diagnostics.some(d => d.code === 'viewer/diagram-clipped'), JSON.stringify(failed.receipt.diagnostics));
}

// Workflow with explicit viewBox: tall swimlane must accept vertical scroll without unexpected overflow (#616)
const wfInput = path.join(tmp, 'wf-input.json');
const wfArtifact = path.join(tmp, 'wf-authored.html');
const wfDoc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
wfDoc.meta.viewBox = [1240, 838];
fs.writeFileSync(wfInput, JSON.stringify(wfDoc));
execFileSync(process.execPath, [path.join(skillRoot, 'bin/archify.mjs'), 'render', 'workflow', wfInput, wfArtifact]);
const wfHtml = fs.readFileSync(wfArtifact, 'utf8');
assert.match(wfHtml, /data-diagram-type="workflow"/);
assert.match(wfHtml, /data-reader-fit="authored-height"/);
const wfResult = await runVisualCheck({ artifactPath: wfArtifact, chromePath });
assert.equal(wfResult.exitCode, 0, JSON.stringify(wfResult.receipt.diagnostics));
assert.ok(wfResult.receipt.containment.viewports.some((viewport) => viewport.overflowY));
for (const viewport of wfResult.receipt.containment.viewports) {
assert.equal(viewport.readerFit, 'authored-height');
assert.equal(viewport.overflowX, false);
if (viewport.overflowY) assert.equal(viewport.verticalScrollAccepted, true);
}
});
12 changes: 12 additions & 0 deletions test/layout-rules.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,18 @@ test('architecture: measured auto canvases opt into height-aware reader fitting'
assert.doesNotMatch(authoredSvg, /data-reader-min-text=/);
});

test('workflow: explicit meta.viewBox declares authored-height and data-diagram-type', () => {
const examplePath = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const doc = JSON.parse(fs.readFileSync(examplePath, 'utf8'));
doc.meta.viewBox = [1240, 838];
const rendered = render('workflow', doc);
assert.equal(rendered.code, 0, rendered.stderr);
const svg = fs.readFileSync(rendered.outPath, 'utf8').match(/<svg\b[^>]*>/)?.[0];
assert.ok(svg, 'expected an SVG root for the authored workflow canvas');
assert.match(svg, /data-diagram-type="workflow"/);
assert.match(svg, /data-reader-fit="authored-height"/);
});

// Automatic Sequence declares width-first; Dataflow declares intrinsic-height.
// Both default canvases need an explicit automatic Reader fit below the wide ratio.
for (const [mode, doc, authoredViewBox] of [
Expand Down
2 changes: 1 addition & 1 deletion test/render-output-checks.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ test('render output check: predicts the certain 1440x900 overflow of a fixed-wid
for (const type of ['architecture', 'workflow', '']) {
const fixed = checkHtml(`viewport-authored-${type}`, node, 'showcase', '0 0 1080 780', '',
` data-reader-fit="authored-height" data-diagram-type="${type}"`);
assert.equal(fixed.result.composition.issues.some(item => item.code === 'composition/viewport-height'), type !== 'architecture');
assert.equal(fixed.result.composition.issues.some(item => item.code === 'composition/viewport-height'), type !== 'architecture' && type !== 'workflow');
}

const wide = checkHtml('viewport-height-wide', node, 'showcase', '0 0 1600 900');
Expand Down
5 changes: 3 additions & 2 deletions test/visual-check.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3450,12 +3450,13 @@ for (const [command, run] of [['browser-check', runBrowserCheck], ['visual-check
}


test('authored Architecture scroll requires readable unclipped document flow and preserves other modes', async () => {
test('authored Architecture and Workflow scroll requires readable unclipped document flow and preserves other modes', async () => {
const input = artifact('authored-scroll.html');
const target = ({ width, theme }) => width === 1440 && theme === 'light';
for (const [name, options, accepted] of [
['readable document', {}, true],
['other diagram mode', { authoredDiagramType: 'workflow' }, false],
['readable workflow document', { authoredDiagramType: 'workflow' }, true],
['other diagram mode', { authoredDiagramType: 'sequence' }, false],
['missing mode', { authoredDiagramType: null }, false],
['clipped or internally scrolled SVG', { authoredUnclipped: false }, false],
['unreadable text', { unreadableAt: target }, false],
Expand Down
3 changes: 2 additions & 1 deletion test/workflow-compiler.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ test('explicit viewBox retains shared-height readable-v2 geometry', () => {
const result = compileSuccessfully(workflow);
assert.deepEqual([0, 1, 2].map((index) => laneFrameRect(result.svg, index).height), [276, 270, 270]);
const svgRoot = result.svg.match(/<svg\b[^>]*>/)?.[0];
assert.equal(attributeOrUndefined(svgRoot, 'data-reader-fit'), undefined);
assert.equal(attributeOrUndefined(svgRoot, 'data-reader-fit'), 'authored-height');
assert.equal(attributeOrUndefined(svgRoot, 'data-diagram-type'), 'workflow');
});

test('absolute route pins retain shared-height readable-v2 geometry', () => {
Expand Down
Loading