From e4ddbb38af284ca995e84ba1a95696853211fcea Mon Sep 17 00:00:00 2001 From: opencode-agent Date: Wed, 5 Aug 2026 03:34:27 +0000 Subject: [PATCH] feat(scripts): add MOBILE_FIRST_MARKERS + _check_mobile_first check --- .opencode/scripts/project-status.py | 47 +++++++++++++++++++++++++++ .opencode/scripts/project_contract.py | 15 +++++++++ 2 files changed, 62 insertions(+) diff --git a/.opencode/scripts/project-status.py b/.opencode/scripts/project-status.py index 1a26365..c631419 100644 --- a/.opencode/scripts/project-status.py +++ b/.opencode/scripts/project-status.py @@ -673,6 +673,50 @@ def _check_frontend_stack(ctx: RepoCtx) -> CheckResult | None: return CheckResult(CheckStatus.OK, "frontend stack", "Tailwind + shadcn-svelte + TS detected") +def _check_mobile_first(ctx: RepoCtx) -> CheckResult | None: + """Fullstack mobile-first guarantee (issue #278): PWA manifest + mobile + Playwright + axe-core accessibility. + + Returns ``None`` if ``frontend/package.json`` does not exist (the missing + package.json WARN is surfaced separately by ``_check_type_specific_structure``). + Returns ``None`` for non-fullstack types — the caller only invokes this for + FULLSTACK. Otherwise returns OK when all markers are present, or WARN with + the missing marker list (never FAIL — issue #275 all-WARN contract). + + Markers (``MOBILE_FIRST_MARKERS``): + * ``frontend/static/manifest.webmanifest`` exists + * ``frontend/src/app.html`` contains both "viewport" and "manifest" + * ``frontend/tests/e2e/mobile.spec.ts`` exists + * ``frontend/tests/e2e/accessibility.spec.ts`` exists + * ``@axe-core/playwright`` in ``frontend/package.json`` devDependencies + """ + pkg_path = ctx.root / "frontend" / "package.json" + if not pkg_path.exists(): + return None + missing: list[str] = [] + for rel in project_contract.MOBILE_FIRST_MARKERS["fullstack_files"]: + if not (ctx.root / rel).exists(): + missing.append(rel) + app_html = read_text("frontend/src/app.html", ctx) or "" + for marker in project_contract.MOBILE_FIRST_MARKERS["fullstack_app_html_markers"]: + if marker not in app_html: + missing.append(f"frontend/src/app.html ({marker})") + try: + pkg = json.loads(pkg_path.read_text(encoding="utf-8-sig")) + except (json.JSONDecodeError, OSError): + pkg = {} + dev_deps = pkg.get("devDependencies", {}) if isinstance(pkg, dict) else {} + if "@axe-core/playwright" not in dev_deps: + missing.append("@axe-core/playwright in package.json devDependencies") + if missing: + return CheckResult( + CheckStatus.WARN, + "mobile-first", + "missing " + ", ".join(missing), + ) + return CheckResult(CheckStatus.OK, "mobile-first", "PWA + mobile Playwright + a11y detected") + + def _check_type_specific_structure(ptype: ProjectType, ctx: RepoCtx) -> list[CheckResult]: """Type-specific extra checks beyond the expected dirs list.""" results: list[CheckResult] = [] @@ -688,6 +732,9 @@ def _check_type_specific_structure(ptype: ProjectType, ctx: RepoCtx) -> list[Che frontend_stack = _check_frontend_stack(ctx) if frontend_stack is not None: results.append(frontend_stack) + mobile = _check_mobile_first(ctx) + if mobile is not None: + results.append(mobile) if ptype == ProjectType.CLI: results.append(_check_cli_package(ctx)) flat = _check_flat_layout(ptype, ctx) diff --git a/.opencode/scripts/project_contract.py b/.opencode/scripts/project_contract.py index c4a3c0b..7de965f 100644 --- a/.opencode/scripts/project_contract.py +++ b/.opencode/scripts/project_contract.py @@ -78,3 +78,18 @@ FRONTEND_STACK_MARKERS: dict[str, list[str]] = { "fullstack_package_deps": ["tailwindcss", "bits-ui"], "fullstack_files": ["frontend/components.json", "frontend/tsconfig.json"], } + +# Fullstack mobile-first markers (project-status _check_mobile_first, issue #278). +# Distinct from FRONTEND_STACK_MARKERS so the stack check stays focused on the +# Tailwind/shadcn/TS trio. Keys: +# fullstack_files — paths (relative to repo root) that must exist +# for PWA + mobile Playwright + a11y to be present. +# fullstack_app_html_markers — substrings that must appear in app.html . +MOBILE_FIRST_MARKERS: dict[str, list[str]] = { + "fullstack_files": [ + "frontend/static/manifest.webmanifest", + "frontend/tests/e2e/mobile.spec.ts", + "frontend/tests/e2e/accessibility.spec.ts", + ], + "fullstack_app_html_markers": ["viewport", "manifest"], +}