Skip to content

feat(frontend): M1 frontend foundation (Vue 3 + Inertia 3 + TypeScript, Node 24) - #10

Merged
AdilAzhariOmsan merged 5 commits into
mainfrom
feat/frontend-shell
Nov 6, 2026
Merged

AdilAzhariOmsan merged 5 commits into
mainfrom
feat/frontend-shell

Conversation

@AdilAzhariOmsan

Copy link
Copy Markdown
Collaborator

M1 frontend foundation: Vue 3 + Inertia 3 + strict TypeScript on Node 24. / is now served through Inertia, with a separate frontend CI job. No product UI yet; M2 styles the page.

Merge condition: this PR merges only when both PHP 8.5 / Laravel 13 and Node 24 / Vue 3 are green on the final head commit. GitHub does not require the second check until follow-up F-1 is done.

Summary

Five logical commits on main (0934dbe):

  1. build(frontend): Node 24 / Vue 3 / Inertia 3 toolchain, lockfile generated on Node 24.22.2 / npm 11.19.1
  2. build(frontend): strict TypeScript projects (app + node)
  3. feat(storefront): Inertia shell serving Storefront/Home (tests first)
  4. ci(frontend): Vitest smoke test and the Node 24 / Vue 3 job
  5. docs(engineering): frontend local check and npm auditing

Related Issues

N/A

Requirement

M1 (frontend foundation), per the Phase 1/2 specification and ADR-0020.

Type of Change

  • Feature
  • Infrastructure
  • Tests
  • Documentation

What Changed

  • Toolchain:
    • .nvmrc = 24; engines.node = ^24.15.0; .npmrc adds engine-strict=true.
    • Adds inertiajs/inertia-laravel ^3.0 (v3.7.0).
    • Runtime: vue ^3.5, @inertiajs/vue3 ^3.0. Dev: @vitejs/plugin-vue, typescript ~6.0.3, vue-tsc, @vue/tsconfig, @types/node 24, vitest 5, @vue/test-utils 2, jsdom 29.
  • TypeScript:
    • Uses the create-vue layout: tsconfig.json holds only references, to tsconfig.app.json (DOM) and tsconfig.node.json (Vite/Vitest config). No composite.
    • Flags: strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, verbatimModuleSyntax. @/* is mapped without baseUrl.
  • Inertia shell:
    • The HandleInertiaRequests middleware shares only locale and direction (rtl for ar).
    • app.blade.php sets <html lang dir> from those props.
    • app.ts resolves pages via import.meta.glob, without the Inertia Vite plugin.
    • Adds Storefront/Home.vue as a placeholder. types/shared.ts is temporary, until ADR-0018.
    • Removes welcome.blade.php.
  • CI:
    • The PHP 8.5 / Laravel 13 job (required, name unchanged) now sets up Node from .nvmrc and runs npm ci + npm run build before Pest. That is the only frontend build in CI.
    • The new, independent Node 24 / Vue 3 job runs npm ci → typecheck → test → npm audit --omit=dev, with the audit last.
  • Docs:
    • security.md: npm audit policy, plus the dev-only advisories recorded.
    • definition-of-done.md: the local check order.

Design / Architecture

  • config/inertia.php is not published (T3). v3.7.0 already defaults pages.paths to resources/js/pages and testing.ensure_pages_exist to true, so assertInertia()->component() fails if the page file is missing. A published copy would drift from later releases.
  • TS project setup (T2): two projects, following create-vue 3.24.0 without its extra @tsconfig/node24 / @types/jsdom packages. vue-tsc --build --force compiles cleanly.
  • SSR: no SSR bundle is built. With ssr.ensure_bundle_exists at its default true, the gateway skips SSR, so tests and CI never contact an SSR server.
  • exactOptionalPropertyTypes (T4): no library conflicts were hit. The flag stays on.

Testing

  • Unit tests (Vitest smoke)
  • Feature/integration tests
  • Architecture tests (existing suite)
  • Manual verification

Test Evidence

Local, Node 24.22.2 / npm 11.19.1, PHP 8.5, with APP_CONFIG_CACHE pointed at a path that doesn't exist, from a clean node_modules / public/build:

Step Result
npm ci ok
npm run build ok
composer check composer.json valid · 0 advisories · Pint passed · PHPStan 0 errors · Pest 8 passed (31 assertions)
npm run typecheck ok
npm test 1 passed
npm audit --omit=dev 0 vulnerabilities
AC Evidence
AC1 Pest assertInertia: component Storefront/Home, locale=en, direction=ltr
AC2 Pest: <html lang="en" dir="ltr">; with locale ar: direction=rtl, <html lang="ar" dir="rtl"> (locale set directly until M3 adds routing)
AC3 /up test, arch tests, Pint and PHPStan (level 6, unchanged) pass
AC4 Strict flags on. A deliberate const n: number = 'x' fails vue-tsc with TS2322, exit 2; clean again after removal
AC5 Vitest on jsdom 29: Storefront/Home mounts and renders its heading
AC6a CI logs node --version in the Node 24 / Vue 3 job (Node from .nvmrc)
AC6b npm ci against this lockfile on Node 22.22.0 / npm 10.9.4: EBADENGINE, Required: {"node":"^24.15.0"}, exit 1, nothing installed
AC7 Resolved: typescript 6.0.3 · @inertiajs/vue3 3.11.0 · inertiajs/inertia-laravel v3.7.0 · vite 8.3.6 · laravel-vite-plugin 3.2.1 · vitest 5.1.0 · @vue/test-utils 2.5.1 · jsdom 29.1.1 · vue 3.5.44
AC8 npm audit --omit=dev: 0, blocking in CI. Dev-only advisories recorded in security.md
AC9 The build runs only in the PHP job, before php artisan test
AC10 See this PR's checks. The required check name is unchanged
AC11 Storefront entry app-*.js: 184.80 kB, 62.31 kB gzip; Home-*.js: 0.30 kB gzip; CSS 10.03 kB gzip
AC12 Absent: auth, vendor context, Filament, app/Modules, spatie/laravel-data, migrations, Boost, @inertiajs/vite, ESLint. laravel/boost appears in composer.lock only as inertia-laravel's conflict: <2.5.0 constraint; it is not installed

Security / Data Isolation

  • No cross-vendor data leakage introduced (no vendor or auth code)
  • Sensitive data handling reviewed (shared props are locale and direction only)

Dev-only npm advisories (moderate, test-time only, never shipped), recorded in security.md with a 2026-11-05 reassessment date:

Database / Migration

  • No database changes

Screenshots / Recording

N/A. The page is a neutral placeholder; M2 styles it.

Documentation

  • Documentation updated (docs/engineering/security.md, docs/engineering/definition-of-done.md)

Reviewer Notes

  • Focus:
    • .github/workflows/ci.yml: build-once in the required job; the frontend job has no needs:.
    • HandleInertiaRequests::share().
    • The tsconfig trio.
  • Known items:
    • The build prints a fontaine warning from the existing bunny-fonts optimizedFallbacks setting. It predates M1 and is left unchanged.
    • The repo's CLAUDE.md asks agents to install Laravel Boost. That was deliberately not done (F-5).
  • Follow-ups:
    • F-1: add Node 24 / Vue 3 to the main ruleset's required checks after this merges (it must have run once on main). Until then, never merge with it red.
    • F-2: ESLint 10 + eslint-plugin-vue + typescript-eslint.
    • F-3: replace types/shared.ts with generated types once ADR-0018 is decided.
    • F-4: a second Vite entry for the vendor dashboard (M5).
    • F-5: Laravel Boost as its own PR, if authorised.

Definition of Done

  • Acceptance criteria are satisfied
  • composer check passes locally (validate, audit, Pint, PHPStan, Pest)
  • No unrelated changes
  • No secrets committed
  • Self-review completed
  • Reviewer feedback addressed

- .nvmrc pins Node 24; package.json engines ^24.15.0 and
  .npmrc engine-strict=true make npm ci reject other Node majors.
- inertiajs/inertia-laravel ^3.0 (resolved v3.7.0).
- vue ^3.5, @inertiajs/vue3 ^3.0; dev: @vitejs/plugin-vue, typescript
  ~6.0.3, vue-tsc, @vue/tsconfig, @types/node 24, vitest 5,
  @vue/test-utils 2, jsdom 29.
- package-lock.json generated with Node 24.22.2 / npm 11.19.1.
create-vue layout without composite: tsconfig.json holds only
references; tsconfig.app.json (DOM, resources/js) and tsconfig.node.json
(Vite/Vitest config) extend @vue/tsconfig with strict,
noUncheckedIndexedAccess, exactOptionalPropertyTypes,
noImplicitOverride and verbatimModuleSyntax. '@/*' maps to
resources/js without baseUrl.

vite.config.js becomes vite.config.ts (content unchanged) so the node
project has an input. Adds typecheck (vue-tsc --build --force) and
test (vitest run) scripts.
- HandleInertiaRequests (root view 'app') shares only locale and
  direction; direction is rtl for Arabic, ltr otherwise. Appended to
  the web group.
- app.blade.php sets <html lang dir> from the shared props and loads
  resources/js/app.ts through @Vite; welcome.blade.php is removed.
- app.ts boots createInertiaApp, resolving pages from
  import.meta.glob('./pages/**/*.vue'); no Inertia Vite plugin.
- Storefront/Home.vue is a neutral placeholder; M2 styles it.
- types/shared.ts types the shared props via InertiaConfig, temporary
  until ADR-0018.
- vite.config.ts adds the vue() plugin and the app.ts entry.

config/inertia.php is not published: v3.7.0 already defaults
pages.paths to resources/js/pages and testing.ensure_pages_exist to
true, so assertInertia()->component() verifies the page file exists.

Tests: Inertia component and props, <html lang dir> for en/ltr, and the
ar/rtl derivation with the locale set directly until M3 adds routing.
- vitest.config.ts is separate from the Vite config: vue() plugin and
  jsdom, without the Laravel plugin.
- Home.test.ts mounts Storefront/Home (Inertia <Head> mocked) and
  asserts the heading renders.
- The PHP 8.5 / Laravel 13 job (required check, name unchanged) sets up
  Node from .nvmrc and runs npm ci + npm run build before Pest, the
  only frontend build in CI.
- A new, independent Node 24 / Vue 3 job runs npm ci (engine-strict)
  -> typecheck -> test -> npm audit --omit=dev, with the audit last so
  an advisory cannot hide the other results.
- security.md: npm audit --omit=dev is blocking in CI; dev-only
  advisories (GHSA-3g3m-2fjr-6pw4, GHSA-g3hv-75wg-m249 via
  @vue/test-utils -> js-beautify) are recorded with chain, rationale
  and reassessment date, plus when to reassess.
- definition-of-done.md: local gate order npm ci -> npm run build ->
  composer check -> typecheck -> test -> npm audit --omit=dev.

@AdilAzhariOmsan AdilAzhariOmsan left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant