Skip to content

Restore launch UI state loading#119

Open
jamesfredley wants to merge 5 commits into
forge-ui-updatesfrom
fix/pr-111-restore-launch-ui
Open

Restore launch UI state loading#119
jamesfredley wants to merge 5 commits into
forge-ui-updatesfrom
fix/pr-111-restore-launch-ui

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR restores the launch UI behavior and production-style formatting after the PR #111 Recoil-to-Zustand migration.

It now covers the state loading fixes, async race guards, reset fallback, visual parity fixes, local proxy tooling, lint setup, and tests needed to verify the migrated launch page.

User-visible fixes

  • Launch form controls populate reliably on first render.
  • Default Included Features (8) renders again instead of falling back to an empty list.
  • Feature and default-feature requests rerun when Java version, servlet, GORM, or reloading values change.
  • The Features modal receives current feature data instead of stale or empty state.
  • Start Over no longer crashes when the select-options response omits optional option buckets.
  • Project Type and Java Version selects use the same standard underline styling and spacing as the current start.grails.org page.
  • Local browser QA works on Windows through the local version-feed proxy.

Root causes and fixes

1. Zustand initialization happened too late

PR #111 moved launch state from Recoil to Zustand. The migrated launch tree could render children before the Zustand store had been seeded with initial data, saved form data, deep-link values, and default values.

Fix

ApplicationState now initializes the store from useLayoutEffect and only renders children after initialization completes.

Why this works

The child tree no longer reads empty first-render values for fields such as appType, javaVersion, gorm, servlet, and reloading. It also avoids render-phase store mutation and removes the MUI controlled/uncontrolled warnings seen during browser QA.

2. Feature loads missed important query fields

The feature endpoints depend on more than the selected version and app type. The request URL also changes when these fields change:

  • javaVersion
  • servlet
  • gorm
  • reloading

Fix

useLoadFeaturesEffect now watches the full set of feature-query fields and reloads both available features and default included features when they change.

Why this works

After option defaults settle, the app requests features with the same query inputs production uses. This restores populated default features and feature modal data.

3. Stale async responses could overwrite current state

Option and feature loading are async. An older request could resolve after the visible form state changed and still write stale data into the shared Zustand store.

Fix

The option, feature, and default-feature loaders now use request ids plus request keys derived from the inputs that affect each request.

Why this works

A response updates state only if it still belongs to the latest request and still matches the current visible query inputs. Older successes and failures are ignored.

4. Reset assumed every option bucket was present

resetForm used option defaults directly. Copilot correctly pointed out that optional buckets such as servlet, GORM, reloading, or JDK version may be missing.

Fix

resetForm now loads options when needed, uses optional chaining for option buckets, and falls back to current store values when a bucket is absent.

Why this works

The reset path no longer crashes when an optional bucket is missing and still applies safe defaults for the fields that are available.

5. Select and form spacing regressed from production

The migrated MUI select controls were rendering with outlined-select sizing instead of the production standard underline layout. That made the form row and dropdown spacing differ from the current start.grails.org page.

Fix

This PR restores standard MUI selects, fixes select label/id wiring, keeps select values controlled with an empty-string fallback, and restores the form row spacing used by production.

Why this works

Browser QA confirmed the local form geometry matches the production page: select wrappers are 306 x 48, standard underline inputs are 306 x 32, and the Project Type menu opens at 306px wide with 36px options.

6. Local tooling and lint gates were unreliable

The local start command used POSIX-style environment variable syntax, the local feed URL was not explicit, the Express 5 wildcard route needed Express 5 syntax, and the migrated React code needed hooks linting to run consistently.

Fix

This PR:

  • Adds cross-env.
  • Changes start:local to use VITE_VERSION_FEED=http://localhost:8088/grails-version-feed.json.
  • Updates the dev proxy wildcard route to /{*splat}.
  • Registers eslint-plugin-react-hooks.
  • Enables react-hooks/rules-of-hooks as an error.
  • Enables react-hooks/exhaustive-deps as a warning.
  • Switches lint globs to Windows-safe double quotes.
  • Adds type: module for the ESM ESLint config.
  • Cleans up component lint findings surfaced by the lint gate.

Why this works

The local proxy flow is portable across Windows and POSIX shells, and lint now validates the migrated React hooks code instead of failing on tooling configuration.

Review feedback addressed

  • Fixed Copilot feedback for resetForm missing option buckets.
  • Fixed Copilot feedback for ApplicationState render-phase side effects.
  • Replied to both Copilot threads and marked them resolved.
  • Rewrote the PR commit metadata to remove the disallowed Sisyphus attribution while keeping Assisted-by: opencode:gpt-5.5.

What changed

  • Restored safe first-render launch state initialization for the Zustand migration.
  • Added stale-response protection to async option and feature loaders.
  • Reloaded feature data when feature-query inputs change.
  • Made reset behavior safe when optional option buckets are absent.
  • Restored launch form select/dropdown visual parity with start.grails.org.
  • Strengthened launch and feature-loading tests.
  • Fixed local start/proxy behavior for browser parity testing.
  • Restored a usable lint gate for the migrated React code.

Verification

Commands run successfully:

npm ci
npm run lint
npm test
npm run build
.\gradlew.bat --no-daemon build

Browser QA was run through the local proxy path with npm run start:local:

  • Launch page loaded with populated controls.
  • Default Included Features (8) rendered.
  • Features modal opened with populated feature groups.
  • Searching for postgres filtered the modal to PostgresSQL.
  • Selecting PostgresSQL updated Additional Selected Features (1).
  • Local version feed and Grails API calls returned 200.
  • Console had 0 warnings and 0 errors after the final fixes.
  • Select and form geometry matched the current start.grails.org layout.

Scope notes

  • The local proxy and start:local changes are dev-tooling fixes only.
  • The async request guards do not change API contracts. They only prevent obsolete responses from mutating current UI state.
  • react-hooks/exhaustive-deps is enabled as a warning so this PR restores lint visibility without expanding into a broad hooks cleanup.
  • The PR history now contains five focused commits with only the allowed Assisted-by trailer.

Copilot AI review requested due to automatic review settings June 11, 2026 05:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores the Launch UI’s initial state/loading behavior after the dependency/Zustand migration regressions (PR #111), ensuring options/features populate correctly and dev/local tooling works consistently.

Changes:

  • Add request-staleness guards to async options/features loads and reload features when query-driving fields change.
  • Restore synchronous-ish store initialization for the Launch UI and tighten “Start Over” reset behavior.
  • Fix local dev start/proxy/version-feed behavior and restore/enhance lint + test coverage expectations.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
dev-proxy-server/src/commands.js Updates the version server route for the Express 5 routing/wildcard behavior.
app/launch/src/state/store.js Adds request IDs/keys to prevent stale async updates; updates reset and features-loading behavior.
app/launch/src/state/ApplicationState.jsx Changes how initial state is applied to the Zustand store at app start.
app/launch/src/components/FeatureSelector/FeatureSelector.jsx Avoids recreating the keydown handler each render by memoizing correctly.
app/launch/src/components/ErrorView/ErrorView.jsx Import ordering cleanup for MUI components.
app/launch/src/components/Application/tests/useAvailableFeatures.test.jsx Extends SDK mock + asserts the rendered features list content.
app/launch/src/components/Application/tests/Application.test.jsx Strengthens assertions to verify key UI elements render.
app/launch/package.json Switches to ESM package mode and improves start:local portability + lint script quoting; adds deps for cross-env and hooks linting.
app/launch/package-lock.json Lockfile updates for added dependencies.
app/launch/eslint.config.js Enables react-hooks lint rules and adjusts no-unused-vars options for catch handling.
Files not reviewed (1)
  • app/launch/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/launch/src/state/store.js Outdated
Comment thread app/launch/src/state/ApplicationState.jsx
Initialize the Zustand store before paint, reload feature data when feature-query fields change, ignore stale async loader responses, and keep reset defaults safe when option buckets are missing.

Assisted-by: opencode:gpt-5.5
Assert that the launch form and action row render instead of only checking for a defined test container.

Assisted-by: opencode:gpt-5.5
Reorder MUI imports and memoize the feature modal keyboard handler factory result so lint can run cleanly.

Assisted-by: opencode:gpt-5.5
Make local launch scripts cross-platform, wire the React hooks lint plugin, and update the Express 5 version feed route used by local browser QA.

Assisted-by: opencode:gpt-5.5
Keep the launch form children hidden until the Zustand store has been initialized, restore production-style standard selects, and preserve the form row spacing used by start.grails.org.

Assisted-by: opencode:gpt-5.5
@jamesfredley jamesfredley force-pushed the fix/pr-111-restore-launch-ui branch from 6b45829 to c3e90c9 Compare June 11, 2026 12:24
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.

2 participants