From bb7d041ea5100c82974e7f0c66cbc8dc8b11e8dd Mon Sep 17 00:00:00 2001 From: James Manuel Date: Wed, 1 Jul 2026 20:45:16 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=A7=20chore:=20bump=20web-apps=20t?= =?UTF-8?q?o=20post-#158=20HEAD=20(04b95b1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picks up Euro-Office/web-apps#158, which removes SKIP_MOBILE from build-pipeline.js — mobile now always builds and is always gated. Required before dropping the dead SKIP_MOBILE pass-through from the Makefile. Refs #261 Co-Authored-By: Claude Sonnet 5 Signed-off-by: James Manuel --- web-apps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-apps b/web-apps index be0c7c0eae..04b95b159e 160000 --- a/web-apps +++ b/web-apps @@ -1 +1 @@ -Subproject commit be0c7c0eae6e5ec90894b4d41019070e6a9a7af2 +Subproject commit 04b95b159e34e117dd4e630b6b733119115d836d From eb48f2765e264ae4742e8d37f291e7096699d778 Mon Sep 17 00:00:00 2001 From: James Manuel Date: Wed, 1 Jul 2026 20:45:16 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=A7=20build(makefile):=20drop=20de?= =?UTF-8?q?ad=20SKIP=5FMOBILE=20pass-through?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since web-apps#158 the build pipeline ignores SKIP_MOBILE, so the Makefile flag is a silent no-op. Removes the comment block, default, and both web-apps/web-apps-dev pass-throughs. Refs #261 Co-Authored-By: Claude Sonnet 5 Signed-off-by: James Manuel --- develop/setup/Makefile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/develop/setup/Makefile b/develop/setup/Makefile index 45d710f0a7..6928108bc5 100644 --- a/develop/setup/Makefile +++ b/develop/setup/Makefile @@ -18,11 +18,6 @@ WEBAPPS_FLAGS := # PRODUCT_VERSION=9.4.0 make web-apps-dev PRODUCT_VERSION ?= $(shell cat $(EO_DEV)/VERSION) -# Pass SKIP_MOBILE=1 to skip framework7-react mobile builds (~50s saved). -# Useful when iterating on desktop editor JS/CSS only. -# SKIP_MOBILE=1 make web-apps-dev -SKIP_MOBILE ?= 0 - CHECK_WEBAPPS_DEPS = @test -d $(EO_DEV)/web-apps/build/node_modules || { echo "Run 'make web-apps' first to install dependencies"; exit 1; } # Dev-friendly cache flush: regenerate nginx cache tag and reload, without @@ -62,7 +57,6 @@ web-apps: cd $(EO_DEV)/web-apps/build && \ npm ci && \ BUILD_ROOT=$(EO_ROOT) PRODUCT_VERSION=$(PRODUCT_VERSION) THEME=euro-office \ - SKIP_MOBILE=$(SKIP_MOBILE) \ node scripts/build-pipeline.js $(FLUSH_CACHE) @@ -72,7 +66,6 @@ web-apps-dev: python3 merge_and_check.py && \ cd $(EO_DEV)/web-apps/build && \ BUILD_ROOT=$(EO_ROOT) PRODUCT_VERSION=$(PRODUCT_VERSION) THEME=euro-office \ - SKIP_MOBILE=$(SKIP_MOBILE) \ node scripts/build-pipeline.js $(FLUSH_CACHE) From de01619add613ab10c5bb93930e0fe6e1f288bbc Mon Sep 17 00:00:00 2001 From: James Manuel Date: Wed, 1 Jul 2026 20:45:16 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20test(e2e):=20add=20mobile=20edi?= =?UTF-8?q?tor=20smoke=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify-deploy.mjs only checks that mobile artifacts exist at build time; it can't catch a mobile editor that deploys but fails at runtime (#258 — a permanent loading skeleton, or a fatal version-mismatch dialog blocking the document load). Forces the mobile bundle deterministically via ?type=mobile (bypassing the example app's User-Agent sniffing), then asserts the rendering canvas mounts and that no framework7 error surface is showing. Both dialog (.dialog.modal-in) and notification (.notification.modal-in) surfaces are checked, since LoadingScriptError uses f7.notification rather than f7.dialog. Covers docx/xlsx/pptx/pdf. Visio is excluded: the example app has no blank vsdx create-new template to exercise. Refs #261 Co-Authored-By: Claude Sonnet 5 Signed-off-by: James Manuel --- e2e/tests/mobile-editor.spec.ts | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 e2e/tests/mobile-editor.spec.ts diff --git a/e2e/tests/mobile-editor.spec.ts b/e2e/tests/mobile-editor.spec.ts new file mode 100644 index 0000000000..c71928eef5 --- /dev/null +++ b/e2e/tests/mobile-editor.spec.ts @@ -0,0 +1,67 @@ +import { test, expect } from '@playwright/test'; + +// Each mobile editor names its main rendering canvas differently: the +// document/presentation/pdf engines share `#id_viewer`, but the spreadsheet +// engine uses its own `#ws-canvas`. +// +// Visio (vsdx) is excluded: the example app's create-new flow has no blank +// vsdx template (`fileExt=vsdx` 500s — "new.vsdx was not included into +// executable at compilation stage"), so there's no way to exercise it via +// this create-new-document pattern. web-apps' build still gates a visio +// mobile bundle (verify-deploy.mjs), but that's a build-time artifact check, +// not something this runtime smoke test can reach without a pre-existing +// sample file. +const MOBILE_EDITOR_TYPES = [ + { label: 'Document', fileExt: 'docx', canvasSelector: '#id_viewer' }, + { label: 'Spreadsheet', fileExt: 'xlsx', canvasSelector: '#ws-canvas' }, + { label: 'Presentation', fileExt: 'pptx', canvasSelector: '#id_viewer' }, + { label: 'PDF form', fileExt: 'pdf', canvasSelector: '#id_viewer' }, +] as const; + +// framework7 error surfaces use two distinct components depending on error +// type: most fatal errors (Unknown, version mismatch, conversion failures) +// go through `f7.dialog.create` (`.dialog.modal-in`), but LoadingScriptError +// specifically uses `f7.notification.create` (`.notification.modal-in`) — +// see apps/documenteditor/mobile/src/controller/Error.jsx. Check both. +// +// This selector also matches the loading preloader (`f7.dialog.preloader()` +// renders as `.dialog.dialog-preloader`, `.modal-in` while open) — that's +// intentional, not a false positive: it means a permanent loading skeleton +// (#258's actual failure mode) is caught too, since the preloader never +// closes. It does mean this assertion is also the "document actually +// finished loading" gate, not just an error check — give it the same +// timeout as the canvas-visible check below, not the default. +const ERROR_SURFACE_SELECTOR = '.dialog.modal-in, .notification.modal-in'; + +test.describe('Mobile editor smoke', () => { + test.use({ viewport: { width: 414, height: 896 } }); + + for (const editor of MOBILE_EDITOR_TYPES) { + test(`${editor.label} mobile editor mounts past the loading skeleton`, async ({ page }) => { + // ?type=mobile forces the mobile bundle deterministically. The example + // app only falls back to User-Agent sniffing when ?type= is absent + // (nodejs/app.js) — the emulated viewport above is for CSS realism, + // unrelated to which bundle gets selected. + await page.goto(`/example/editor?fileExt=${editor.fileExt}&type=mobile`); + + const editorIframe = page.locator('iframe[name="frameEditor"]'); + await expect(editorIframe).toBeAttached({ timeout: 15_000 }); + await expect(editorIframe).toHaveAttribute('src', /type=mobile/); + + // #258's actual failure mode: mobile artifacts deploy correctly, but the + // app never gets past the loading skeleton (or hits a fatal error, e.g. + // version mismatch) once it talks to a live docservice — a build-time + // check like verify-deploy.mjs can't catch that. + // + // The rendering canvas alone isn't a sufficient signal: it's part of the + // static UI scaffold and stays present (and "visible") even when the + // document itself fails to load — verified by reproducing a fatal load + // error, which left the canvas visible but surfaced a framework7 error + // surface on top of it. So this asserts both: the canvas mounted AND no + // error dialog/notification is showing. + const frame = page.frameLocator('iframe[name="frameEditor"]'); + await expect(frame.locator(editor.canvasSelector)).toBeVisible({ timeout: 30_000 }); + await expect(frame.locator(ERROR_SURFACE_SELECTOR)).toHaveCount(0, { timeout: 30_000 }); + }); + } +}); From 4e3a267330840b95437594801cf4eeb26dddc555 Mon Sep 17 00:00:00 2001 From: James Manuel Date: Thu, 2 Jul 2026 13:27:25 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=90=9B=20test(e2e):=20gate=20mobile?= =?UTF-8?q?=20smoke=20on=20loading=20skeleton,=20not=20canvas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The canvas mounts from the SDK bundle regardless of doc load, so the gate false-passed when the doc never loaded. Gate on .doc-placeholder clearing (the isDocReady signal) instead, with a generous timeout. Scope is build-present-and-renders; infra faults like an unresponsive docservice channel are out of scope. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: James Manuel --- e2e/tests/mobile-editor.spec.ts | 62 +++++++++++---------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/e2e/tests/mobile-editor.spec.ts b/e2e/tests/mobile-editor.spec.ts index c71928eef5..f0625ea501 100644 --- a/e2e/tests/mobile-editor.spec.ts +++ b/e2e/tests/mobile-editor.spec.ts @@ -1,16 +1,8 @@ import { test, expect } from '@playwright/test'; -// Each mobile editor names its main rendering canvas differently: the -// document/presentation/pdf engines share `#id_viewer`, but the spreadsheet -// engine uses its own `#ws-canvas`. -// -// Visio (vsdx) is excluded: the example app's create-new flow has no blank -// vsdx template (`fileExt=vsdx` 500s — "new.vsdx was not included into -// executable at compilation stage"), so there's no way to exercise it via -// this create-new-document pattern. web-apps' build still gates a visio -// mobile bundle (verify-deploy.mjs), but that's a build-time artifact check, -// not something this runtime smoke test can reach without a pre-existing -// sample file. +// Canvas id differs by engine: doc/presentation/pdf use #id_viewer, spreadsheet #ws-canvas. +// Visio excluded: create-new has no blank vsdx template (fileExt=vsdx 500s). The build-time +// verify-deploy.mjs check still gates the visio mobile bundle. const MOBILE_EDITOR_TYPES = [ { label: 'Document', fileExt: 'docx', canvasSelector: '#id_viewer' }, { label: 'Spreadsheet', fileExt: 'xlsx', canvasSelector: '#ws-canvas' }, @@ -18,50 +10,36 @@ const MOBILE_EDITOR_TYPES = [ { label: 'PDF form', fileExt: 'pdf', canvasSelector: '#id_viewer' }, ] as const; -// framework7 error surfaces use two distinct components depending on error -// type: most fatal errors (Unknown, version mismatch, conversion failures) -// go through `f7.dialog.create` (`.dialog.modal-in`), but LoadingScriptError -// specifically uses `f7.notification.create` (`.notification.modal-in`) — -// see apps/documenteditor/mobile/src/controller/Error.jsx. Check both. -// -// This selector also matches the loading preloader (`f7.dialog.preloader()` -// renders as `.dialog.dialog-preloader`, `.modal-in` while open) — that's -// intentional, not a false positive: it means a permanent loading skeleton -// (#258's actual failure mode) is caught too, since the preloader never -// closes. It does mean this assertion is also the "document actually -// finished loading" gate, not just an error check — give it the same -// timeout as the canvas-visible check below, not the default. +// Fatal errors surface as an f7 dialog or, for LoadingScriptError, a notification — check both. +// See apps/*/mobile/src/controller/Error.jsx. const ERROR_SURFACE_SELECTOR = '.dialog.modal-in, .notification.modal-in'; +// Remit: verify the mobile build is present and actually renders a document (the #258 class of +// failure). Infra faults such as an unresponsive docservice channel are out of scope — the app +// can go false-ready over a dead channel; see cm-findings/DocumentServer/pr-262-ws-hang-false-pass.md. test.describe('Mobile editor smoke', () => { test.use({ viewport: { width: 414, height: 896 } }); for (const editor of MOBILE_EDITOR_TYPES) { - test(`${editor.label} mobile editor mounts past the loading skeleton`, async ({ page }) => { - // ?type=mobile forces the mobile bundle deterministically. The example - // app only falls back to User-Agent sniffing when ?type= is absent - // (nodejs/app.js) — the emulated viewport above is for CSS realism, - // unrelated to which bundle gets selected. + test(`${editor.label} mobile editor loads`, async ({ page }) => { + // ?type=mobile forces the mobile bundle (bypasses UA sniffing in nodejs/app.js). await page.goto(`/example/editor?fileExt=${editor.fileExt}&type=mobile`); const editorIframe = page.locator('iframe[name="frameEditor"]'); await expect(editorIframe).toBeAttached({ timeout: 15_000 }); await expect(editorIframe).toHaveAttribute('src', /type=mobile/); - // #258's actual failure mode: mobile artifacts deploy correctly, but the - // app never gets past the loading skeleton (or hits a fatal error, e.g. - // version mismatch) once it talks to a live docservice — a build-time - // check like verify-deploy.mjs can't catch that. - // - // The rendering canvas alone isn't a sufficient signal: it's part of the - // static UI scaffold and stays present (and "visible") even when the - // document itself fails to load — verified by reproducing a fatal load - // error, which left the canvas visible but surfaced a framework7 error - // surface on top of it. So this asserts both: the canvas mounted AND no - // error dialog/notification is showing. + // The real "document loaded" signal is the loading skeleton clearing, not the canvas: + // .doc-placeholder (uniform across editors) shows while !isDocReady and is removed when + // the doc loads — the mobile analog of the desktop suite's #loading-mask. #id_viewer alone + // mounts with the SDK bundle before any document loads, so it can't tell "loaded" apart + // from "never loaded". Assert the skeleton appears, then clears, then the shell is clean. const frame = page.frameLocator('iframe[name="frameEditor"]'); - await expect(frame.locator(editor.canvasSelector)).toBeVisible({ timeout: 30_000 }); - await expect(frame.locator(ERROR_SURFACE_SELECTOR)).toHaveCount(0, { timeout: 30_000 }); + const placeholder = frame.locator('.doc-placeholder'); + await expect(placeholder.first()).toBeVisible({ timeout: 15_000 }); + await expect(placeholder).toHaveCount(0, { timeout: 30_000 }); + await expect(frame.locator(ERROR_SURFACE_SELECTOR)).toHaveCount(0, { timeout: 5_000 }); + await expect(frame.locator(editor.canvasSelector)).toBeVisible({ timeout: 5_000 }); }); } });