chore!: upgrade to React 19, Chakra v3, stac-react v1#72
Merged
Conversation
…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>
…mproved authentication handling
…clarity and performance
…nd improve error handling
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>
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>
Deploying stac-manager with
|
| 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 |
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>
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.
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
mainwas 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:
react/react-dom@chakra-ui/react@developmentseed/stac-react@devseed-ui/collecticons-chakraframer-motionThese 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 (
isLoadinginstead of the oldstateenum, callableoptionsfor token rotation).What changed
extendTheme→createSystem+defineConfig; custom palettes wired into Chakra v3'scolorPalettesemantic-token system; recipe overrides for button/card/field/menu/select with full slot lists;ColorModeScriptdropped (no dark mode in use).colorScheme→colorPalette,isAttached→attached,spacing→gap,isDisabled→disabled), removed-export swaps (Divider→Separator,useToast→createToaster+<Toaster/>,Input*Element→InputGroup), andforwardRefboilerplate dropped per React 19's ref-as-prop.useCollectionsrewritten onto the shared authedApiviauseApi()(themain.tsxbridge is the single source);AbortControlleradded.structuredClonepolyfill for jsdom;eslint-plugin-react-hooksenabled; root jest now ignores/e2e/and gitignored/.worktrees/.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),
CollectionDetailitems-list not fetching on direct page load (stac-react v1 resets internal state when itsStacApirebuilds during OIDC token load), toastwidth:0invisibility, 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
no-explicit-any+ 3exhaustive-depson intentional memos) ✅/e2e/(run vianpm run e2e) and gitignored/.worktrees/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:
Out of scope (follow-ups)
PluginBoxrebuildseditSchema(values)per keystroke;useRenderKeyfightsFastField;usePluginsre-resolves on everydatachange.alt, NotificationButton disclosure semantics, "Spacial extent" typo.Companion / plan
docs/plans/2026-05-25-react-19-chakra-v3-migration.md.