Skip to content

fix(dashboard): cap env name at 32 chars + strip underscores (BUG-DASH-001/002)#147

Merged
mastermanas805 merged 1 commit into
mainfrom
fix/p1-dashboard-env-validation-2026-05-29
May 29, 2026
Merged

fix(dashboard): cap env name at 32 chars + strip underscores (BUG-DASH-001/002)#147
mastermanas805 merged 1 commit into
mainfrom
fix/p1-dashboard-env-validation-2026-05-29

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

Closes BUG-DASH-001 (P0) and BUG-DASH-002 from the 2026-05-29 QA wave.

What broke

BUG-DASH-001 (P0) — env-switcher "+ new env…" accepted env names of arbitrary length, persisted them to localStorage, broke every subsequent API call:

  • GET /api/v1/vault/<67-char> → 400 invalid_env
  • GET /api/v1/resources?env=<67> → 200 with empty list (BUG-DASH-007 read-path gap)
  • No UI affordance to delete the bad env — user had to open devtools and clear localStorage to recover.

BUG-DASH-002 — the JS regex was [^a-z0-9_-] (permits underscores). The api regex is ^[a-z0-9-]{1,32}$ (no underscore). A user typing my_env_name would persist locally then 400 on every API call.

What the fix does

In addEnv() (src/hooks/useDashboardCtx.ts):

  1. Drop underscore from the char-class strip → underscore-bearing input is scrubbed (BUG-DASH-002).
  2. .slice(0, 32) clips to the api's 32-char cap before any persist (BUG-DASH-001).
  3. Final ENV_REGEX = /^[a-z0-9-]{1,32}$/ gate validates the post-strip-and-clip string; mismatch → bail out without touching state, so the live env stays on its previous valid value (the user can never get locked into a broken state).

No UI plumbing changes — existing <EnvSwitcher /> gates addEnv behind a non-empty draft already.

Test coverage

3 new test cases in useDashboardCtx.test.ts:

  • addEnv clips an over-32-char input to the api cap
  • addEnv strips underscores (api regex forbids them)
  • addEnv leaves live env unchanged on all-invalid + over-cap input

Existing tests (sanitises ' My Env!! 'myenv; ignores all-invalid '!!!') still pass — contract is strictly tightened, not changed.

Local gate (npm run gate — the EXACT mandatory gate per project memory feedback_coverage_measure_per_package_not_dotdotdot.md):

  • tsc --noEmit ✓
  • vite build + prerender ✓
  • vitest run ✓ — 76 test files, 1072 tests, 0 failures

Rule 22 surface checklist

  • instanode-web/src/hooks/useDashboardCtx.ts — addEnv regex + cap
  • instanode-web/src/hooks/useDashboardCtx.test.ts — regression tests

Backend API is unchanged — this PR brings the dashboard's client validation into line with the api's existing regex. CLAUDE.md + OpenAPI already reflect the api regex; no doc edits required.

Drift discipline

  • ✅ No new endpoints, no new tables, no new feature flags.
  • ✅ ~15 LOC of production change.
  • ✅ Single-site fix — addEnv is the only entry point that mutates the env state.

🤖 Generated with Claude Code

…H-001/002)

BUG-DASH-001 (P0): pre-fix the env-switcher's "+ new env…" input
accepted custom env names of arbitrary length and persisted them to
localStorage. A 67-char paste broke every subsequent /api/v1/* call:

  - GET /api/v1/vault/<67-char>       → 400 invalid_env
  - GET /api/v1/resources?env=<67>    → 200 with empty list (read-path
                                       gap, BUG-DASH-007)
  - GET /api/v1/deployments?env=<67>  → 200 with empty list

There was no UI affordance to delete the bad env — users had to open
devtools and clear localStorage to recover.

BUG-DASH-002: the JS regex was `[^a-z0-9_-]`, permitting underscores.
The api regex is `^[a-z0-9-]{1,32}$` (no underscore), so a user typing
`my_env_name` would persist locally and then 400 on every API call —
same "client says yes, server says no" gap.

Fix in addEnv() (src/hooks/useDashboardCtx.ts):

  1. Drop underscore from the char-class strip → underscore-bearing
     input is silently scrubbed, matching the api regex.
  2. .slice(0, 32) clips to the api's 32-char cap before any persist.
  3. Final ENV_REGEX gate validates the post-strip-and-clip string. If
     it doesn't match, return early WITHOUT touching state — the live
     env stays on its previous valid value, so the user is never
     locked into a broken state.

The existing UI plumbing in <EnvSwitcher /> (AppShell.tsx) already
gates addEnv behind a non-empty draft; no UI changes required.

Coverage block (per CLAUDE.md rule 17):
  Symptom:       /api/v1/vault/<too-long> → 400 invalid_env; user locked out
  Enumeration:   rg -n 'addEnv\|ENV_REGEX' src/hooks/ (1 hit pre-fix)
  Sites found:   1 (addEnv export)
  Sites touched: 1
  Coverage test: 3 new test cases in useDashboardCtx.test.ts:
                  - addEnv clips an over-32-char input to the api cap
                  - addEnv strips underscores (api regex forbids them)
                  - addEnv leaves live env unchanged on all-invalid input
                 Existing tests ("sanitises ... My Env!!", "ignores
                 all-invalid") still pass — the contract is strictly
                 tightened, not changed.
  Live verify:   open /app, click env-switcher → "+ new env…", type
                 67 chars → input is clipped to 32, regex-validated,
                 persisted only if it matches; reload and confirm
                 localStorage["instanode.env"] is ≤32 chars + api-shape.

Test results: 14 tests pass in useDashboardCtx.test.ts (was 11); full
`npm run gate` passes (76 test files, 1072 tests, 0 failures).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/assets/index-Btm4w76P.js 0 B (-100% 🔽)
dist/assets/index-BsJUZYRr.css 6.13 KB (0%)
dist/assets/index-HRt7gd2I.js 163.49 KB (+100% 🔺)

@mastermanas805 mastermanas805 merged commit ad549ab into main May 29, 2026
16 checks passed
@mastermanas805 mastermanas805 deleted the fix/p1-dashboard-env-validation-2026-05-29 branch May 29, 2026 16:42
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