diff --git a/docs_build/dev/BUILD_PR.md b/docs_build/dev/BUILD_PR.md index 8e003cf3d..d47106b59 100644 --- a/docs_build/dev/BUILD_PR.md +++ b/docs_build/dev/BUILD_PR.md @@ -1,23 +1,23 @@ -# PR_26177_004-shared-color-foundation +# PR_26177_005-shared-text-foundation ## Purpose -Add a small shared color foundation. +Add a small shared text foundation. ## Source Of Truth -This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth for `PR_26177_004-shared-color-foundation`. +This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth for `PR_26177_005-shared-text-foundation`. ## Stack -- Base branch: `PR_26177_003-shared-geometry-foundation` +- Base branch: `PR_26177_004-shared-color-foundation` ## Exact Scope -- Add `src/shared/color/` foundation. -- Include hex/rgb/hsl conversion, clamp, lerp/blend helpers, luminance/contrast basics. -- Add targeted tests for the shared color area. -- No page styling changes. +- Add `src/shared/text/` foundation. +- Include safe string helpers such as slugify, casing, truncate, escapeHtml, and normalizeWhitespace. +- Add targeted tests for the shared text area. +- No copy rewrites outside tests/docs. - Create required Codex reports under `docs_build/dev/reports/`. - Create repo-structured delta ZIP under `tmp/`. @@ -25,19 +25,19 @@ This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth f - `docs_build/dev/PLAN_PR.md` - `docs_build/dev/BUILD_PR.md` -- `src/shared/color/color.js` -- `tests/shared/ColorFoundation.test.mjs` -- `docs_build/dev/reports/PR_26177_004-shared-color-foundation.md` -- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md` -- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md` -- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md` -- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md` +- `src/shared/text/text.js` +- `tests/shared/TextFoundation.test.mjs` +- `docs_build/dev/reports/PR_26177_005-shared-text-foundation.md` +- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md` +- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md` +- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md` +- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md` - `docs_build/dev/reports/codex_review.diff` - `docs_build/dev/reports/codex_changed_files.txt` ## Out Of Scope -- No page styling changes. +- No copy rewrites outside tests/docs. - No browser-owned product data. - No runtime UI changes. - No browser storage changes. @@ -51,9 +51,9 @@ This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth f Run exactly: ```powershell -node ./scripts/run-node-test-files.mjs tests/shared/ColorFoundation.test.mjs -node --check src/shared/color/color.js -node --check tests/shared/ColorFoundation.test.mjs +node ./scripts/run-node-test-files.mjs tests/shared/TextFoundation.test.mjs +node --check src/shared/text/text.js +node --check tests/shared/TextFoundation.test.mjs git diff --check ``` @@ -62,5 +62,5 @@ git diff --check Create repo-structured delta ZIP: ```text -tmp/PR_26177_004-shared-color-foundation_delta.zip +tmp/PR_26177_005-shared-text-foundation_delta.zip ``` diff --git a/docs_build/dev/PLAN_PR.md b/docs_build/dev/PLAN_PR.md index 62670625e..0682bd3cf 100644 --- a/docs_build/dev/PLAN_PR.md +++ b/docs_build/dev/PLAN_PR.md @@ -1,23 +1,22 @@ -# PLAN_PR: PR_26177_004-shared-color-foundation +# PLAN_PR: PR_26177_005-shared-text-foundation ## Purpose -Add a small shared color foundation. +Add a small shared text foundation. ## Scope -- Add `src/shared/color/` foundation. -- Include hex/rgb/hsl conversion helpers. -- Include clamp, lerp/blend helpers, luminance, and contrast basics. +- Add `src/shared/text/` foundation. +- Include safe string helpers for whitespace normalization, slug/casing, truncation, and HTML escaping. - Add targeted tests. -- No page styling changes. +- No copy rewrites outside tests/docs. - No browser-owned product data. - No runtime UI changes. - No unrelated cleanup. ## Implementation Plan -1. Add `src/shared/color/color.js`. -2. Add `tests/shared/ColorFoundation.test.mjs`. -3. Validate conversion, blending, luminance, and contrast helpers. +1. Add `src/shared/text/text.js`. +2. Add `tests/shared/TextFoundation.test.mjs`. +3. Validate safe string helpers. 4. Produce required Codex reports and repo-structured ZIP. diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation.md new file mode 100644 index 000000000..c8fce16be --- /dev/null +++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation.md @@ -0,0 +1,23 @@ +# PR_26177_005-shared-text-foundation + +Date: 2026-06-26 +Scope: Shared text foundation +Status: PASS + +## Summary + +- Added `src/shared/text/text.js`. +- Added safe string helpers for whitespace normalization, slug/casing, truncation, and HTML escaping. +- Added targeted tests in `tests/shared/TextFoundation.test.mjs`. +- No copy rewrites, runtime UI, browser product-data ownership, API/database, or unrelated cleanup changes were made. + +## Validation + +- PASS: `node ./scripts/run-node-test-files.mjs tests/shared/TextFoundation.test.mjs`. +- PASS: `node --check src/shared/text/text.js`. +- PASS: `node --check tests/shared/TextFoundation.test.mjs`. +- PASS: `git diff --check`. + +## Artifact + +- `tmp/PR_26177_005-shared-text-foundation_delta.zip` diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md new file mode 100644 index 000000000..04f5b7ca5 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md @@ -0,0 +1,5 @@ +# PR_26177_005-shared-text-foundation Branch Validation + +- PASS: Current branch is `PR_26177_005-shared-text-foundation`. +- PASS: Branch is stacked on `PR_26177_004-shared-color-foundation`. +- PASS: Scope is documentation/report files plus shared text code and tests only. diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md new file mode 100644 index 000000000..c93ec9d94 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md @@ -0,0 +1,6 @@ +# PR_26177_005-shared-text-foundation Manual Validation Notes + +- Reviewed scope against the user request and BUILD document. +- Confirmed the change does not alter UI, storage, API, database, scheduler, or engine behavior. +- Confirmed no `start_of_day` files were modified. +- Confirmed ZIP artifact path: `tmp/PR_26177_005-shared-text-foundation_delta.zip`. diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md new file mode 100644 index 000000000..87ebe2d92 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md @@ -0,0 +1,13 @@ +# PR_26177_005-shared-text-foundation Requirement Checklist + +- PASS: One PR purpose only. +- PASS: Smallest valid scoped change. +- PASS: No unrelated cleanup. +- PASS: Did not modify `start_of_day` folders. +- PASS: Did not add browser product-data ownership. +- PASS: Used ES modules consistent with repo style. +- PASS: Added `src/shared/text/` foundation. +- PASS: Included `slugify`, casing, `truncate`, `escapeHtml`, and `normalizeWhitespace` helpers. +- PASS: No copy rewrites outside tests/docs. +- PASS: Added targeted tests for the shared text area. +- PASS: Did not run full samples smoke by default. diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md new file mode 100644 index 000000000..8fa44a0c0 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md @@ -0,0 +1,8 @@ +# PR_26177_005-shared-text-foundation Validation Lane + +- PASS: `node ./scripts/run-node-test-files.mjs tests/shared/TextFoundation.test.mjs`. +- PASS: `node --check src/shared/text/text.js`. +- PASS: `node --check tests/shared/TextFoundation.test.mjs`. +- PASS: `git diff --check`. + +Full samples smoke was intentionally not run because this PR changes only a shared helper module and targeted tests. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 1b00743a4..b115685ce 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,11 +1,11 @@ docs_build/dev/BUILD_PR.md docs_build/dev/PLAN_PR.md -docs_build/dev/reports/PR_26177_004-shared-color-foundation.md -docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md -docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md -docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md -docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md +docs_build/dev/reports/PR_26177_005-shared-text-foundation.md +docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md +docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md +docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md +docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md docs_build/dev/reports/codex_changed_files.txt docs_build/dev/reports/codex_review.diff -src/shared/color/color.js -tests/shared/ColorFoundation.test.mjs +src/shared/text/text.js +tests/shared/TextFoundation.test.mjs diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index e85e64434..596dc3bbd 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,36 +1,36 @@ diff --git a/docs_build/dev/BUILD_PR.md b/docs_build/dev/BUILD_PR.md -index cc725fb0f..8e003cf3d 100644 +index 8e003cf3d..d47106b59 100644 --- a/docs_build/dev/BUILD_PR.md +++ b/docs_build/dev/BUILD_PR.md @@ -1,23 +1,23 @@ --# PR_26177_003-shared-geometry-foundation -+# PR_26177_004-shared-color-foundation +-# PR_26177_004-shared-color-foundation ++# PR_26177_005-shared-text-foundation ## Purpose --Add a small shared geometry foundation. -+Add a small shared color foundation. +-Add a small shared color foundation. ++Add a small shared text foundation. ## Source Of Truth --This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth for `PR_26177_003-shared-geometry-foundation`. -+This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth for `PR_26177_004-shared-color-foundation`. +-This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth for `PR_26177_004-shared-color-foundation`. ++This `BUILD_PR.md`, `PLAN_PR.md`, and the user request are the source of truth for `PR_26177_005-shared-text-foundation`. ## Stack --- Base branch: `PR_26177_002-shared-noise-foundation` -+- Base branch: `PR_26177_003-shared-geometry-foundation` +-- Base branch: `PR_26177_003-shared-geometry-foundation` ++- Base branch: `PR_26177_004-shared-color-foundation` ## Exact Scope --- Add `src/shared/geometry/` foundation. --- Include small reusable primitives/helpers such as vectors, rectangles, bounds, distance, clamp/intersection basics. --- Add targeted tests for the shared geometry area. --- No engine refactor. -+- Add `src/shared/color/` foundation. -+- Include hex/rgb/hsl conversion, clamp, lerp/blend helpers, luminance/contrast basics. -+- Add targeted tests for the shared color area. -+- No page styling changes. +-- Add `src/shared/color/` foundation. +-- Include hex/rgb/hsl conversion, clamp, lerp/blend helpers, luminance/contrast basics. +-- Add targeted tests for the shared color area. +-- No page styling changes. ++- Add `src/shared/text/` foundation. ++- Include safe string helpers such as slugify, casing, truncate, escapeHtml, and normalizeWhitespace. ++- Add targeted tests for the shared text area. ++- No copy rewrites outside tests/docs. - Create required Codex reports under `docs_build/dev/reports/`. - Create repo-structured delta ZIP under `tmp/`. @@ -38,27 +38,27 @@ index cc725fb0f..8e003cf3d 100644 - `docs_build/dev/PLAN_PR.md` - `docs_build/dev/BUILD_PR.md` --- `src/shared/geometry/geometry.js` --- `tests/shared/GeometryFoundation.test.mjs` --- `docs_build/dev/reports/PR_26177_003-shared-geometry-foundation.md` --- `docs_build/dev/reports/PR_26177_003-shared-geometry-foundation_branch-validation.md` --- `docs_build/dev/reports/PR_26177_003-shared-geometry-foundation_requirement-checklist.md` --- `docs_build/dev/reports/PR_26177_003-shared-geometry-foundation_validation-lane.md` --- `docs_build/dev/reports/PR_26177_003-shared-geometry-foundation_manual-validation-notes.md` -+- `src/shared/color/color.js` -+- `tests/shared/ColorFoundation.test.mjs` -+- `docs_build/dev/reports/PR_26177_004-shared-color-foundation.md` -+- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md` -+- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md` -+- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md` -+- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md` +-- `src/shared/color/color.js` +-- `tests/shared/ColorFoundation.test.mjs` +-- `docs_build/dev/reports/PR_26177_004-shared-color-foundation.md` +-- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md` +-- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md` +-- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md` +-- `docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md` ++- `src/shared/text/text.js` ++- `tests/shared/TextFoundation.test.mjs` ++- `docs_build/dev/reports/PR_26177_005-shared-text-foundation.md` ++- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md` ++- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md` ++- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md` ++- `docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md` - `docs_build/dev/reports/codex_review.diff` - `docs_build/dev/reports/codex_changed_files.txt` ## Out Of Scope --- No engine refactor. -+- No page styling changes. +-- No page styling changes. ++- No copy rewrites outside tests/docs. - No browser-owned product data. - No runtime UI changes. - No browser storage changes. @@ -66,12 +66,12 @@ index cc725fb0f..8e003cf3d 100644 Run exactly: ```powershell --node ./scripts/run-node-test-files.mjs tests/shared/GeometryFoundation.test.mjs --node --check src/shared/geometry/geometry.js --node --check tests/shared/GeometryFoundation.test.mjs -+node ./scripts/run-node-test-files.mjs tests/shared/ColorFoundation.test.mjs -+node --check src/shared/color/color.js -+node --check tests/shared/ColorFoundation.test.mjs +-node ./scripts/run-node-test-files.mjs tests/shared/ColorFoundation.test.mjs +-node --check src/shared/color/color.js +-node --check tests/shared/ColorFoundation.test.mjs ++node ./scripts/run-node-test-files.mjs tests/shared/TextFoundation.test.mjs ++node --check src/shared/text/text.js ++node --check tests/shared/TextFoundation.test.mjs git diff --check ``` @@ -79,361 +79,260 @@ index cc725fb0f..8e003cf3d 100644 Create repo-structured delta ZIP: ```text --tmp/PR_26177_003-shared-geometry-foundation_delta.zip -+tmp/PR_26177_004-shared-color-foundation_delta.zip +-tmp/PR_26177_004-shared-color-foundation_delta.zip ++tmp/PR_26177_005-shared-text-foundation_delta.zip ``` diff --git a/docs_build/dev/PLAN_PR.md b/docs_build/dev/PLAN_PR.md -index b9bb1a6d3..62670625e 100644 +index 62670625e..0682bd3cf 100644 --- a/docs_build/dev/PLAN_PR.md +++ b/docs_build/dev/PLAN_PR.md -@@ -1,22 +1,23 @@ --# PLAN_PR: PR_26177_003-shared-geometry-foundation -+# PLAN_PR: PR_26177_004-shared-color-foundation +@@ -1,23 +1,22 @@ +-# PLAN_PR: PR_26177_004-shared-color-foundation ++# PLAN_PR: PR_26177_005-shared-text-foundation ## Purpose --Add a small shared geometry foundation. -+Add a small shared color foundation. +-Add a small shared color foundation. ++Add a small shared text foundation. ## Scope --- Add `src/shared/geometry/` foundation. --- Include reusable primitives/helpers for vectors, rectangles, bounds, distance, clamp, containment, and intersection basics. -+- Add `src/shared/color/` foundation. -+- Include hex/rgb/hsl conversion helpers. -+- Include clamp, lerp/blend helpers, luminance, and contrast basics. +-- Add `src/shared/color/` foundation. +-- Include hex/rgb/hsl conversion helpers. +-- Include clamp, lerp/blend helpers, luminance, and contrast basics. ++- Add `src/shared/text/` foundation. ++- Include safe string helpers for whitespace normalization, slug/casing, truncation, and HTML escaping. - Add targeted tests. --- No engine refactor. -+- No page styling changes. +-- No page styling changes. ++- No copy rewrites outside tests/docs. - No browser-owned product data. - No runtime UI changes. - No unrelated cleanup. ## Implementation Plan --1. Add `src/shared/geometry/geometry.js`. --2. Add `tests/shared/GeometryFoundation.test.mjs`. --3. Validate vector, rectangle, bounds, distance, clamp, containment, and intersection behavior. -+1. Add `src/shared/color/color.js`. -+2. Add `tests/shared/ColorFoundation.test.mjs`. -+3. Validate conversion, blending, luminance, and contrast helpers. +-1. Add `src/shared/color/color.js`. +-2. Add `tests/shared/ColorFoundation.test.mjs`. +-3. Validate conversion, blending, luminance, and contrast helpers. ++1. Add `src/shared/text/text.js`. ++2. Add `tests/shared/TextFoundation.test.mjs`. ++3. Validate safe string helpers. 4. Produce required Codex reports and repo-structured ZIP. -diff --git a/docs_build/dev/reports/PR_26177_004-shared-color-foundation.md b/docs_build/dev/reports/PR_26177_004-shared-color-foundation.md +diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt +index 1b00743a4..b115685ce 100644 +--- a/docs_build/dev/reports/codex_changed_files.txt ++++ b/docs_build/dev/reports/codex_changed_files.txt +@@ -1,11 +1,11 @@ + docs_build/dev/BUILD_PR.md + docs_build/dev/PLAN_PR.md +-docs_build/dev/reports/PR_26177_004-shared-color-foundation.md +-docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md +-docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md +-docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md +-docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md ++docs_build/dev/reports/PR_26177_005-shared-text-foundation.md ++docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md ++docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md ++docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md ++docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md + docs_build/dev/reports/codex_changed_files.txt + docs_build/dev/reports/codex_review.diff +-src/shared/color/color.js +-tests/shared/ColorFoundation.test.mjs ++src/shared/text/text.js ++tests/shared/TextFoundation.test.mjs +diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation.md new file mode 100644 -index 000000000..976ea2c1f +index 000000000..c8fce16be --- /dev/null -+++ b/docs_build/dev/reports/PR_26177_004-shared-color-foundation.md ++++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation.md @@ -0,0 +1,23 @@ -+# PR_26177_004-shared-color-foundation ++# PR_26177_005-shared-text-foundation + +Date: 2026-06-26 -+Scope: Shared color foundation ++Scope: Shared text foundation +Status: PASS + +## Summary + -+- Added `src/shared/color/color.js`. -+- Added hex/rgb/hsl conversion, clamp, lerp/blend, luminance, and contrast helpers. -+- Added targeted tests in `tests/shared/ColorFoundation.test.mjs`. -+- No page styling, runtime UI, API, database, or unrelated cleanup changes were made. ++- Added `src/shared/text/text.js`. ++- Added safe string helpers for whitespace normalization, slug/casing, truncation, and HTML escaping. ++- Added targeted tests in `tests/shared/TextFoundation.test.mjs`. ++- No copy rewrites, runtime UI, browser product-data ownership, API/database, or unrelated cleanup changes were made. + +## Validation + -+- PASS: `node ./scripts/run-node-test-files.mjs tests/shared/ColorFoundation.test.mjs`. -+- PASS: `node --check src/shared/color/color.js`. -+- PASS: `node --check tests/shared/ColorFoundation.test.mjs`. ++- PASS: `node ./scripts/run-node-test-files.mjs tests/shared/TextFoundation.test.mjs`. ++- PASS: `node --check src/shared/text/text.js`. ++- PASS: `node --check tests/shared/TextFoundation.test.mjs`. +- PASS: `git diff --check`. + +## Artifact + -+- `tmp/PR_26177_004-shared-color-foundation_delta.zip` -diff --git a/docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md ++- `tmp/PR_26177_005-shared-text-foundation_delta.zip` +diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md new file mode 100644 -index 000000000..d4699efd9 +index 000000000..04f5b7ca5 --- /dev/null -+++ b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_branch-validation.md -@@ -0,0 +1,8 @@ -+# PR_26177_004-shared-color-foundation Branch Validation -+ -+Status: PASS ++++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_branch-validation.md +@@ -0,0 +1,5 @@ ++# PR_26177_005-shared-text-foundation Branch Validation + -+- PASS: Branch `PR_26177_004-shared-color-foundation` was created from `PR_26177_003-shared-geometry-foundation`. -+- PASS: Stack base was clean before branch creation. -+- PASS: One PR purpose only: shared color foundation. -+- PASS: No `start_of_day` files changed. -diff --git a/docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md ++- PASS: Current branch is `PR_26177_005-shared-text-foundation`. ++- PASS: Branch is stacked on `PR_26177_004-shared-color-foundation`. ++- PASS: Scope is documentation/report files plus shared text code and tests only. +diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md new file mode 100644 -index 000000000..7c1b6d1be +index 000000000..c93ec9d94 --- /dev/null -+++ b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_manual-validation-notes.md -@@ -0,0 +1,5 @@ -+# PR_26177_004-shared-color-foundation Manual Validation Notes -+ -+Status: PASS -+ -+Manual review confirmed this PR only adds shared color helpers and targeted tests. It does not change page styling, runtime UI, browser product-data ownership, API, or database behavior. -diff --git a/docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md ++++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_manual-validation-notes.md +@@ -0,0 +1,6 @@ ++# PR_26177_005-shared-text-foundation Manual Validation Notes ++ ++- Reviewed scope against the user request and BUILD document. ++- Confirmed the change does not alter UI, storage, API, database, scheduler, or engine behavior. ++- Confirmed no `start_of_day` files were modified. ++- Confirmed ZIP artifact path: `tmp/PR_26177_005-shared-text-foundation_delta.zip`. +diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md new file mode 100644 -index 000000000..48c379881 +index 000000000..87ebe2d92 --- /dev/null -+++ b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_requirement-checklist.md -@@ -0,0 +1,11 @@ -+# PR_26177_004-shared-color-foundation Requirement Checklist -+ -+| Requirement | Status | Notes | -+|---|---:|---| -+| Add `src/shared/color/` foundation | PASS | Added `src/shared/color/color.js`. | -+| Include hex/rgb/hsl conversion | PASS | Added conversion helpers. | -+| Include clamp helpers | PASS | Added `clamp01` and `clampByte`. | -+| Include lerp/blend helpers | PASS | Added `lerpColor` and `blendColors`. | -+| Include luminance/contrast basics | PASS | Added relative luminance and contrast ratio helpers. | -+| No page styling changes | PASS | No CSS or page styling files changed. | -+| Add targeted tests | PASS | Added `tests/shared/ColorFoundation.test.mjs`. | -diff --git a/docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md ++++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_requirement-checklist.md +@@ -0,0 +1,13 @@ ++# PR_26177_005-shared-text-foundation Requirement Checklist ++ ++- PASS: One PR purpose only. ++- PASS: Smallest valid scoped change. ++- PASS: No unrelated cleanup. ++- PASS: Did not modify `start_of_day` folders. ++- PASS: Did not add browser product-data ownership. ++- PASS: Used ES modules consistent with repo style. ++- PASS: Added `src/shared/text/` foundation. ++- PASS: Included `slugify`, casing, `truncate`, `escapeHtml`, and `normalizeWhitespace` helpers. ++- PASS: No copy rewrites outside tests/docs. ++- PASS: Added targeted tests for the shared text area. ++- PASS: Did not run full samples smoke by default. +diff --git a/docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md new file mode 100644 -index 000000000..dd80db48b +index 000000000..8fa44a0c0 --- /dev/null -+++ b/docs_build/dev/reports/PR_26177_004-shared-color-foundation_validation-lane.md -@@ -0,0 +1,22 @@ -+# PR_26177_004-shared-color-foundation Validation Lane -+ -+Status: PASS -+ -+## Commands -+ -+```powershell -+node ./scripts/run-node-test-files.mjs tests/shared/ColorFoundation.test.mjs -+node --check src/shared/color/color.js -+node --check tests/shared/ColorFoundation.test.mjs -+git diff --check -+``` -+ -+## Results ++++ b/docs_build/dev/reports/PR_26177_005-shared-text-foundation_validation-lane.md +@@ -0,0 +1,8 @@ ++# PR_26177_005-shared-text-foundation Validation Lane + -+- PASS: Targeted color foundation test. -+- PASS: Changed JS syntax checks. ++- PASS: `node ./scripts/run-node-test-files.mjs tests/shared/TextFoundation.test.mjs`. ++- PASS: `node --check src/shared/text/text.js`. ++- PASS: `node --check tests/shared/TextFoundation.test.mjs`. +- PASS: `git diff --check`. + -+## Not Run -+ -+- Full samples smoke was not run by default. -diff --git a/src/shared/color/color.js b/src/shared/color/color.js ++Full samples smoke was intentionally not run because this PR changes only a shared helper module and targeted tests. +diff --git a/src/shared/text/text.js b/src/shared/text/text.js new file mode 100644 -index 000000000..61bdbfd5e +index 000000000..3e387388f --- /dev/null -+++ b/src/shared/color/color.js -@@ -0,0 +1,160 @@ -+function finiteOrZero(value) { -+ const number = Number(value); -+ return Number.isFinite(number) ? number : 0; ++++ b/src/shared/text/text.js +@@ -0,0 +1,63 @@ ++const HTML_ESCAPE_MAP = Object.freeze({ ++ "&": "&", ++ "<": "<", ++ ">": ">", ++ "\"": """, ++ "'": "'", ++}); ++ ++function toText(value) { ++ return value == null ? "" : String(value); +} + -+export function clamp01(value) { -+ return Math.max(0, Math.min(1, finiteOrZero(value))); ++function stripDiacritics(value) { ++ return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, ""); +} + -+export function clampByte(value) { -+ return Math.round(Math.max(0, Math.min(255, finiteOrZero(value)))); ++export function normalizeWhitespace(value) { ++ return toText(value).replace(/\s+/g, " ").trim(); +} + -+function normalizeRgb(color) { -+ return { -+ r: clampByte(color?.r), -+ g: clampByte(color?.g), -+ b: clampByte(color?.b), -+ }; ++export function slugify(value) { ++ return stripDiacritics(normalizeWhitespace(value)) ++ .toLowerCase() ++ .replace(/[^a-z0-9]+/g, "-") ++ .replace(/^-+|-+$/g, ""); +} + -+function normalizeHue(value) { -+ return ((finiteOrZero(value) % 360) + 360) % 360; ++export function toTitleCase(value) { ++ return normalizeWhitespace(value).replace(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*/gu, (word) => ( ++ word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() ++ )); +} + -+function percentToUnit(value) { -+ const number = finiteOrZero(value); -+ return clamp01(number > 1 ? number / 100 : number); -+} ++export function toCamelCase(value) { ++ const words = stripDiacritics(normalizeWhitespace(value)) ++ .split(/[^A-Za-z0-9]+/) ++ .filter(Boolean); + -+function componentToHex(value) { -+ return clampByte(value).toString(16).padStart(2, "0"); ++ return words.map((word, index) => { ++ const lower = word.toLowerCase(); ++ return index === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1); ++ }).join(""); +} + -+/** -+ * Converts a hex color string to RGB components. -+ * -+ * @param {string} hex Hex color such as "#336699" or "#369". -+ * @returns {{r: number, g: number, b: number}} RGB color. -+ */ -+export function hexToRgb(hex) { -+ const text = String(hex || "").trim().replace(/^#/, ""); -+ const expanded = text.length === 3 -+ ? text.split("").map((character) => `${character}${character}`).join("") -+ : text; ++export function truncate(value, maxLength, options = {}) { ++ const text = toText(value); ++ const length = Math.max(0, Math.floor(Number(maxLength) || 0)); ++ const suffix = options.suffix == null ? "..." : String(options.suffix); + -+ if (!/^[\da-f]{6}$/i.test(expanded)) { -+ throw new TypeError("hex must be a 3 or 6 digit color string."); ++ if (text.length <= length) { ++ return text; + } + -+ return { -+ r: Number.parseInt(expanded.slice(0, 2), 16), -+ g: Number.parseInt(expanded.slice(2, 4), 16), -+ b: Number.parseInt(expanded.slice(4, 6), 16), -+ }; -+} -+ -+export function rgbToHex(color) { -+ const rgb = normalizeRgb(color); -+ return `#${componentToHex(rgb.r)}${componentToHex(rgb.g)}${componentToHex(rgb.b)}`; -+} -+ -+export function rgbToHsl(color) { -+ const rgb = normalizeRgb(color); -+ const r = rgb.r / 255; -+ const g = rgb.g / 255; -+ const b = rgb.b / 255; -+ const max = Math.max(r, g, b); -+ const min = Math.min(r, g, b); -+ const lightness = (max + min) / 2; -+ const delta = max - min; -+ -+ if (delta === 0) { -+ return { h: 0, s: 0, l: Number((lightness * 100).toFixed(6)) }; -+ } -+ -+ const saturation = delta / (1 - Math.abs(2 * lightness - 1)); -+ let hue = 0; -+ if (max === r) { -+ hue = 60 * (((g - b) / delta) % 6); -+ } else if (max === g) { -+ hue = 60 * ((b - r) / delta + 2); -+ } else { -+ hue = 60 * ((r - g) / delta + 4); -+ } -+ -+ return { -+ h: Number(normalizeHue(hue).toFixed(6)), -+ s: Number((saturation * 100).toFixed(6)), -+ l: Number((lightness * 100).toFixed(6)), -+ }; -+} -+ -+export function hslToRgb(color) { -+ const h = normalizeHue(color?.h); -+ const s = percentToUnit(color?.s); -+ const l = percentToUnit(color?.l); -+ const chroma = (1 - Math.abs(2 * l - 1)) * s; -+ const x = chroma * (1 - Math.abs(((h / 60) % 2) - 1)); -+ const m = l - chroma / 2; -+ let r = 0; -+ let g = 0; -+ let b = 0; -+ -+ if (h < 60) { -+ [r, g, b] = [chroma, x, 0]; -+ } else if (h < 120) { -+ [r, g, b] = [x, chroma, 0]; -+ } else if (h < 180) { -+ [r, g, b] = [0, chroma, x]; -+ } else if (h < 240) { -+ [r, g, b] = [0, x, chroma]; -+ } else if (h < 300) { -+ [r, g, b] = [x, 0, chroma]; -+ } else { -+ [r, g, b] = [chroma, 0, x]; ++ if (length <= suffix.length) { ++ return suffix.slice(0, length); + } + -+ return normalizeRgb({ -+ r: (r + m) * 255, -+ g: (g + m) * 255, -+ b: (b + m) * 255, -+ }); -+} -+ -+export function lerp(start, end, amount) { -+ return finiteOrZero(start) + (finiteOrZero(end) - finiteOrZero(start)) * clamp01(amount); -+} -+ -+export function lerpColor(start, end, amount) { -+ const a = normalizeRgb(start); -+ const b = normalizeRgb(end); -+ return normalizeRgb({ -+ r: lerp(a.r, b.r, amount), -+ g: lerp(a.g, b.g, amount), -+ b: lerp(a.b, b.b, amount), -+ }); -+} -+ -+export function blendColors(start, end, amount = 0.5) { -+ return rgbToHex(lerpColor(start, end, amount)); -+} -+ -+function linearizeChannel(value) { -+ const channel = clampByte(value) / 255; -+ return channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4; -+} -+ -+export function relativeLuminance(color) { -+ const rgb = normalizeRgb(color); -+ return 0.2126 * linearizeChannel(rgb.r) + 0.7152 * linearizeChannel(rgb.g) + 0.0722 * linearizeChannel(rgb.b); ++ return `${text.slice(0, length - suffix.length)}${suffix}`; +} + -+export function contrastRatio(first, second) { -+ const a = relativeLuminance(first); -+ const b = relativeLuminance(second); -+ const lighter = Math.max(a, b); -+ const darker = Math.min(a, b); -+ return Number(((lighter + 0.05) / (darker + 0.05)).toFixed(6)); ++export function escapeHtml(value) { ++ return toText(value).replace(/[&<>"']/g, (character) => HTML_ESCAPE_MAP[character]); +} -diff --git a/tests/shared/ColorFoundation.test.mjs b/tests/shared/ColorFoundation.test.mjs +diff --git a/tests/shared/TextFoundation.test.mjs b/tests/shared/TextFoundation.test.mjs new file mode 100644 -index 000000000..4a96d3a35 +index 000000000..893c5edfd --- /dev/null -+++ b/tests/shared/ColorFoundation.test.mjs -@@ -0,0 +1,50 @@ ++++ b/tests/shared/TextFoundation.test.mjs +@@ -0,0 +1,37 @@ +/* +Toolbox Aid +David Quesenberry +06/26/2026 -+ColorFoundation.test.mjs ++TextFoundation.test.mjs +*/ +import assert from "node:assert/strict"; +import { -+ blendColors, -+ clamp01, -+ clampByte, -+ contrastRatio, -+ hexToRgb, -+ hslToRgb, -+ lerpColor, -+ relativeLuminance, -+ rgbToHex, -+ rgbToHsl, -+} from "../../src/shared/color/color.js"; ++ escapeHtml, ++ normalizeWhitespace, ++ slugify, ++ toCamelCase, ++ toTitleCase, ++ truncate, ++} from "../../src/shared/text/text.js"; + +export function run() { -+ assert.equal(clamp01(2), 1); -+ assert.equal(clamp01(-1), 0); -+ assert.equal(clampByte(260), 255); -+ assert.equal(clampByte(12.4), 12); ++ assert.equal(normalizeWhitespace(" Alpha\n\tBeta Gamma "), "Alpha Beta Gamma"); ++ assert.equal(normalizeWhitespace(null), ""); + -+ assert.deepEqual(hexToRgb("#369"), { r: 51, g: 102, b: 153 }); -+ assert.deepEqual(hexToRgb("#336699"), { r: 51, g: 102, b: 153 }); -+ assert.equal(rgbToHex({ r: 51, g: 102, b: 153 }), "#336699"); -+ assert.throws(() => hexToRgb("not-a-color"), TypeError); ++ assert.equal(slugify(" Café Racer: Level 12! "), "cafe-racer-level-12"); ++ assert.equal(slugify(" -- "), ""); + -+ assert.deepEqual(rgbToHsl({ r: 255, g: 0, b: 0 }), { h: 0, s: 100, l: 50 }); -+ assert.deepEqual(hslToRgb({ h: 120, s: 100, l: 50 }), { r: 0, g: 255, b: 0 }); -+ assert.equal(rgbToHex(hslToRgb(rgbToHsl({ r: 51, g: 102, b: 153 }))), "#336699"); ++ assert.equal(toTitleCase("the QUICK-brown fox"), "The Quick-brown Fox"); ++ assert.equal(toCamelCase("Café racer level 12"), "cafeRacerLevel12"); + -+ assert.deepEqual(lerpColor({ r: 0, g: 0, b: 0 }, { r: 255, g: 255, b: 255 }, 0.5), { -+ r: 128, -+ g: 128, -+ b: 128, -+ }); -+ assert.equal(blendColors({ r: 255, g: 0, b: 0 }, { r: 0, g: 0, b: 255 }, 0.5), "#800080"); ++ assert.equal(truncate("Hello world", 8), "Hello..."); ++ assert.equal(truncate("Hello", 8), "Hello"); ++ assert.equal(truncate("Hello world", 2), ".."); ++ assert.equal(truncate("Hello world", 8, { suffix: "!" }), "Hello w!"); + -+ assert.equal(relativeLuminance({ r: 0, g: 0, b: 0 }), 0); -+ assert.equal(Number(relativeLuminance({ r: 255, g: 255, b: 255 }).toFixed(6)), 1); -+ assert.equal(contrastRatio({ r: 0, g: 0, b: 0 }, { r: 255, g: 255, b: 255 }), 21); ++ assert.equal(escapeHtml(""), "<button data-name="A&B">It's</button>"); +} + +if (import.meta.url === `file://${process.argv[1]}`) { diff --git a/src/shared/text/text.js b/src/shared/text/text.js new file mode 100644 index 000000000..3e387388f --- /dev/null +++ b/src/shared/text/text.js @@ -0,0 +1,63 @@ +const HTML_ESCAPE_MAP = Object.freeze({ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'", +}); + +function toText(value) { + return value == null ? "" : String(value); +} + +function stripDiacritics(value) { + return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, ""); +} + +export function normalizeWhitespace(value) { + return toText(value).replace(/\s+/g, " ").trim(); +} + +export function slugify(value) { + return stripDiacritics(normalizeWhitespace(value)) + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +export function toTitleCase(value) { + return normalizeWhitespace(value).replace(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*/gu, (word) => ( + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + )); +} + +export function toCamelCase(value) { + const words = stripDiacritics(normalizeWhitespace(value)) + .split(/[^A-Za-z0-9]+/) + .filter(Boolean); + + return words.map((word, index) => { + const lower = word.toLowerCase(); + return index === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1); + }).join(""); +} + +export function truncate(value, maxLength, options = {}) { + const text = toText(value); + const length = Math.max(0, Math.floor(Number(maxLength) || 0)); + const suffix = options.suffix == null ? "..." : String(options.suffix); + + if (text.length <= length) { + return text; + } + + if (length <= suffix.length) { + return suffix.slice(0, length); + } + + return `${text.slice(0, length - suffix.length)}${suffix}`; +} + +export function escapeHtml(value) { + return toText(value).replace(/[&<>"']/g, (character) => HTML_ESCAPE_MAP[character]); +} diff --git a/tests/shared/TextFoundation.test.mjs b/tests/shared/TextFoundation.test.mjs new file mode 100644 index 000000000..893c5edfd --- /dev/null +++ b/tests/shared/TextFoundation.test.mjs @@ -0,0 +1,37 @@ +/* +Toolbox Aid +David Quesenberry +06/26/2026 +TextFoundation.test.mjs +*/ +import assert from "node:assert/strict"; +import { + escapeHtml, + normalizeWhitespace, + slugify, + toCamelCase, + toTitleCase, + truncate, +} from "../../src/shared/text/text.js"; + +export function run() { + assert.equal(normalizeWhitespace(" Alpha\n\tBeta Gamma "), "Alpha Beta Gamma"); + assert.equal(normalizeWhitespace(null), ""); + + assert.equal(slugify(" Café Racer: Level 12! "), "cafe-racer-level-12"); + assert.equal(slugify(" -- "), ""); + + assert.equal(toTitleCase("the QUICK-brown fox"), "The Quick-brown Fox"); + assert.equal(toCamelCase("Café racer level 12"), "cafeRacerLevel12"); + + assert.equal(truncate("Hello world", 8), "Hello..."); + assert.equal(truncate("Hello", 8), "Hello"); + assert.equal(truncate("Hello world", 2), ".."); + assert.equal(truncate("Hello world", 8, { suffix: "!" }), "Hello w!"); + + assert.equal(escapeHtml(""), "<button data-name="A&B">It's</button>"); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + run(); +}