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
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ jobs:
tests/test_dag_viewer_browser.py tests/test_integrated_explorer_browser.py
tests/test_integrated_workspace_browser.py tests/test_integrated_continuity_browser.py
tests/test_scoped_overview_browser.py tests/test_phone_fit_control.py
tests/test_integrated_selected_focus_browser.py
tests/test_integrated_selected_focus_browser.py tests/test_integrated_phone_back_browser.py
tests/test_integrated_reader_first_browser.py
env:
AUTOFORM_REQUIRE_BROWSER_TESTS: "1"
AUTOFORM_REQUIRE_MATHJAX_TESTS: "1"
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ date when the release is tagged.

### Changed

- **Explorer delivery.** The approved integrated redesign ships below 144 KB,
- **Explorer delivery.** The approved integrated redesign ships below 145 KB,
with the same bounded graph, JSON and frame budgets and pinned safe compiler.
Touch keeps single-pointer zoom controls; reading handles retain page-owned
mathematical titles, and typed dependency counts remain accessible descriptions.
Expand Down Expand Up @@ -117,6 +117,10 @@ date when the release is tagged.
downloads. ATLAS's
42 MB index had passed the old 32 MiB limit, so search and the Topics
outline failed on every page.
- **Explorer reading defaults.** Desktop chapters open with the reader showing
their text again; only authored-area overviews and scopes over 120 nodes open
map-first, with the 0.9 zoom-out floor. On a phone, Back to a result whose
reader was closed no longer fails with "Could not open this view" (#507).
- **Explorer review follow-ups.** Fit retains a selected unlinked result.
Deferred list math cancels with its view and falls back to visible text on
unavailable typesetting. Preview formatting keeps currency and code literal;
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ each `AUTOFORM_REQUIRE_*` variable below, so it fails where a local run skips:

`autoform_cli/assets/` holds the book/map's shared JS and CSS.
With Node 22.23.2, run `make build-explorer`, `make check-explorer`, and browser tests.
Pinned esbuild/Terser emit one JS response below 144 KB; publication checks the
Pinned esbuild/Terser emit one JS response below 145 KB; publication checks the
UTF-8/LF source fingerprint. CRLF checkouts work too. Consumers need no Node.
Compression renames only private properties, never graph/index/route/history
fields, and uses no unsafe transforms or dropped logs.
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ test-browser:
tests/test_dag_viewer_browser.py tests/test_integrated_explorer_browser.py \
tests/test_integrated_workspace_browser.py tests/test_integrated_continuity_browser.py \
tests/test_scoped_overview_browser.py tests/test_phone_fit_control.py \
tests/test_integrated_selected_focus_browser.py
tests/test_integrated_selected_focus_browser.py \
tests/test_integrated_phone_back_browser.py tests/test_integrated_reader_first_browser.py

build-explorer:
npm ci --ignore-scripts --no-audit --no-fund --prefer-offline
Expand Down
21 changes: 12 additions & 9 deletions autoform_cli/assets/blueprint-dag.js
Original file line number Diff line number Diff line change
Expand Up @@ -1306,7 +1306,7 @@
ui$spotlight = null, ui$mapAllowed = mapAllowed, ui$readableCrop = false, ui$mathJob = null,
ui$selectedStatuses = new Set(presentStatuses), ui$showIsolated = true,
ui$transform = {x: 20, y: 20, scale: 1}, ui$reading = Boolean(firstBundle && firstBundle.readingSource),
ui$area = null, ui$overviewGroup = null, ui$scopeOverview = false, ui$overviewOnly = false, ui$filtersTouched = false, ui$relationIds = null,
ui$area = null, ui$overviewGroup = null, ui$scopeOverview = false, ui$coarseOverview = false, ui$overviewOnly = false, ui$filtersTouched = false, ui$relationIds = null,
ui$hovered = null,
ui$requestedNode = firstBundle && firstBundle.nodeId || null, ui$outlineLimit = 120;
if (!mapAllowed) ui$mode = "list";
Expand Down Expand Up @@ -1537,7 +1537,7 @@
var mapRequested = Boolean(bundle.mapFirst && (ui$scopeOverview || ui$overviewOnly || mapAllowed && topicCards) && !bundle.anchor && !(record && record.result));
var readingRequested = Boolean(bundle.focus && !mapRequested || bundle.anchor || record && record.result);
setSheet(bundle.snapshot ? bundle.snapshot.readingOpen !== false && 1 : sheetLayout() && (topicOverview || mapRequested) ? false : readingRequested ||
!ui$scopeOverview && !ui$overviewOnly && (!sheetLayout() || Boolean(bundle.nodeId)), ui$selected);
!ui$overviewOnly && (sheetLayout() ? !ui$scopeOverview && Boolean(bundle.nodeId) : !ui$coarseOverview), ui$selected);
refresh(true); firstResize = true; resize();
if (navigation) navigation.loadIndex().catch(showError);
readingReady = readingReady.catch(function () {}).then(function () { return new Promise(function (resolve) { requestAnimationFrame(function () {
Expand Down Expand Up @@ -1667,7 +1667,9 @@
var scopeOverview = projected._scopeOverview;
if (scopeOverview && scopeOverview.groups.some(function (group) { return group.key === ui$overviewGroup; })) scopeOverview.selectedGroup = ui$overviewGroup;
ui$scopeOverview = Boolean(scopeOverview); ui$overviewGroup = scopeOverview && scopeOverview.selectedGroup || null;
host.classList.toggle("bp-dag-scope-overview", ui$scopeOverview);
// Only authored-area overviews and oversized coarsening open map-first; shelves and context cards keep the reader.
ui$coarseOverview = Boolean(overview && (!overview._scopeOverview.$structural || overview._scopeOverview.originalNodes > MAX_MAP_NODES));
host.classList.toggle("bp-dag-scope-overview", ui$coarseOverview);
host.classList.toggle("bp-dag-area-expanded", Boolean(navigation && ui$area));
adoptGraph(projected); ui$mapAllowed = mapAllowed; ui$activeDirty = true; ui$spotlight = ui$hovered = null;
overlays.forEach(function (control) { control.remove(); }); overlays.clear();
Expand Down Expand Up @@ -2583,7 +2585,7 @@
x >= box.x && x <= box.x + box.w && y >= box.y && y <= box.y + box.h; });
if (group) { activateNode(group); return; }
}
next = Math.max(.9, next);
if (ui$coarseOverview) next = Math.max(.9, next);
}
ui$transform.x = x - (x - ui$transform.x) * next / old;
ui$transform.y = y - (y - ui$transform.y) * next / old; ui$transform.scale = next; schedule(); remember();
Expand Down Expand Up @@ -3116,7 +3118,7 @@
if (navigation && updateHistory) navigation.checkpoint();
var refit = ui$direction !== "both", cleared = ui$selected;
ui$selected = null; ui$direction = "both"; ui$activeDirty = true; ui$spotlight = null;
setSheet(navigation && !sheetLayout() && !ui$overviewOnly && !ui$scopeOverview, null);
setSheet(navigation && !sheetLayout() && !ui$overviewOnly && !ui$coarseOverview, null);
syncNavigationSelection(); renderInspector(); renderInventory(false); if (refit) requestAnimationFrame(fit); else schedule();
if (updateHistory) writeLocation("push");
var chosen = returnFocus && cleared && (ui$mode === "list" ? dom$inventoryRows.get(cleared.id) : overlays.get(cleared.id));
Expand All @@ -3137,7 +3139,8 @@
syncSheetAccessibility();
positionMapFeedback();
if (width !== dom$stage.clientWidth || height !== dom$stage.clientHeight) resize();
if (closed && selected && !restoring && sheetLayout() && mapAllowed) centerIfHidden(selected);
// A route change can arrive here before refresh(); frame the current projection's cards.
if (closed && selected && !restoring && sheetLayout() && mapAllowed) { calculateActive(); centerIfHidden(selected); }
}
function sheetLayout() { return Boolean(window.matchMedia && window.matchMedia("(max-width: 720px)").matches); }
function wrapMapKey(open, returnFocus) {
Expand All @@ -3157,10 +3160,10 @@
function syncSheetAccessibility() {
if (!dom$inspectorInner || !dom$sheetHandle) return;
var opened = host.classList.contains("bp-dag-sheet-open");
var collapsed = !opened && (sheetLayout() || Boolean(ui$area) || ui$overviewOnly || ui$scopeOverview);
var collapsed = !opened && (sheetLayout() || Boolean(ui$area) || ui$overviewOnly || ui$coarseOverview);
// The handle names the page or entry, with its status, rather than a generic "Reading"; its accessible name starts with that title.
// An open desktop reader over an area or the overview closes with ×.
var page = currentBundle || data, close = opened && !sheetLayout() && (ui$area || ui$overviewOnly || ui$scopeOverview),
var page = currentBundle || data, close = opened && !sheetLayout() && (ui$area || ui$overviewOnly || ui$coarseOverview),
record = records.get(page.nodeId || ui$requestedNode || (ui$selected && ui$selected.id));
if (!record || !record.result) record = page.readingSource && records.get(page.readingSource.dataset.autoformNodeId);
var title = record ? record.title : page.title;
Expand Down Expand Up @@ -3204,7 +3207,7 @@
dom$statusInputs.forEach(function (input, key) { if (input.checked) ui$selectedStatuses.add(key); });
if (ui$selected && !ui$selectedStatuses.has(ui$selected.status)) {
ui$selected = null; ui$direction = "both"; ui$spotlight = null;
setSheet(navigation && !sheetLayout() && !ui$overviewOnly && !ui$scopeOverview, null);
setSheet(navigation && !sheetLayout() && !ui$overviewOnly && !ui$coarseOverview, null);
dom$announcement.textContent = "Selection cleared because its status is hidden.";
}
ui$activeDirty = true; writeLocation("replace"); refresh(true); announceShown();
Expand Down
4 changes: 2 additions & 2 deletions autoform_cli/assets/blueprint-explorer.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion autoform_cli/dag_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ def viewer_script() -> str:
raise ValueError("Explorer bundle is missing or corrupt; run npm run build:explorer before publication.") from error
if not contents.startswith(f"/* autoform-explorer-source-sha256:{fingerprint} */\n"):
raise ValueError("Explorer bundle is stale; run npm run build:explorer before publication.")
if len(contents.encode("utf-8")) >= 144_000:
if len(contents.encode("utf-8")) >= 145_000:
raise ValueError("Explorer bundle exceeds the JavaScript budget; run npm run build:explorer before publication.")
return contents

Expand Down
2 changes: 1 addition & 1 deletion docs/explorer-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Page-owned reading labels add no MathJax menus/tab stops; typed link counts desc

[build-explorer.mjs](../scripts/build-explorer.mjs) hashes canonical UTF-8/LF development text.
That fingerprint differs from raw math sources, exact index/sidecar bytes and the runtime byte SHA.
Pinned development-only compilers ship one asset below 144 KB; consumers need no Node.
Pinned development-only compilers ship one asset below 145 KB; consumers need no Node.
Unsafe compression, public-field mangling/raw-source fallbacks are forbidden; stale bundles fail publication.

Source/unit and geometry tests check lifecycle, ranks, conservation/collisions; MathJax/WebKit check glyphs, hits/focus and Back.
Expand Down
4 changes: 2 additions & 2 deletions scripts/build-explorer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ if (args.length > 1 || (args.length && args[0] !== "--check")) {
if (typeof compressed.code !== "string") throw new Error("The explorer compressor produced no classic script");
const banner = `/* autoform-explorer-source-sha256:${digest} */\n`;
const output = Buffer.from(banner + compressed.code + "\n", "utf8");
if (output.byteLength >= 144_000) {
console.error(`Explorer bundle is ${output.byteLength} bytes; the integrated JavaScript limit is below 144,000 bytes. Source sha256 ${digest}`);
if (output.byteLength >= 145_000) {
console.error(`Explorer bundle is ${output.byteLength} bytes; the integrated JavaScript limit is below 145,000 bytes. Source sha256 ${digest}`);
process.exitCode = 1;
} else if (args[0] === "--check") {
let existing;
Expand Down
11 changes: 1 addition & 10 deletions tests/test_dag_viewer_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,13 +1008,7 @@ def test_app_layout_fills_the_browser_viewport(desktop_page: object, explorer_co
assert rail is not None and stage is not None and reader is not None
assert rail["x"] + rail["width"] <= stage["x"] + 1
assert stage["x"] + stage["width"] <= reader["x"] + 1
assert stage["width"] >= 550
handle = page.locator(".bp-dag-sheet-handle")
if handle.get_attribute("aria-expanded") == "false":
_assert_hit(page, handle)
handle.click()
stage, reader = page.locator(".bp-dag-stage").bounding_box(), page.locator(".bp-reading-pane").bounding_box()
assert stage is not None and reader is not None and stage["width"] >= 550 and reader["width"] >= 340
assert stage["width"] >= 550 and reader["width"] >= 340


@pytest.mark.parametrize("page_name", ("chain", "star"))
Expand Down Expand Up @@ -1102,9 +1096,6 @@ def test_atlas_hierarchy_keeps_topics_readable_and_details_out_of_the_way(
assert abs(float(end) - cursor) < .001
topic.click()
_consumer_ready(page, explorer_consumer, "atlas/topology")
if handle.get_attribute("aria-expanded") == "false":
_assert_hit(page, handle)
handle.click()
playwright.expect(handle).to_have_attribute("aria-expanded", "true")
playwright.expect(page.locator(".bp-reading-body")).to_contain_text("continuity, deformation, and invariants")
assert len(explorer_consumer.graphs["atlas"]["nodes"]) == 30
Expand Down
4 changes: 2 additions & 2 deletions tests/test_explorer_asset_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def test_real_compiler_rejects_invalid_utf8_before_writing_a_bundle(tmp_path: Pa
def test_explorer_bundle_keeps_the_integrated_client_budget_and_has_no_source_maps_or_local_paths() -> None:
bundled = _BUNDLE.read_bytes()
bundled.decode("utf-8")
assert len(bundled) < 144_000
assert len(bundled) < 145_000
assert b"sourceMappingURL=" not in bundled
assert str(_ROOT).encode("utf-8") not in bundled
assert b"/Users/" not in bundled and b"/home/runner/" not in bundled
Expand Down Expand Up @@ -185,7 +185,7 @@ def test_publication_rejects_oversized_bundle_even_with_current_source_fingerpri
assets.mkdir()
fingerprint = hashlib.sha256(dag_viewer._SCRIPT.encode("utf-8")).hexdigest()
(assets / _BUNDLE.name).write_text(
f"/* autoform-explorer-source-sha256:{fingerprint} */\n" + " " * 144_000, encoding="utf-8",
f"/* autoform-explorer-source-sha256:{fingerprint} */\n" + " " * 145_000, encoding="utf-8",
)
with pytest.raises(ValueError, match="Explorer bundle exceeds the JavaScript budget"):
dag_viewer.viewer_script()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_explorer_keyboard_focus.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
.replace("function schedule(){}", "function schedule(){scheduled++;}")
.replace("function resize(){}", "function resize(){resized++;}")
+ _CONSTANTS + r"""
var ui$selected=null,ui$scopeOverview=false,ui$overviewOnly=false,ui$direction='both';
var ui$selected=null,ui$scopeOverview=false,ui$coarseOverview=false,ui$overviewOnly=false,ui$direction='both';
var ui$active=[],ui$requestedNode=null,ui$outlineLimit=120,ui$transform={x:0,y:0,scale:1};
var ui$pointers=new Map(),ui$moved=false,ui$pinch=null;
var firstResize=false,atlas=false,alive=true,ui$phone=false,palette={planned:{label:'Planned'}},overlays=new Map();
Expand Down
4 changes: 2 additions & 2 deletions tests/test_graph_view_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ def test_the_phone_reader_peeks_opens_to_half_or_covers_the_map() -> None:
# Its accessible name starts with the title it shows; an open desktop reader over an area or the overview closes with ×.
assert 'refs.sheetHandle.setAttribute("aria-label", close ? "Close reading" : title + ", reading");' in script
assert "Toggle mathematical reading" not in script
assert "close = opened && !sheetLayout() && (state.area || state.overviewOnly || state.scopeOverview)" in script
assert "close = opened && !sheetLayout() && (state.area || state.overviewOnly || state.coarseOverview)" in script
assert "var page = currentBundle || data" in script
assert "record = page.readingSource && records.get(page.readingSource.dataset.autoformNodeId);" in script
assert "band = Math.max(NODE_H, Math.min(band, drawer.clientHeight * .45));" in script
Expand Down Expand Up @@ -1474,7 +1474,7 @@ def test_explorer_assets_and_ordinary_payload_stay_within_budgets(tmp_path: Path

# The reader, hierarchy rail and bounded layout share one prebuilt
# response; consumers still need no application build or runtime CDN.
assert len(dag_viewer.viewer_script().encode()) < 144_000
assert len(dag_viewer.viewer_script().encode()) < 145_000
assert output.stat().st_size < 300_000
assert search.stat().st_size < 100_000
search_payload = json.loads(search.read_text(encoding="utf-8"))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_integrated_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ def test_native_section_intent_cannot_revive_delayed_reader_focus_or_scroll_rest
};
// Supply the renderer's actual private lexical collaborators; executed source stays verbatim.
let ui$selected=null,ui$filtersTouched,ui$area,ui$requestedNode,ui$selectedStatuses,ui$showIsolated,
ui$mode,ui$listLimit,ui$relationIds,ui$direction,ui$query,ui$transform,ui$activeDirty,ui$reading,ui$overviewOnly,ui$overviewGroup,ui$scopeOverview,ui$outlineLimit;
ui$mode,ui$listLimit,ui$relationIds,ui$direction,ui$query,ui$transform,ui$activeDirty,ui$reading,ui$overviewOnly,ui$overviewGroup,ui$scopeOverview,ui$coarseOverview,ui$outlineLimit;
const dom$readingBody=refs.$readingBody,dom$search=refs.$search,dom$error=refs.$error,
dom$breadcrumb=refs.$breadcrumb,dom$navigation=refs.$navigation,dom$inventoryScroll=refs.$inventoryScroll;
let dom$outlineToggle;
Expand Down
Loading
Loading