Repository navigation
fix: keep deployment log stream connected on redeploy and harden project creation - #68
Merged
Merged
Conversation
…t duplicate project creates The deployment logs WebSocket previously gave up the moment it received any normal (1000) closure, which is exactly what happens when a redeploy churns the underlying log source — leaving the user staring at a frozen log pane. It also wiped state on every reconnect, so the partial logs they had were lost too. The reconnect path now: - always retries (exponential backoff, capped at 10s) until the component unmounts, since the backend tail is an infinite stream - preserves prior log state instead of clearing it - de-dupes by absolute file line number so the last-1000-lines replay the backend sends after reconnect doesn't show up twice Project creation could result in N duplicate projects from a single user intent because the submit-disabled flag was driven by React state, which isn't synchronous — a quick double click slipped two submissions through before the disabled prop landed. Adds a `useRef` re-entry guard in both `ProjectConfigurator` and `ManualProjectConfigurator` so the second call is dropped synchronously.
…and map slug races to 409 When create_project failed any step after the project row was inserted — default-environment creation, env-var insertion, or storage-link wiring — the project row was left dangling. The handler returned 500 but the user ended up with a half-initialized project visible in their list, and the real failure was buried inside ProjectError::Other(format!(...)) so the HTTP status was always 500 even when the underlying cause was a 4xx. Wrapping the multi-step create in a single Sea-ORM transaction would require pushing `&impl ConnectionTrait` through `EnvironmentService`, `EnvVarService`, and `ExternalServiceManager` — and would hold table locks across cross-service calls. We do manual rollback instead: split the post-insert work into `finalize_project_creation`, and on any error issue a single `Entity::delete_by_id` on the project row. CASCADE FKs clean up environments, env vars, and links. No long-lived locks; failures are observable through the typed error. Adds typed variants — `EnvironmentCreationFailed`, `EnvVarCreationFailed`, `StorageLinkFailed`, `SlugConflict` — replacing the `Other(String)` flatten. `SlugAlreadyExists` and the new `SlugConflict` both map to 409 (was 400). Slug detection now classifies via SQLSTATE `23505` plus textual fallbacks inside an `is_unique_violation` helper, so a concurrent create racing on the same slug surfaces as `SlugConflict` -> HTTP 409 instead of a generic 500 the frontend can't act on. Adds 5 tests: pure-unit classifier coverage, happy-path create with auto-environment, duplicate-name -> suffixed-slug, slug conflict -> 409, and validation rejection without leaving a phantom project. Integration tests skip gracefully without Docker per CLAUDE.md guidance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two user-reported bugs in one branch, plus tightens backend project-creation consistency and error mapping.
1. Deployment logs WebSocket dies on redeploy
web/src/components/deployments/DeploymentStages.tsxThe log stream gave up the moment it received any normal (1000) closure — exactly what happens when a redeploy churns the underlying log source — leaving the user staring at a frozen log pane. It also wiped state on every reconnect, so the partial logs they had were lost too.
lineso the backend's last-1000-lines replay after reconnect doesn't show up twice2. Single click → 15 duplicate projects
web/src/components/project/{ProjectConfigurator,ManualProjectConfigurator}.tsxSubmit was disabled via React state (
isSubmitting), but state updates aren't synchronous — fast double-clicks slipped multiple submissions through before the disabled prop landed. Adds auseRefsynchronous re-entry guard at the top ofhandleSubmitin both configurators.3. Backend project-create consistency
When
create_projectfailed any step after the project row was inserted (default env, env vars, storage links), the project row was left dangling. The handler returned 500 and the user ended up with a half-initialized project visible in their list. The real failure was buried insideProjectError::Other(format!(...))so the HTTP status was always 500 even when the cause was a 4xx.finalize_project_creation, on errorEntity::delete_by_idon the project row cascades to environments, env vars, links. No long-running transaction → no cross-service table locks.ProjectErrorvariants:SlugConflict,EnvironmentCreationFailed,EnvVarCreationFailed,StorageLinkFailed— all with structured fields perCLAUDE.mderror rules.SlugAlreadyExistsnow correctly maps to 409 (was 400). Concurrent slug races now surface asSlugConflict→ 409 instead of a generic 500.is_unique_violationhelper classifies via SQLSTATE23505+RecordNotInserted+ textual fallbacks.Test plan
Backend (
crates/temps-projects, 41 passed including 5 new):test_is_unique_violation_detects_record_not_inserted— pure unit, classifier coveragetest_create_project_succeeds_and_creates_default_environment— happy path + auto-envtest_create_project_with_duplicate_name_gets_suffixed_slug— duplicate name →name-<6char>test_create_project_slug_conflict_returns_typed_error— real PG unique-violation classified,Problemis HTTP 409test_create_project_rolls_back_on_invalid_storage_service— validation path leaves zero phantom projectsFrontend:
/projectslist)Verification commands:
```bash
cargo check --lib # 0 errors
cargo test --lib -p temps-projects # 41 passed
cd web && bunx tsc --noEmit # no new TS errors vs main
```