frontend-harness 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to `frontend-harness` are documented here.
4
4
 
5
5
  This project follows semver while it is pre-1.0: patch releases may include harness policy improvements that tighten validation, and minor releases may change generated protocol expectations.
6
6
 
7
+ ## 0.7.7 - 2026-05-26
8
+
9
+ ### Changed
10
+
11
+ - Reframed the Vue scaffold as a general Vue application baseline instead of an admin-oriented template, with admin shells, menus, permissions, and table-page workflows documented as optional recipes.
12
+ - Strengthened the default Vue scaffold with product-neutral utility, storage, HTTP client, reset, and utility-style layers inspired by mature Vue projects while avoiding Element Plus style variants.
13
+ - Added `.env.example` and typed `VITE_API_BASE_URL` support to the Vue scaffold, tightened generated dependency ranges, and verified generated projects through lint, typecheck, test, and build.
14
+
7
15
  ## 0.7.6 - 2026-05-25
8
16
 
9
17
  ### Changed
package/README.md CHANGED
@@ -136,7 +136,7 @@ npm run harness:clean
136
136
 
137
137
  Use `init` after installation or package upgrades. Use `protocol check` when you want to verify that `AGENTS.md` and `CLAUDE.md` are current. Use `clean` to remove ignored runtime artifacts while preserving durable project skills, knowledge, and config.
138
138
 
139
- `scaffold --preset vue` creates a Vue 3 + Vite + TypeScript engineering baseline inspired by the company `freight-admin` stack. It includes Vite config, ESLint/Prettier, Element Plus auto imports, and small `utils`, `storage`, and `service` layers. It intentionally does not create admin pages, routes, dashboards, permissions, or business components.
139
+ `scaffold --preset vue` creates a general Vue 3 + Vite + TypeScript engineering baseline. It includes Vite config, ESLint/Prettier, Element Plus auto imports, product-neutral style layers, and small `utils`, `storage`, and `service` layers. Mature internal Vue applications can inform the baseline, but the default scaffold is not an admin template and does not create dashboards, sidebars, permission systems, business pages, or domain components. Admin-style shells, table pages, route permissions, and menu trees belong in optional recipes rather than the default preset.
140
140
 
141
141
  UI restoration work treats design exports, screenshots, and generated code as source evidence for implementation and loop repair, not as paperwork to backfill after coding. For design-backed restoration, visual equivalence is the highest priority for the first project implementation: source material does not mean the agent may redesign the screen. When design_to_code or generated design markup is available, preserve it and classify whether it is usable_as_visual_baseline, usable_for_patch_reference, usable_as_source, or unusable before deciding whether it can seed implementation or only guide repair; verify enforces that classification through visual-baseline metadata for design_to_code sources, and temporary asset localization is required only when the generated artifact is used as project source. Project-native rewrites may change file placement, framework integration, semantic markup, asset localization, naming, state wiring, and component boundaries only when the rendered result preserves the design's primary layout, spacing, typography, color, density, hierarchy, and visible states. When a design contains images, classify each one as required content, functional UI element, brand/product asset, decorative sample, or replaceable placeholder before implementation. Required images should become durable assets; decorative samples and placeholders should be replaced or documented in design evidence. Final handoff evidence should include a restoration verdict with score or pass/fail judgement, key visual differences, accepted gaps, and links to evidence artifacts. Component splits should follow semantic responsibility, reuse, and interaction boundaries rather than mirroring every design layer as a component.
142
142
 
@@ -4,6 +4,7 @@ const VUE_TEMPLATE_ROOT = new URL("../../../templates/scaffold/vue/", import.met
4
4
  const VUE_TEMPLATE_FILES = [
5
5
  { source: "gitignore.tpl", target: ".gitignore" },
6
6
  { source: ".husky/pre-commit", target: ".husky/pre-commit", mode: 0o755 },
7
+ { source: "env.example.tpl", target: ".env.example" },
7
8
  { source: "eslint.config.mjs", target: "eslint.config.mjs" },
8
9
  { source: "index.html.tpl", target: "index.html" },
9
10
  { source: "postcss.config.mjs", target: "postcss.config.mjs" },
@@ -16,7 +17,9 @@ const VUE_TEMPLATE_FILES = [
16
17
  { source: "src/env.d.ts", target: "src/env.d.ts" },
17
18
  { source: "src/features/home/HomeView.vue", target: "src/features/home/HomeView.vue" },
18
19
  { source: "src/styles/main.scss", target: "src/styles/main.scss" },
20
+ { source: "src/styles/reset.scss", target: "src/styles/reset.scss" },
19
21
  { source: "src/styles/tokens.scss", target: "src/styles/tokens.scss" },
22
+ { source: "src/styles/utilities.scss", target: "src/styles/utilities.scss" },
20
23
  { source: "src/utils/index.ts", target: "src/utils/index.ts" },
21
24
  { source: "src/storage/index.ts", target: "src/storage/index.ts" },
22
25
  { source: "src/service/http.ts", target: "src/service/http.ts" },
@@ -90,18 +93,18 @@ function packageJson(projectName) {
90
93
  "eslint-plugin-vue": "^10.6.2",
91
94
  "frontend-harness": `^${currentHarnessVersion()}`,
92
95
  husky: "^9.1.7",
93
- jsdom: "^29.1.1",
96
+ jsdom: "~25.0.1",
94
97
  "lint-staged": "^15.5.2",
95
98
  postcss: "^8.4.47",
96
99
  prettier: "^3.3.3",
97
- sass: "^1.93.2",
100
+ sass: "~1.93.2",
98
101
  typescript: "^5.7.0",
99
102
  "unplugin-auto-import": "^19.0.0",
100
103
  "unplugin-vue-components": "^28.0.0",
101
104
  "vue-eslint-parser": "^10.2.0",
102
105
  "vue-tsc": "^3.0.7",
103
- vite: "^5.4.21",
104
- vitest: "^4.1.6"
106
+ vite: "~5.4.21",
107
+ vitest: "~2.1.9"
105
108
  },
106
109
  "lint-staged": {
107
110
  "src/**/*.{ts,vue}": [
@@ -1 +1 @@
1
- {"version":3,"file":"vue-template.js","sourceRoot":"","sources":["../../../src/runtime/scaffold/vue-template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vue-template.js","sourceRoot":"","sources":["../../../src/runtime/scaffold/vue-template.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-harness",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -1,9 +1,9 @@
1
1
  # {{projectName}}
2
2
 
3
- Vue 3 + Vite engineering scaffold.
3
+ Vue 3 + Vite application scaffold.
4
4
 
5
- This preset is based on the company Vue stack shape used in freight-admin, but it only includes engineering infrastructure.
6
- It intentionally avoids business pages, admin routes, dashboards, permissions, and domain components.
5
+ This preset provides a general Vue engineering baseline. It includes project infrastructure and small reusable layers, but it is not an admin-management template.
6
+ Use mature internal Vue applications as references for proven patterns, then pull admin shells, table pages, menus, route permissions, and business workflows in as project-specific recipes only when the application needs them.
7
7
 
8
8
  ## Scripts
9
9
 
@@ -20,17 +20,24 @@ It intentionally avoids business pages, admin routes, dashboards, permissions, a
20
20
  - `npm run harness:check`
21
21
  - `npm run harness:clean`
22
22
 
23
- ## Base layers
23
+ ## Base Layers
24
24
 
25
25
  - `src/utils` for shared helpers
26
26
  - `src/storage` for browser storage wrappers
27
- - `src/service` for API client setup
28
- - `src/styles` for project-wide styles
27
+ - `src/service` for configurable API client setup, token injection hooks, blob passthrough, and normalized errors
28
+ - `src/styles` for project-wide style layers
29
29
  - `src/styles/tokens.scss` for reusable UI restoration tokens
30
- - `src/features/<feature>` for page-specific types, fixtures, columns, calculations, and components
30
+ - `src/styles/reset.scss` for browser reset rules
31
+ - `src/styles/utilities.scss` for small, product-neutral utility classes
32
+ - `src/features/<feature>` for feature-local views, types, fixtures, calculations, and components
31
33
  - `tests/integration` for Vitest + Testing Library behavior tests
32
34
  - `tests/e2e` for Playwright browser smoke tests
33
35
 
36
+ ## Optional Recipes
37
+
38
+ Admin layouts, side navigation, menu trees, route permissions, searchable tables, and pagination shells are intentionally outside this default preset.
39
+ Add those as project recipes or feature templates when the product is actually an admin-style application.
40
+
34
41
  ## UI restoration workflow
35
42
 
36
43
  Use the injected `ui-implementation` project skill and `.frontend-harness/guidance/latest.md` for the current harness plan.
@@ -0,0 +1 @@
1
+ VITE_API_BASE_URL=/api
@@ -15,9 +15,11 @@ export default [
15
15
  languageOptions: {
16
16
  globals: {
17
17
  document: "readonly",
18
+ clearTimeout: "readonly",
18
19
  localStorage: "readonly",
19
20
  sessionStorage: "readonly",
20
21
  Storage: "readonly",
22
+ setTimeout: "readonly",
21
23
  window: "readonly"
22
24
  }
23
25
  }
@@ -30,6 +32,9 @@ export default [
30
32
  ecmaVersion: "latest",
31
33
  sourceType: "module"
32
34
  }
35
+ },
36
+ rules: {
37
+ "no-unused-vars": "off"
33
38
  }
34
39
  },
35
40
  {
@@ -1,7 +1,15 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
+ interface ImportMetaEnv {
4
+ readonly VITE_API_BASE_URL?: string;
5
+ }
6
+
7
+ interface ImportMeta {
8
+ readonly env: ImportMetaEnv;
9
+ }
10
+
3
11
  declare module "*.vue" {
4
12
  import type { DefineComponent } from "vue";
5
- const component: DefineComponent<{}, {}, any>;
13
+ const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>;
6
14
  export default component;
7
15
  }
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <main class="app-shell">
3
3
  <section class="hero">
4
- <p class="eyebrow">Vue scaffold</p>
5
- <h1>Engineering base is ready</h1>
4
+ <p class="eyebrow">Vue application</p>
5
+ <h1>Project foundation is ready</h1>
6
6
  <p class="lead">
7
- This scaffold carries the company Vue engineering baseline: Vite, TypeScript, Element Plus, auto imports, storage, utility, and service layers.
7
+ This scaffold gives the project a neutral Vue foundation: Vite, TypeScript, Element Plus, auto imports, storage, utility, and service layers.
8
8
  </p>
9
9
  </section>
10
10
  <section class="grid">
@@ -18,7 +18,7 @@
18
18
  </article>
19
19
  <article>
20
20
  <h2>service</h2>
21
- <p>API client setup and error normalization.</p>
21
+ <p>API client setup and error normalization without product-specific assumptions.</p>
22
22
  </article>
23
23
  </section>
24
24
  </main>
@@ -1,18 +1,82 @@
1
- import axios from "axios";
1
+ import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
2
2
 
3
- export const http = axios.create({
4
- baseURL: import.meta.env.VITE_API_BASE_URL || "/api",
5
- timeout: 15_000
6
- });
3
+ export interface HttpClientOptions {
4
+ baseURL?: string;
5
+ timeoutMs?: number;
6
+ getToken?: () => string | null | undefined;
7
+ onUnauthorized?: (error: Error) => void | Promise<void>;
8
+ isSuccess?: (payload: unknown, response: AxiosResponse) => boolean;
9
+ getMessage?: (payload: unknown, response?: AxiosResponse) => string | undefined;
10
+ }
11
+
12
+ export interface HttpError extends Error {
13
+ status?: number;
14
+ code?: string | number;
15
+ payload?: unknown;
16
+ }
17
+
18
+ export function createHttpClient(options: HttpClientOptions = {}) {
19
+ const client = axios.create({
20
+ baseURL: options.baseURL ?? import.meta.env.VITE_API_BASE_URL ?? "/api",
21
+ timeout: options.timeoutMs ?? 15_000,
22
+ headers: { "Content-Type": "application/json" },
23
+ paramsSerializer: {
24
+ indexes: null
25
+ }
26
+ });
7
27
 
8
- http.interceptors.response.use(
9
- (response) => response,
10
- (error) => Promise.reject(normalizeHttpError(error))
11
- );
28
+ client.interceptors.request.use((config: InternalAxiosRequestConfig) => {
29
+ const token = options.getToken?.();
30
+ if (token) {
31
+ config.headers.Authorization = `Bearer ${token}`;
32
+ }
33
+ return config;
34
+ });
35
+
36
+ client.interceptors.response.use(
37
+ async (response) => {
38
+ if (response.config.responseType === "blob") return response;
39
+ if (options.isSuccess && !options.isSuccess(response.data, response)) {
40
+ const error = createHttpError(options.getMessage?.(response.data, response) ?? "Request failed", response, response.data);
41
+ if (error.status === 401) await options.onUnauthorized?.(error);
42
+ throw error;
43
+ }
44
+ return response;
45
+ },
46
+ async (error) => {
47
+ const normalized = normalizeHttpError(error, options);
48
+ if (normalized.status === 401) await options.onUnauthorized?.(normalized);
49
+ return Promise.reject(normalized);
50
+ }
51
+ );
52
+
53
+ return client;
54
+ }
12
55
 
13
- export function normalizeHttpError(error: unknown): Error {
56
+ export const http = createHttpClient();
57
+
58
+ export function normalizeHttpError(error: unknown, options: Pick<HttpClientOptions, "getMessage"> = {}): HttpError {
14
59
  if (axios.isAxiosError(error)) {
15
- return new Error(error.response?.data?.message || error.message || "Request failed");
60
+ return createHttpError(
61
+ options.getMessage?.(error.response?.data, error.response) ?? error.response?.data?.message ?? error.message ?? "Request failed",
62
+ error.response,
63
+ error.response?.data
64
+ );
16
65
  }
17
66
  return error instanceof Error ? error : new Error(String(error));
18
67
  }
68
+
69
+ function createHttpError(message: string, response?: AxiosResponse, payload?: unknown): HttpError {
70
+ const error = new Error(message) as HttpError;
71
+ error.status = response?.status;
72
+ error.payload = payload;
73
+ if (isRecord(payload)) {
74
+ const code = payload.code;
75
+ if (typeof code === "string" || typeof code === "number") error.code = code;
76
+ }
77
+ return error;
78
+ }
79
+
80
+ function isRecord(value: unknown): value is Record<string, unknown> {
81
+ return typeof value === "object" && value !== null;
82
+ }
@@ -1 +1 @@
1
- export { http, normalizeHttpError } from "./http";
1
+ export { createHttpClient, http, normalizeHttpError, type HttpClientOptions, type HttpError } from "./http";
@@ -1,15 +1,29 @@
1
+ import { safeJsonParse } from "@/utils";
2
+
1
3
  type StorageKind = "local" | "session";
2
4
 
5
+ interface StorageOptions {
6
+ namespace?: string;
7
+ }
8
+
3
9
  function resolveStorage(kind: StorageKind): Storage {
4
10
  return kind === "session" ? sessionStorage : localStorage;
5
11
  }
6
12
 
7
- export function createStorage(kind: StorageKind = "local") {
13
+ function createKey(key: string, namespace?: string): string {
14
+ return namespace ? `${namespace}:${key}` : key;
15
+ }
16
+
17
+ export function createStorage(kind: StorageKind = "local", options: StorageOptions = {}) {
8
18
  const storage = resolveStorage(kind);
19
+ const namespace = options.namespace;
9
20
 
10
21
  return {
22
+ key(key: string): string {
23
+ return createKey(key, namespace);
24
+ },
11
25
  get<T>(key: string): T | null {
12
- const raw = storage.getItem(key);
26
+ const raw = storage.getItem(createKey(key, namespace));
13
27
  if (raw === null) return null;
14
28
  try {
15
29
  return JSON.parse(raw) as T;
@@ -18,14 +32,55 @@ export function createStorage(kind: StorageKind = "local") {
18
32
  throw new Error(`Storage value for ${key} is not valid JSON: ${message}`);
19
33
  }
20
34
  },
35
+ getOr<T>(key: string, fallback: T): T {
36
+ return safeJsonParse(storage.getItem(createKey(key, namespace)), fallback);
37
+ },
21
38
  set<T>(key: string, value: T): void {
22
- storage.setItem(key, JSON.stringify(value));
39
+ storage.setItem(createKey(key, namespace), JSON.stringify(value));
23
40
  },
24
41
  remove(key: string): void {
25
- storage.removeItem(key);
42
+ storage.removeItem(createKey(key, namespace));
26
43
  },
27
44
  clear(): void {
28
45
  storage.clear();
29
46
  }
30
47
  };
31
48
  }
49
+
50
+ export function createCookieStorage(options: StorageOptions & { maxAgeSeconds?: number } = {}) {
51
+ const namespace = options.namespace;
52
+ const maxAgeSeconds = options.maxAgeSeconds;
53
+
54
+ return {
55
+ key(key: string): string {
56
+ return createKey(key, namespace);
57
+ },
58
+ get<T>(key: string): T | string | null {
59
+ const value = readCookie(createKey(key, namespace));
60
+ if (value === null) return null;
61
+ return safeJsonParse<T | string>(value, value);
62
+ },
63
+ set<T>(key: string, value: T): void {
64
+ const encodedKey = encodeURIComponent(createKey(key, namespace));
65
+ const encodedValue = encodeURIComponent(JSON.stringify(value));
66
+ const maxAge = typeof maxAgeSeconds === "number" ? `; max-age=${maxAgeSeconds}` : "";
67
+ document.cookie = `${encodedKey}=${encodedValue}${maxAge}; path=/`;
68
+ },
69
+ remove(key: string): void {
70
+ document.cookie = `${encodeURIComponent(createKey(key, namespace))}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
71
+ },
72
+ has(key: string): boolean {
73
+ return readCookie(createKey(key, namespace)) !== null;
74
+ }
75
+ };
76
+ }
77
+
78
+ function readCookie(key: string): string | null {
79
+ const encodedKey = encodeURIComponent(key);
80
+ const item = document.cookie
81
+ .split(";")
82
+ .map((part) => part.trim())
83
+ .find((part) => part.startsWith(`${encodedKey}=`));
84
+ if (!item) return null;
85
+ return decodeURIComponent(item.slice(encodedKey.length + 1));
86
+ }
@@ -1,4 +1,6 @@
1
1
  @use "./tokens" as *;
2
+ @use "./reset";
3
+ @use "./utilities";
2
4
 
3
5
  :root {
4
6
  color-scheme: light;
@@ -10,12 +12,3 @@
10
12
  -webkit-font-smoothing: antialiased;
11
13
  -moz-osx-font-smoothing: grayscale;
12
14
  }
13
-
14
- * { box-sizing: border-box; }
15
-
16
- html, body, #app {
17
- min-height: 100%;
18
- margin: 0;
19
- }
20
-
21
- body { background: $color-surface-muted; }
@@ -0,0 +1,23 @@
1
+ @use "./tokens" as *;
2
+
3
+ * {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ html,
8
+ body,
9
+ #app {
10
+ min-height: 100%;
11
+ margin: 0;
12
+ }
13
+
14
+ body {
15
+ background: $color-surface-muted;
16
+ }
17
+
18
+ button,
19
+ input,
20
+ textarea,
21
+ select {
22
+ font: inherit;
23
+ }
@@ -17,4 +17,3 @@ $space-6: 24px;
17
17
 
18
18
  $radius-control: 6px;
19
19
  $radius-card: 8px;
20
- $table-min-width: 960px;
@@ -0,0 +1,38 @@
1
+ @use "./tokens" as *;
2
+
3
+ .u-flex {
4
+ display: flex;
5
+ }
6
+
7
+ .u-flex-column {
8
+ display: flex;
9
+ flex-direction: column;
10
+ }
11
+
12
+ .u-flex-center {
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ }
17
+
18
+ .u-flex-between {
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ }
23
+
24
+ .u-text-truncate {
25
+ overflow: hidden;
26
+ text-overflow: ellipsis;
27
+ white-space: nowrap;
28
+ }
29
+
30
+ .u-full-width {
31
+ width: 100%;
32
+ }
33
+
34
+ .u-surface {
35
+ background: $color-surface;
36
+ border: 1px solid $color-border;
37
+ border-radius: $radius-card;
38
+ }
@@ -9,3 +9,72 @@ export function isBlank(value: unknown): value is string {
9
9
  export function pickDefined<T extends Record<string, unknown>>(value: T): Partial<T> {
10
10
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as Partial<T>;
11
11
  }
12
+
13
+ export function safeJsonParse<T>(value: string | null, fallback: T): T {
14
+ if (value === null || value.trim() === "") return fallback;
15
+ try {
16
+ return JSON.parse(value) as T;
17
+ } catch {
18
+ return fallback;
19
+ }
20
+ }
21
+
22
+ export async function awaitTo<T>(promise: Promise<T>): Promise<[Error, null] | [null, T]> {
23
+ try {
24
+ return [null, await promise];
25
+ } catch (error) {
26
+ return [normalizeError(error), null];
27
+ }
28
+ }
29
+
30
+ export function normalizeError(error: unknown): Error {
31
+ return error instanceof Error ? error : new Error(String(error));
32
+ }
33
+
34
+ export function debounce<T extends (...args: unknown[]) => void>(callback: T, delayMs: number): (...args: Parameters<T>) => void {
35
+ let timer: ReturnType<typeof setTimeout> | null = null;
36
+ return (...args) => {
37
+ if (timer) clearTimeout(timer);
38
+ timer = setTimeout(() => callback(...args), delayMs);
39
+ };
40
+ }
41
+
42
+ export function debounceAsync<T extends (...args: unknown[]) => Promise<unknown>>(
43
+ callback: T,
44
+ delayMs: number
45
+ ): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
46
+ let timer: ReturnType<typeof setTimeout> | null = null;
47
+ return (...args) =>
48
+ new Promise((resolve, reject) => {
49
+ if (timer) clearTimeout(timer);
50
+ timer = setTimeout(() => {
51
+ callback(...args).then(
52
+ (value) => resolve(value as Awaited<ReturnType<T>>),
53
+ (error) => reject(error)
54
+ );
55
+ }, delayMs);
56
+ });
57
+ }
58
+
59
+ export function createLoadingTask<TArgs extends unknown[], TResult>(
60
+ task: (...args: TArgs) => Promise<TResult>
61
+ ): {
62
+ readonly isLoading: boolean;
63
+ run: (...args: TArgs) => Promise<TResult | null>;
64
+ } {
65
+ let loading = false;
66
+ return {
67
+ get isLoading() {
68
+ return loading;
69
+ },
70
+ async run(...args) {
71
+ if (loading) return null;
72
+ loading = true;
73
+ try {
74
+ return await task(...args);
75
+ } finally {
76
+ loading = false;
77
+ }
78
+ }
79
+ };
80
+ }
@@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test";
3
3
  test("desktop smoke renders the scaffold shell", async ({ page }) => {
4
4
  await page.goto("/");
5
5
 
6
- await expect(page.getByRole("heading", { level: 1, name: "Engineering base is ready" })).toBeVisible();
6
+ await expect(page.getByRole("heading", { level: 1, name: "Project foundation is ready" })).toBeVisible();
7
7
  await expect(page.getByRole("heading", { level: 2, name: "utils" })).toBeVisible();
8
8
  await expect(page.getByRole("heading", { level: 2, name: "storage" })).toBeVisible();
9
9
  await expect(page.getByRole("heading", { level: 2, name: "service" })).toBeVisible();
@@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test";
3
3
  test("mobile smoke keeps scaffold content in the viewport", async ({ page }) => {
4
4
  await page.goto("/");
5
5
 
6
- await expect(page.getByRole("heading", { level: 1, name: "Engineering base is ready" })).toBeVisible();
6
+ await expect(page.getByRole("heading", { level: 1, name: "Project foundation is ready" })).toBeVisible();
7
7
  const documentOverflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth);
8
8
  expect(documentOverflow).toBe(false);
9
9
  });
@@ -4,10 +4,10 @@ import App from "../../src/App.vue";
4
4
  import { renderPage } from "../render";
5
5
 
6
6
  describe("scaffold app", () => {
7
- it("renders the engineering baseline sections", () => {
7
+ it("renders the application baseline sections", () => {
8
8
  renderPage(App);
9
9
 
10
- expect(screen.getByRole("heading", { level: 1, name: "Engineering base is ready" })).toBeInTheDocument();
10
+ expect(screen.getByRole("heading", { level: 1, name: "Project foundation is ready" })).toBeInTheDocument();
11
11
  expect(screen.getByRole("heading", { level: 2, name: "utils" })).toBeInTheDocument();
12
12
  expect(screen.getByRole("heading", { level: 2, name: "storage" })).toBeInTheDocument();
13
13
  expect(screen.getByRole("heading", { level: 2, name: "service" })).toBeInTheDocument();