Commit Graph

10 Commits

Author SHA1 Message Date
Jordan Ritter 6dea8ad764 style(showcase): apply oxfmt to SU shell-runtime-urls follow-up files
Pure prettier-style rewrite (line-wrapping, single-line collapses, multi-
line callback indentation). No semantic changes — confirmed via tsc clean,
oxlint clean, vitest 235/235 pass in showcase/shell.

Touched files:
- showcase/scripts/__tests__/generate-registry-pattern.test.ts
- showcase/scripts/generate-registry.ts
- showcase/shell/src/lib/backend-url.ts
- showcase/shell/src/lib/backend-url.test.ts
- showcase/shell/src/lib/docs-redirects.test.ts
- showcase/shell/src/lib/local-backends-env.test.ts
- showcase/shell/src/lib/runtime-config.ts
- showcase/shell/src/lib/runtime-config.test.ts
- showcase/shell/src/lib/runtime-url-wiring.test.ts
- showcase/shell/src/middleware.ts
- showcase/shell/src/middleware.test.ts
- showcase/shell/vitest.global-setup.ts
2026-06-11 14:02:36 -07:00
Jordan Ritter 8e427bbc0a fix(showcase): final-round backend-pattern + POSTHOG + table + script-side parity (SU7-F1/F2/F3)
SU7-F1 — backend host pattern hardening:
- F1.1  Reject bare trailing ?/# in the backend host pattern
- F1.2  Strip internal tab/CR/LF from the backend host pattern
- F1.3  Warn when ignoring an empty-string local backend override
- F1.4  Reject empty-userinfo @ in the backend host pattern authority
- F1.5  Keep __proto__ keys as data in local-backend maps
- F1.6  Commit the local-backends memo key only after the value computes
- F1.7  Trim local backend overrides before validation and name the real
        rejection
- F1.8  Honest FATAL when the pattern host is a stray scheme fragment
- F1.9  Canonicalize the pattern authority for parity with the override
        path
- F1.10 Acknowledge the staging-to-prod fail-open in the pattern fallback
- F1.11 Harden backend-url/local-backends-env test hygiene

SU7-F2 — runtime-config & client-config edge cases:
- F2.1  Branch POSTHOG_HOST rejection reasons (scheme/degenerate/parse-
        failure) instead of the catch-all mislabel
- F2.2  Reject loopback BASE_URL/DOCS_HOST in production instead of the
        silent http:// prepend
- F2.3  Key the DOCS_HOST fallback once-guard on (mode, shellHost, value)
        and mode-prefix all value-only guard keys
- F2.4  Reject a present-but-empty posthogKey in the client config reader
- F2.5  Drop the trailing slash from SSR_PLACEHOLDER_URL for structural
        parity with server values
- F2.6  Attribute the DOCS_HOST slash-strip to readDocsHost itself
- F2.7  Normalize trailing-dot FQDN spellings in the docs self-host loop
        guard (both compare sides)
- F2.8  Harden console spies to capture all log args; pin the full all-env
        config shape; converge SSR simulation on vi.stubGlobal

SU7-F3 — script-side parity, table classification & test isolation:
- F3 #1  Handle a missing reference integration per the error contract
- F3 #2  Port the runtime backend-host-pattern normalization into the
         generator — scheme/trailing-slash strip, degenerate fallback,
         NEXT_PUBLIC fallback
- F3 #3  Treat non-mapping manifest parses (empty/null/scalar/array YAML)
         as validation errors, not TypeErrors
- F3 #4  Label a missing/unreadable constraints.yaml per the stderr+exit(1)
         error contract
- F3 #5  Align atomic-write tmp naming with the test harness straggler-
         sweep convention; guard main() on direct invocation
- F3 #6  Correct the determineCellStatus unshipped docstring; replace
         stale hardcoded cell counts with formulas
- F3 #7  Isolate the pattern suite on a per-suite tmpdir harness; snapshot
         the generator's full write set
- F3 #8  Classify discarded duplicate wildcards as duplicates — hoist the
         owner check above the destination warns
- F3 #9  Reject a root ("/") EXACT seo-redirect source — homepage-hijack
         twin of the root-wildcard guard
- F3 #10 Reject seo-redirect entries with non-printable-ASCII source/
         destination — close the silent-dead-entry class
- F3 #11 Strip trailing slashes in normalizePosthogHost before the scheme
         test
- F3 #12 Message-filter the empty-slug-set error count; pin the single
         matcher entry
2026-06-11 14:01:15 -07:00
Jordan Ritter 4f9f5dba4c fix(showcase/shell): docs-host robustness, builder & matcher hardening (SU2-stragglers + SU5 + SU6-A/B)
Round-by-round CR convergence covering the redirect builder, the middleware
matcher, the docs-host self-loop guard, and the runtime-config env readers.

Highlights:
- Clear module-load warns after fresh middleware import
- Validate SET BASE_URL values (scheme-less/degenerate/garbage) with
  sentinel fallback + once-guarded FATAL log
- Normalize path/query/fragment-bearing DOCS_HOST to origin; reject
  non-http(s) schemes; branch rejection reasons
- Harden POSTHOG_HOST (degenerate-host/scheme rejection); expose
  posthogKey via readEnvPair semantics
- Reject a DOCS_HOST equal to the shell's own host (redirect-loop guard,
  authority compare)
- Warn on missing local-ports.json under SHOWCASE_LOCAL=1 and validate
  TCP port range; extract helper for tests
- backend-url hardening — slug charset guard, frozen local-backends memo,
  pattern path-segment warn, local-override URL validation
- Client config fail-loud covers all four URL fields with type checks
- Make RuntimeConfig.posthogKey optional — absence is a valid state, not
  a wiring bug
- Drop R15/R17 and guard /integrations from SEO redirects
- Dedup duplicate wildcard prefixes with first-match-wins warn
- Validate malformed SEO entries at lookup-build time
- Restore case-insensitive redirect matching parity
- Normalize trailing slashes before redirect matching
- Keep the framework segment on F13, pin MG3 case fix
- Read posthogKey from runtime config in middleware, not raw process.env
- Fall back to the default backend host pattern for degenerate values
- Disable docs redirects when the default fallback collides with the
  shell host
- Bring validateBaseUrl to parity with its sibling readers
- Strip query/fragment from POSTHOG_HOST while keeping reverse-proxy paths
- Restrict local backend overrides to http(s) URLs
- Add server-only guard to runtime-config
- Harden localBackendsEnv failure posture
- Hoist /integrations namespace guard above the docs-host redirect
- Validate seo-redirect sources and cross-kind shadowing in
  buildRedirectLookup
- Unify slash normalization for middleware matching
- Lowercase-normalize REGISTRY_FRAMEWORK_SLUGS at construction
- Escalate missing POSTHOG_KEY to console.error in production
- Skip all redirect steps when docs redirects are disabled (sentinel
  consumer)
- Reject userinfo credentials in DOCS_HOST, POSTHOG_HOST, and the backend
  host pattern
- Branch dev-vs-prod logging in readDocsHost and fatalPatternOnce
- Prepend http:// (not https://) to scheme-less loopback hosts
- Round-5 micro-finding batch across the URL config libs

SU5-A1..A7 — registry safety, // reject, builder lint batch (case-
insensitive :path*, same-destination twin allowlist, original-case
divergence remainder), matcher api boundary, generator+vitest infra, test
hygiene + empty docs-host guard, comment batch.

SU6-A1..A6 — reject miscased :path* tokens, warn on tokenless wildcards,
normalize redirect-destination comparisons like request time, reject
destinations containing "//", surface missing POSTHOG_KEY at config-
resolution time, compile matcher harness like Next's runtime, type
parse/tokensToRegexp in the path-to-regexp shim, keep buildRedirectLookup
JSDoc attached.

SU6-B1..B7 — reject query/fragment/userinfo in pattern and local-override
URL gates, return parsed-normalized URL form from validation success
paths, distinguish unset/blank/padded SHOWCASE_LOCAL states, warn when
SHOWCASE_LOCAL is set to a value other than 1, validate {slug} placeholder
in generate-registry, mirror middleware drop semantics in the wiring
test's registry re-derivation, pin the noStore spy and calls to one fresh
module instance in the Edge-path test.
2026-06-11 14:00:50 -07:00
Jordan Ritter b28a1dc494 fix(showcase/shell): runtime-config + backend-url env-hardening; redirect builder safety (SU2-A/B + CR2-C)
SU2-B series — runtime-config / backend-url env robustness:
- Correct the Edge-safety story in runtime-config (SU2-B1)
- Stop per-request FATAL-CONFIG spam for unset BASE_URL (SU2-B2)
- Prepend https:// to a scheme-less POSTHOG_HOST (SU2-B3)
- Trim whitespace paste artifacts in env values and host patterns (SU2-B4)
- Memoize parseLocalBackends and warn once per value (SU2-B5)
- Make {slug} substitution immune to $-patterns (SU2-B6)
- Harden the client runtime-config reader (SU2-B7)
- runtime-config hardening batch (SU2-B8)
- Validate local-ports.json before baking NEXT_PUBLIC_LOCAL_BACKENDS (SU2-B9)
- test: warn-once assertions retry-safe; stop console leaks (SU2-B10)

CR2-C series — test infrastructure:
- Generate registry.json in a vitest globalSetup (CR2-C1)
- Stop ambient POSTHOG_KEY firing real fetches in middleware tests (CR2-C2)
- Assert the production slug set, not a re-derivation (CR2-C3)
- Make the registry generator subprocess robust (CR2-C4)
- Middleware/wiring test hygiene batch (CR2-C5)

SU2-A series — redirect-layer & PostHog capture:
- Stop $-pattern expansion in wildcard redirect substitution (SU2-A1)
- Surface PostHog capture failures once per failure class (SU2-A2)
- Duplicate exact redirect sources are first-match-wins (SU2-A3)
- Resolve runtime config once per redirected request (SU2-A4)
- Include destination host in seo_redirect capture (SU2-A5)
- Normalize scheme-less POSTHOG_HOST at the capture use site (SU2-A6)
- Correct redirect-layer comments and guard wildcard prefix boundary (SU2-A7)
- Cover docs-host hardening branches, compile matcher via path-to-regexp (SU2-A8)
2026-06-11 14:00:20 -07:00
Jordan Ritter febd8bc946 feat(showcase/shell): runtime-derived backend URLs and docs-host redirects (SU-13)
Carry backendHostPattern + docsHost in the shell runtime config (no longer
baked from registry.json at Docker build time). Derive demo backend URLs at
runtime from the pattern; issue docs-host 301s from middleware with a runtime
DOCS_HOST so a misconfigured value can no longer 500 every docs route.

Validate NEXT_PUBLIC_LOCAL_BACKENDS and empty overrides; guard the backend
host pattern against silent env misconfigs. Reword the stale demo-page
comment about backend URL derivation. Pin the registry slug set and SSR
placeholder URL composition; fix env/spy/global leaks in runtime-config
test cleanup.

Squash of the initial runtime-URL refactor cluster:
- feat(showcase): carry backendHostPattern + docsHost in shell runtime config
- fix(showcase): derive demo backend URLs at runtime instead of baked registry values
- fix(showcase): issue docs-host 301s from middleware with runtime DOCS_HOST
- fix(showcase): never let a misconfigured DOCS_HOST 500 every docs route
- fix(showcase): guard backend host pattern against silent env misconfigs
- fix(showcase): validate NEXT_PUBLIC_LOCAL_BACKENDS values and empty overrides
- docs(showcase): reword stale demo-page comment about backend URL derivation
- test(showcase): fix env/spy/global leaks in runtime-config test cleanup
- test(showcase): pin registry slug set and SSR placeholder URL composition
2026-06-11 13:59:46 -07:00
Jordan Ritter 9871d06671 refactor(showcase): rename getRuntimeConfigEdge to getRuntimeConfigForMiddleware
Clarify the wrapper's role (it forces noStore:false because unstable_noStore is
unavailable in middleware/Edge). Pure rename across shell, shell-docs, and
shell-dashboard: definitions, middleware call sites, and tests. No behavior
change.
2026-05-30 10:22:35 -07:00
Jordan Ritter 8714ab569b chore(showcase): apply oxfmt formatting across showcase scripts and shells
oxfmt --write normalized formatting on showcase scripts, the four shells, and the
new oxlint rule; required for the repo-root oxfmt --check CI gate.
2026-05-29 11:45:16 -07:00
Jordan Ritter 09b9f8910b chore(showcase): pre-push cleanup -- comment rot, log levels, env coalesce, test hardening
Non-functional cleanup pass on the showcase deploy-pipeline integration
branch. All changes are scoped to comment rot, log severity for already-
demoted runtime-config fields, length-aware env-name coalescing (a
deliberately-empty primary no longer masks a populated alternate), and
test-quality tightening. No production behavior change beyond the
specific items below.

Changes by area:

- shell/shell-dashboard/shell-docs runtime-config.ts: factor the
  `process.env[primary] ?? process.env[alt]` chain into a shared
  length-aware `readEnvPair` helper. The prior `??` form treated
  `PRIMARY=""` as set, masking a populated alternate; the helper now
  treats empty-string as unset and falls through to the alternate.
- shell-docs runtime-config.ts: demote the two recoverable URL fields
  (`intelligenceSignupUrl`, `posthogHost`) from console.info to
  console.warn. The `FATAL-CONFIG:` Sentry-alert prefix is preserved
  only on the true sentinels; the demoted fields now clear prod log-
  aggregation thresholds without raising ops alerts.
- All three shells' runtime-config.ts: prefix log lines with the shell
  name (e.g. `[shell-docs runtime-config]`) so the shared log stream
  identifies which shell emitted the line.
- shell-docs runtime-config-serialize.ts: rewrite the U+2028 / U+2029
  RegExp arguments using six-character ASCII backslash-u escape
  sequences (was: literal codepoints in the string arg). The literal
  codepoints are line terminators that a formatter or editor could
  silently strip, breaking the security-critical XSS escape. The
  ASCII form is robust to any such pass.
- shell-docs use-google-analytics.test.ts: de-tautologize the hook-
  order test. It now asserts `usePathname(` and `useEffect(` both
  exist in the source, so deleting all hooks would fail the test
  rather than trivially satisfying the early-return path.
- shell-dashboard baseline-types.test.ts: update the partner-count
  expectation from 25 to 26 -- the 26th entry (Cloudflare) is a
  legitimate integration that landed independently; the test was
  stale and had nothing to do with this branch.
- scripts/resolve-verify-matrix.ts: drop the `FIX 7 --` plan-
  internal prefix from a comment; keep the explanation.
- shell-docs/.env.example: correct the `NEXT_PUBLIC_SHELL_URL`
  fallback claim (sentinel, not canonical prod host) and document
  the remaining 7 consumed env vars with their FATAL/warn/silent
  semantics so the example matches runtime-config.ts.

Skipped:
- C-SENTINEL-DEDUP (`http://ops.invalid` shared constant across
  shell-dashboard's next.config.ts and runtime-config.ts): both
  files are at different module levels (root vs src/lib) and the
  string appears once in each; extracting to a shared module would
  widen the diff into a refactor for marginal benefit. Skipped per
  the spec's "if it widens diff awkwardly, skip" guidance.
- C-SSRTEST: already exhaustively covered. Each of the three shells
  has an SSR placeholder test that exercises every URL field via
  `new URL()` parseability and (for shell-docs) the analytics-key
  empty-string semantics. Treated as a no-op.

Validation: shell + shell-dashboard + shell-docs runtime-config /
serialize / GA tests green; bin/showcase Ruby suite green (87 runs);
showcase/scripts resolve-verify-matrix + aggregate-build-results +
lint-rule-no-public-env green (79 runs).
2026-05-29 11:45:15 -07:00
Jordan Ritter 28f33ecc8a fix(showcase): stop SSR 500 + hook-order regressions in shell runtime-config; tolerate env-name variants
Six fixes addressing CR findings on the Option-B runtime URL-injection migration:

1. SSR_PLACEHOLDER must be parseable URL sentinels — `new URL("")` throws on
   SSR causing 500s for any consumer that constructs URLs from runtime-config
   fields. Use `.invalid`-TLD sentinels (RFC 2606) for URL fields; analytics
   keys stay empty string. Add `suppressHydrationWarning` on consumers that
   render the placeholder server-side and the real value post-hydration
   (integration-grid, page-actions popover).

2. Hook-order: move `usePathname()`/`useEffect` ABOVE the early-return in
   use-google-analytics. Gate the effect bodies on `GA_ID` instead so React
   sees a stable hook order across renders.

3. `readUrl`/`readKey` accept either bare or `NEXT_PUBLIC_*`-prefixed env
   names via a fallback chain — covers both server-only and inlined-public
   variable conventions without forcing a rename across deploy targets.

4. Extract `serializeRuntimeConfig` to `lib/runtime-config-serialize.ts` so
   the OWASP-escape behavior (XSS via </script>, U+2028/U+2029 line-terminator
   injection) can be unit-tested without importing the layout into vitest.

5. Reclassify `intelligenceSignupUrl`/`posthogHost` from FATAL-CONFIG to
   info-level in shell-docs — these are optional integrations, not hard
   wiring failures, so absence should not poison the error stream.

6. Comment-rot cleanup: drop "Option B", B12, "the bug we are fixing", fix
   "four substrings"→"three substrings" miscounts, and refresh shell-docs
   .env.example to describe the runtime-injection contract instead of a
   stale next.config throw claim.

V1: shell + shell-docs `next build` succeeds (no Edge-runtime crash on
`unstable_noStore`).
V2: `OPS_BASE_URL=` shell-dashboard `next build` no longer throws —
`next.config.ts` is now a phase-aware function that emits a sentinel
destination at build time and throws only at start (PHASE_PRODUCTION_BUILD
from next/constants).

Tests: shell-docs 72/72, shell 12/12, shell-dashboard runtime-config 16/16
(pre-existing baseline-partner-count failure unchanged).
2026-05-29 11:45:15 -07:00
Jordan Ritter 83af6b8530 feat(showcase): add shell runtime-config server+client
Introduces showcase/shell/src/lib/runtime-config.ts (server-only —
imports next/cache and is read at request time by the root layout)
plus runtime-config.client.ts (reads window.__SHOWCASE_CONFIG__
injected by the layout). Shell's RuntimeConfig contains baseUrl and
posthogHost. getRuntimeConfigEdge() provides the Edge-runtime variant
for middleware (skips unstable_noStore).

Adds vitest config + dev deps to package.json and red-green tests for
both modules. Tests verify env-vs-fallback precedence, trailing-slash
stripping, no-module-load-freeze (live process.env reads per call),
and the Edge wrapper's noStore-skip behavior.

Refs plan-B §B7.
2026-05-29 11:45:07 -07:00