Skip to content

chore!: upgrade to React 19, Chakra v3, stac-react v1#72

Merged
alukach merged 58 commits into
mainfrom
chore/react-19-upgrade
Jun 11, 2026
Merged

chore!: upgrade to React 19, Chakra v3, stac-react v1#72
alukach merged 58 commits into
mainfrom
chore/react-19-upgrade

Conversation

@alukach

@alukach alukach commented May 25, 2026

Copy link
Copy Markdown
Member

Important

AI-driven upgrade. The code was generated by Claude Code (Opus 4.7/4.8) under human direction, reviewed at the diff level, and smoke-tested against the dev server throughout. Automated gates (tsc, jest, lint, parcel build) pass, and visual/runtime parity with main was checked side-by-side on the collection list/detail, item detail, and form pages. Treat it as code that's been read, exercised, and signed off — not raw LLM output.

Summary

Upgrades the core UI + data stack to current majors:

Dep Was Now
react / react-dom 18.3 19.2
@chakra-ui/react 2.8 3.35
@developmentseed/stac-react 0.1.0-alpha.10 1.0.0-alpha.3
@devseed-ui/collecticons-chakra 3.0 4.0
framer-motion 10.16 12.40

These are interlocked by peer deps: stac-react v1 requires React 19; Chakra v2 refuses React 19 (→ v3); collecticons-chakra v3 peers Chakra v2 + framer-motion v10 (→ v4); framer-motion v10 peers React 18 (→ v12). Pick one, pick them all. End state: a single React 19 and framer-motion 12 across the tree, theme + components on Chakra v3 idioms, and stac-react v1's typed API (isLoading instead of the old state enum, callable options for token rotation).

What changed

  • Provider / themeextendThemecreateSystem + defineConfig; custom palettes wired into Chakra v3's colorPalette semantic-token system; recipe overrides for button/card/field/menu/select with full slot lists; ColorModeScript dropped (no dark mode in use).
  • Component sweep — namespace migrations (Card/Menu/Tag/Avatar/Field/RadioGroup/Table/Popover/List), prop renames (colorSchemecolorPalette, isAttachedattached, spacinggap, isDisableddisabled), removed-export swaps (DividerSeparator, useToastcreateToaster + <Toaster/>, Input*ElementInputGroup), and forwardRef boilerplate dropped per React 19's ref-as-prop.
  • stac-react v1 — hook consumers updated for the new shape; useCollections rewritten onto the shared authed Api via useApi() (the main.tsx bridge is the single source); AbortController added.
  • Tests / tooling — Chakra v3 test wrappers, snapshots re-baselined, structuredClone polyfill for jsdom; eslint-plugin-react-hooks enabled; root jest now ignores /e2e/ and gitignored /.worktrees/.
  • Cleanup — 9 dead files deleted, duplicate type defs removed.

Bug fixes found while migrating

Rules-of-Hooks violations (hooks after a conditional throw), JsonEditor stale closure (ref-stash) and zero-width blank render, restored visual parity (Card padding, Heading sizes, Tag colors, semantic-token borders, container max-width tokens removed in v3), CollectionDetail items-list not fetching on direct page load (stac-react v1 resets internal state when its StacApi rebuilds during OIDC token load), toast width:0 invisibility, controlled radio/checkbox groups, required-field indicators (v3 dropped the auto asterisk), Form/JSON toggle data-loss + id-field survival, outline-button active state, log-out on token-refresh failure, ItemMap HTTP 204 handling, and notification title precedence.

Gates

  • tsc: clean across client / data-core / data-widgets / data-plugins ✅
  • lint: 0 errors (warnings only: no-explicit-any + 3 exhaustive-deps on intentional memos) ✅
  • tests: 96/96, 13 suites ✅ — root jest skips Playwright /e2e/ (run via npm run e2e) and gitignored /.worktrees/
  • parcel build: succeeds ✅

Smoke test before merge

Automated gates can't confirm Chakra v3's restructured DOM looks right or that namespace APIs route events correctly. Quick walk-through:

  • Collection list — cards/thumbnails (or SVG placeholder), brand-tinted tags, expected heading/body sizes
  • Collection detail — items load on a direct page load
  • Item detail — map renders, COG preview overlay loads within the item's bbox
  • User menu — Avatar sits inside the Logout button, no DOM-nesting warnings
  • CollectionForm — a submit error shows a toast; a 400 validation shows the bell badge; all widgets render (inputs, selects, radios, checkboxes, tagger, JSON editor, nested arrays)

Out of scope (follow-ups)

  • PerfPluginBox rebuilds editSchema(values) per keystroke; useRenderKey fights FastField; usePlugins re-resolves on every data change.
  • A11y — missing label associations on CollectionList search/filter, some missing alt, NotificationButton disclosure semantics, "Spacial extent" typo.

Companion / plan

  • collecticons cleanup (nested-svg, dup deps): chore: cleanup v4 collecticons#30 — non-blocking.
  • Migration plan (now mostly historical): docs/plans/2026-05-25-react-19-chakra-v3-migration.md.

alukach and others added 20 commits May 25, 2026 11:31
…akra 4

Cross-cutting dep bump in preparation for the v3 + 19 migration. Code
still uses Chakra v2 APIs so tests fail — addressed in subsequent
commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- extendTheme → createSystem + defineConfig
- ChakraProvider theme={} → ChakraProvider value={}
- styles.global → config.globalCss
- component overrides → recipes / slotRecipes
- Drop ColorModeScript (no light/dark mode in use)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename props: colorScheme→colorPalette, isAttached→attached,
  noOfLines→lineClamp, spacing→gap, isDisabled→disabled
- Restructure namespace components: Card, Menu, Table, Avatar, Tag,
  RadioGroup, ProgressCircle, Popover, List
- Replace removed components: Divider→Separator,
  FormControl/Label/ErrorMessage→Field.*, Input*Element→InputGroup
  start/endElement, Radio→RadioGroup.Item, CircularProgress→
  ProgressCircle.*, UnorderedList/ListItem→List.*, MenuButton/List→
  Menu.Trigger/Content, Select→NativeSelect.*
- forwardRef now imported from react
- ChakraProps → HTMLChakraProps
- useDisclosure().isOpen → .open
- Fade → framer-motion (motion.div + AnimatePresence)
- variant="link" not used; soft-outline cast to 'outline' to satisfy
  v3 button recipe types until variant typing is augmented properly
- Drop leftIcon/rightIcon on Button/IconButton; render icons as
  child nodes
- Update useItem/useCollection consumers: hook now exposes
  isLoading instead of state
- Drop dead pages/ItemList tree (was importing missing modules and
  not wired into router)
- Fix rollup-plugin-typescript2 include glob (its default pattern
  `*.ts+(|x)` no longer matches under current pluginutils, blocking
  all subpackage builds)

Note: useToast call sites in pages/CollectionForm/index.tsx and
components/Notifications.tsx are pointed at a tiny local shim
(src/_legacy/useToast.ts) so parcel can resolve the import while
B-7 rewires the v3 toaster singleton + <Toaster /> mount. The
shim's methods are no-ops; toasts will silently no-op until B-7.

The 4 remaining tsc errors are in test files (ChakraProvider now
requires a `value` prop). Those belong to B-8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace v2 useToast hook with v3 createToaster singleton +
  <Toaster /> mount in main.tsx
- Custom NotificationBox rendering moved from per-toast render
  callback to the <Toaster /> render function, switched on
  meta.kind
- Standard toasts (CollectionForm submit/update/dismiss) mapped
  status->type, close->dismiss, closeAll->dismiss()
- Delete the temporary _legacy/useToast shim

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…line snapshots

- Pass `value={defaultSystem}` to all ChakraProvider test wrappers
  (Chakra v3 requires it)
- Re-baseline plugin-box and array-fieldset snapshots tied to Chakra v3's
  DOM emission (class name churn, removal of Box's css-0 wrapper divs,
  Button no longer wraps icons in chakra-button__icon span)
- Polyfill globalThis.structuredClone in jest-setup.ts so Chakra v3's
  recipe internals can run under jest-environment-jsdom@29 (jsdom 20)
- Rework data-widgets/utils.test.ts to snapshot rendered DOM instead of
  the raw React element (pretty-format@29's ReactElement plugin doesn't
  recognise React 19's Symbol(react.transitional.element))

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CollectionDetail: restore total-count badge by reading numberMatched
  off the untyped STAC response (stac-react v1 dropped the field from
  its typed signature but most servers still emit it); hide badge when
  unavailable instead of showing misleading page-count
- UserInfo: wrap Avatar.Root with asChild + span to avoid invalid
  div-inside-button DOM nesting
- CollectionForm: preserve structured error.detail via JSON.stringify
  fallback instead of String() which produced "[object Object]"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address remaining lint errors after the Chakra v2 → v3 / React 19 sweep:
- Add explicit displayName to forwardRef'd components in client, data-core,
  and data-widgets (react/display-name)
- Drop now-meaningless eslint-disable comments whose target rules are no
  longer registered (react-hooks/exhaustive-deps) or no longer firing
  (@typescript-eslint/no-non-null-assertion)
- Extract NativeSelect onChange handler to avoid jsx-curly-newline /
  prettier circular conflict
- Replace `children:` prop literal in utils.test.ts createElement call
  with a typed ComponentProps variable to satisfy both react/no-children-prop
  and the React 19 ChakraProvider TS overload
- Auto-applied prettier formatting across the affected files

No production logic changed; lint clean across all 4 packages,
109/109 tests passing, build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The mount-only useEffect captured onChange/onLoad from the first render
and wired them into the JSONEditor instance. If a parent re-renders
with a new callback identity, edits would route to the stale closure.
Route callbacks through refs that are updated every render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WidgetSelect and usePaginateHook called hooks after `throw` statements,
violating the Rules of Hooks. If a transition between throwing and
non-throwing inputs occurred without an unmount, React would crash
with "Rendered more hooks than during the previous render". React 19's
stricter dev-mode checks will flag this.

WidgetSelect: extracted an inner component so the outer one does the
prop validation (throws) and the inner one calls hooks unconditionally.
usePaginateHook: hooks moved above throws. The accompanying test cases
that previously asserted throws by calling the hook bare now use
renderHook so the hook runs inside a proper React render context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Avatar.Root asChild<span> wrapping defeated Avatar's internal slot
context. Chosen approach: render the Avatar as a sibling of the Logout
Button inside a Flex container, so the Avatar keeps its native <div>
root and the Button keeps native <button> semantics — no div-inside-
button DOM violation, slot context intact, keyboard accessibility
preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- RequireAuth: colorScheme='primary' was a v2 leftover; v3 wants
  colorPalette. The Login button now picks up the primary palette.
- WidgetRadio: copy-paste error in the allowOther validation error
  message referenced WidgetCheckbox; fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ndicator

- Slot recipes (menu/select/field) now declare the full v3 anatomy
  via {menu,select,field}Anatomy.keys() instead of partial lists. The
  partial-list trap silently strips styling from omitted slots — same
  pattern we hit and fixed for `card` earlier.
- ButtonGroup attached-button negative-margin rules now target the v3
  Group [data-attached] attribute selector instead of the dead v2
  `.chakra-button__group` class. The 2px overlap on attached outline
  buttons now actually renders.
- <Toast.Indicator /> mounted in the Toaster render function so
  type='loading' / 'success' / 'error' / 'warning' / 'info' actually
  display their indicator (v3 no longer auto-renders the icon).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…auth wiring

- Replace the hand-rolled `debounce` (recreated a fresh closure per
  call so it never debounced) with a straight AbortController-gated
  fetch. The previous implementation also guarded the fetch effect on
  `!collections`, so offset/limit changes never refetched once data
  had loaded — fixed by depending on offset/limit directly and aborting
  the in-flight request on dep change or unmount.
- Read the configured Api via useApi() instead of re-deriving an
  Authorization header from useAuth().token. The StacApiAuthBridge in
  main.tsx is the single source for auth-to-API wiring; this hook now
  consumes it like the rest of the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…variant, drop fontSize='md' on Text

These previously-uncommitted fixes from the in-flight visual-regression
sweep were left in the working tree; landing them now.

- SmartLink: the v3 asChild path was `<ChLink asChild><Link to={to}/></ChLink>`,
  where the self-closing Link had no children and Chakra v3's Slot wiring
  doesn't merge children from the wrapper. Result: every collection card
  rendered an empty `<a class="chakra-link"></a>`. Destructure children
  out and pass them through both branches.
- ItemCard: B-4 sweep changed `variant='filled'` -> `variant='subtle'`,
  but the theme still defines a custom `filled` variant with
  `background: 'base.50'`. Restore with a cast (Chakra v3 type only
  knows about default variants).
- CollectionDetail / ItemDetail / ItemCard: B-4 mechanically converted
  v2's `<Text size='md'>` (silently ignored in v2) to v3's `<Text
  fontSize='md'>` (actively 20px). v2 rendered these at inherited body
  size (16px). Drop the prop so they inherit again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MainNavigation: List.Root needed explicit flexDirection='row'. The
  v3 list recipe defaults to flexDirection: column, so passing only
  display='flex' kept the Browse/Create items stacked vertically.
- UserInfo: restore the v2 visual (Avatar inset within the Logout
  button) instead of the sibling-Flex restructure. Use Avatar.Root
  asChild + <span> so the Avatar renders as phrasing content (legal
  inside <button>); the recipe context + Ark UI state machine still
  apply because withProvider provides them independently of the
  rendered tag (verified empirically — Image gets data-state="hidden",
  Fallback "visible"). Avatar sized via size='xs' (32×32, 12px font),
  Button pl='2px' to keep the leading-avatar layout tight.
- CollectionDetail: items now fetch on direct page load. stac-react
  v1's useStacSearch internally calls M() (state reset) whenever its
  StacApi instance changes, which fires during the OIDC token-load
  remount on direct loads. The previous effect deps ([collectionId,
  setCollections]) were stable, so the reset wiped `collections`
  without ever being restored, leaving the query disabled and no
  /search request firing. New effect depends on `collections` and
  re-applies [collectionId] whenever it gets cleared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files deleted (verified no external imports):
- pages/ItemForm/* — unrouted page importing non-existent components
- pages/ItemDetail/{PropertyList,TableValue,Roles}.tsx — no consumers
- components/Pagination.tsx — only referenced in commented-out blocks
- utils/usePaginateHook.{ts,test.ts} — only used by deleted Pagination
- components/auth/Callback.tsx — OIDC callback handled inline in auth/Context
- hooks/usePrevious.tsx — re-exported but never imported anywhere

Also dropped from types/index.ts:
- LoadingState (still locally redefined where actually needed)
- Property / PropertyGroup / PropertyItem (only used by deleted PropertyList)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hand

- data-core: widget-renderer.tsx had a local WidgetProps interface
  identical to the exported one in ./types. Replace with an import so
  external consumers and the renderer itself share one definition.
- pages/{CollectionList,CollectionDetail,ItemDetail}: remove
  commented-out v2 JSX blocks (search-params pagination, old Add-new
  / Edit Button placeholders with v2 colorScheme + leftIcon). These
  referenced symbols that no longer exist after the migration or were
  marked for follow-up that's now resolved differently.
- components/Notifications.tsx: split `borderBottom='1px solid'` +
  `borderColor='base.100'` into individual border-bottom-{width,style,
  color} props. The shorthand was resetting all four sides via CSS
  cascade order; same trap we fixed in the theme's button/input recipes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React 19's ref-as-prop simplification makes forwardRef + manual
displayName ceremony unnecessary for components that don't take refs
externally. Audited: no consumer passes ref to any of these.

- data-widgets/lib/components/elements.tsx: Fieldset, FieldsetHeader,
  FieldsetBody, FieldsetFooter, FieldLabel, FieldIconBtn,
  FieldsetDeleteBtn — 7 plain pass-through wrappers, now plain function
  components. Removed 7 displayName assignments (function name suffices).
- data-core/lib/components/error-box.tsx: ErrorBox same treatment.
- client/src/components/auth/ButtonWithAuth.tsx,
  MenuItemWithAuth.tsx: same.
- client/src/components/InnerPageHeader.tsx: InnerPageHeader accepts
  `ref` as a regular prop (consumed by InnerPageHeaderSticky for its
  IntersectionObserver). InnerPageHeaderSticky drops the merged-ref
  dance since no external consumer ever passed it a ref — just owns
  the local ref directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .env file slipped into 5aa4a44 as part of the dead-file removal
commit (untracked → staged → committed alongside the intentional
deletions). Nothing sensitive was in it, but it shouldn't be in the
repo. Removing from tracking and adding to .gitignore so future
.env / .env.local / .env.*.local don't accidentally land again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alukach alukach requested review from AliceR and danielfdsilva May 26, 2026 05:25
@alukach alukach changed the title Migrate to React 19 + Chakra UI v3 Upgrade to React 19, Chakra v3, stac-react v1 May 26, 2026
@alukach alukach marked this pull request as ready for review May 26, 2026 05:27
@alukach alukach marked this pull request as draft May 26, 2026 06:45
The Box wrapping jsoneditor only set `h='20rem'` with no explicit width,
so when placed inside a Chakra Field.Root (or any container that doesn't
stretch its children), it sized to zero width. Ace populated the session
correctly but couldn't lay out any lines into a 0-width container, so
the visible text-layer stayed empty.

Also adds a Playwright functional test and an isolated `/test-jsoneditor`
harness route, since the existing unit tests mock jsoneditor entirely
and cannot catch real Ace rendering bugs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
alukach and others added 25 commits June 8, 2026 21:09
Chakra v2's FormControl auto-rendered the asterisk for required fields; v3's
Field does not — it needs an explicit <Field.RequiredIndicator/>. The scalar
widgets (input, number, select, radio, checkbox, tagger) passed
required={isRequired} to Field.Root but never rendered the indicator, so
required fields silently lost their `*` (array fields kept theirs via the
hand-rolled ArrayFieldset marker, making the inconsistency visible). Add the
indicator inside each Field.Label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Formik leaves an untouched field undefined, so passing value={value} straight
to v3's RadioGroup.Root / CheckboxGroup mounted them uncontrolled and flipped
to controlled on the first change — React's controlled/uncontrolled warning and
a possible reset. Coerce to null (radio) and [] (checkbox), matching how
WidgetInput already guards with `value ?? ''`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The plugin-resolution effect awaited pl.init() and then unconditionally called
setReadyPlugins, with no cancellation. If `plugins` changed (or the component
unmounted — incl. StrictMode's double-invoke) mid-flight, a stale resolve could
overwrite newer plugins or set state after unmount. Add a cancelled flag with
an effect-cleanup so only the latest in-flight load commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Form/JSON view toggle marks the selected view with data-active='', but
v3's outline button recipe (and our override) had no active/selected style, so
the active button looked identical to the inactive one. Add an &[data-active]
rule (v3's _active condition covers [data-active]) tinting the selected button
with colorPalette.subtle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-on to the container.* -> v3 sizing fix: ErrorBox's maxW now resolves to
a real value (3xl) instead of the ignored `container.md`, changing its emotion
class hash. Only the generated class name changed; structure is identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ule)

Two error-level lint failures predating the should-fix commits:
- UserInfo.tsx imported `Flex` but stopped using it after the Avatar/Button
  restructure — drop the import.
- json-jsoneditor.tsx had `// eslint-disable-next-line react-hooks/exhaustive-deps`,
  but eslint-plugin-react-hooks isn't installed/configured in this repo, so the
  directive references an undefined rule and eslint errors on it. Remove the
  directive (the rule isn't enforced); keep the explanatory comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`npm test` (root jest) had its testPathIgnorePatterns commented out, so it
swept in packages/client/e2e/*.spec.ts (Playwright specs jest can't execute)
and, locally, the gitignored .worktrees/ repo copies — the latter also collide
jest's haste map (duplicate module names). Enable testPathIgnorePatterns for
/e2e/ and /.worktrees/, and add modulePathIgnorePatterns for the worktree copies
so module resolution ignores them too. Playwright specs continue to run via
`npm run e2e`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flat config had react-hooks rules stubbed out but the plugin was never
installed — so Rules-of-Hooks and dependency-array bugs shipped silently, a real
gap for a React-19 codebase this hook-heavy. Install the plugin and turn on the
rules the author had commented: rules-of-hooks as error (0 violations — the
migration's conditional-hook fixes hold), exhaustive-deps as warn.

Restores the now-valid exhaustive-deps suppression on JsonEditor's intentional
mount-only effect, and adds the stable editorRef to the value-sync effect's deps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The editorData useMemo read stacData but omitted it from its deps. It worked
only incidentally (every setStacData also produced a fresh formData object that
re-ran the memo); a latent stale-render trap if formData ever became
referentially stable. Surfaced now that exhaustive-deps is enabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`{title || props.type === 'validation-error' ? 'Validation error' : 'Error'}`
binds as `(title || type==='validation-error') ? ...`, so every titled
notification (the 403 "Forbidden" and generic "Error N" cases) rendered the
literal "Validation error" and the real title was never shown. Parenthesize so
a present title wins and the type-based label is only the fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The visibilitychange top-up, accessTokenExpired backstop, and 401
self-heal all go through react-oidc-context's wrapped signinSilent,
which flips isLoading for the duration of the token request. Both the
main.tsx provider gate and the App.tsx route gate key off that flag, so
every fallback refresh unmounted the whole tree to a spinner and back —
wiping in-progress form state exactly when recovery was supposed to be
seamless. Exempt the signinSilent navigator so isLoading only reports
initial session resolution and explicit redirect navigations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ark checkboxes consume the surrounding Field context, so multiple
Checkbox.Roots under one Field.Root all inherited the Field's ids —
duplicate DOM ids, every label's htmlFor pointing at the first input
(clicking option B toggled option A) — and the Field's required flag
got stamped onto every hidden input, making native validation demand
all options be checked. Use the Fieldset group pattern instead, which
provides no checkbox-consumable context, and render the required
indicator on the legend. Adds regression tests for id uniqueness,
label association, and required propagation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Playwright webServer hard-failed without a .env (tasks/server.mjs
exits on missing REACT_APP_STAC_API, and the posthtml build needs
APP_TITLE/APP_DESCRIPTION), and its port wiring was untrustworthy: the
dev server scans 9000-9999 for a free port while Playwright polls a
hardcoded one, so the two could silently diverge — or reuse a stale
server on 9000. Provide env defaults in the webServer block (the
harness pages never call the STAC API; exported real values still win)
and teach the dev server to honor an explicit PORT so it binds exactly
where Playwright is polling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every refresh failure funneled into removeUser(), so a plain network
error — e.g. the visibilitychange top-up firing the instant a
hibernated tab becomes visible, before Wi-Fi reconnects after laptop
wake — destroyed a session whose refresh token was still perfectly
valid. Only drop local user state when the IdP definitively rejects
the refresh (an OAuth ErrorResponse such as invalid_grant); transient
failures now just log and leave the session for the next attempt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Parallel 401s through Api.fetch, or a 401 retry overlapping the
visibilitychange/accessTokenExpired handlers, each fired their own
signinSilent. Under IdP refresh-token rotation with reuse detection,
racing two refresh_token grants can revoke the entire session. Dedupe
through a ref so concurrent callers await the same refresh promise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
closable defaults to undefined, so rendering CloseTrigger on
closable !== false put a close button on the infinite-duration
'Creating/Updating collection...' loading toasts (v2's isClosable was
opt-in). Dismissing one mid-flight left the completion update targeting
a removed toast, so the success confirmation never appeared. Render the
close button only when closable is set — the success toasts already
pass closable: true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chakra v3 no longer peers on either, and no source file imports
@emotion/styled anywhere or framer-motion outside the client app. For
the publishable data-core/data-widgets packages the leftover entries
forced downstream consumers to install two unused peers — framer-motion
had even been bumped to v12 while dead. The client keeps framer-motion
(App.tsx loading transition) and everything keeps @emotion/react, which
Chakra still peers on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TestJsonEditor self-describes as a dev-only Playwright harness but was
routed unconditionally, shipping in the production route table. Gate it
on NODE_ENV; the e2e suite runs against the dev server, so the spec
still reaches it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
useStacSearch wipes its state when its StacApi rebuilds (token
rotation), and the auto-resubmit lands back on results page 1 — but the
locally tracked page number kept its old value, showing e.g. 'Showing
page 3 of N' over page-1 results. Reset it alongside setCollections,
which also covers navigating to a different collection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stac-react hooks don't 'defer their requests until the provider
appears' — a hook rendered without StacApiProvider throws, since the
only QueryClientProvider lives inside it. The arrangement is safe
solely because App.tsx gates every route behind the same auth isLoading
flag; document that load-bearing coupling instead of the wrong claim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reload() fetched without an abort signal, so a reload racing the
effect-driven fetch was last-resolved-wins and could set state after
unmount. Route every request through a single controller ref: starting
a new request aborts the in-flight one, and the effect cleanup now
covers reload-initiated requests as well.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Field.Label's htmlFor pointed at a control id none of these widgets
rendered, so clicking the label did nothing (pre-existing in v2, same
class of issue as the checkbox group fix). Radio groups now use
Fieldset/Legend — a single label has nothing valid to point at in a
group of controls — and select/tagger share a useId between Field.Root
and react-select's inputId so the label focuses the actual input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
variant={'soft-outline' as 'outline'} and {'filled' as 'subtle'}
defeated recipe typing — a renamed or removed theme variant would still
compile. Run @chakra-ui/cli typegen against the client theme on
postinstall so the generated recipe unions include the custom variants,
and drop the casts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PUBLIC_URL=/ produced protocol-relative //meta/... URLs for the header
icon, favicons, and og:image, which browsers resolve against a host
named "meta". Joining was only correct for absolute bases without a
trailing slash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploying stac-manager with  Cloudflare Pages  Cloudflare Pages

Latest commit: e420ce4
Status: ✅  Deploy successful!
Preview URL: https://9517a272.stac-manager.pages.dev
Branch Preview URL: https://chore-react-19-upgrade.stac-manager.pages.dev

View logs

The hardcoded http:// tiler URL is mixed content on https deployments —
Chrome silently upgrades it, Firefox and Safari block or downgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alukach alukach merged commit af5b71d into main Jun 11, 2026
5 checks passed
@alukach alukach deleted the chore/react-19-upgrade branch June 11, 2026 19:18
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