Upgrade to React 19, Chakra v3, stac-react v1#72
Draft
alukach wants to merge 24 commits into
Draft
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
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
This upgrade was AI-driven. The code changes — dep bumps, theme/recipe rewrite, component sweep, test wrapper updates, bug fixes — were generated by Claude Code (Opus 4.7) under human direction. Each step was reviewed at the diff level and the running app was manually smoke-tested against the dev server throughout. Automated gates (tsc, jest, lint, parcel build) all pass; visual/runtime parity against
mainwas verified side-by-side on the collection list, collection detail, item detail, and form pages. Treat this PR as machine-generated code that has been read, exercised, and signed off on — not as untouched LLM output.Summary
Upgrades
stac-managerto the current major versions of its core UI and data dependencies:react/react-dom@chakra-ui/react@developmentseed/stac-react@devseed-ui/collecticons-chakraframer-motionThe upgrade was an interlocked set: every one of these libraries had peer-dep constraints that pulled the others along. stac-react
^1.0.0-alpha.3peer-requires React 19. Chakra v2 peers refuse React 19, so we needed Chakra v3. collecticons-chakra v3 peers on Chakra v2 + framer-motion v10, so we needed collecticons-chakra v4. Framer Motion v10 peers on React 18, so it had to move to v12. Once you pick one, you pick them all.End state: a single React 19 across the tree, a single framer-motion 12, theme + components on Chakra v3 idioms, and adoption of stac-react v1's improved API (typed
isLoadinginstead ofstateenum, callable-optionsfor token rotation).What that actually involved
Mechanical migration in order of landed commits:
--legacy-peer-deps/ overrides.extendTheme→createSystem+defineConfig. Custom palettes (primary,base,danger, …) participate in Chakra v3'scolorPalettesemantic-token system; neutrals (base,gray,surface) use the gray-style mapping (lighterborder/muted), chromatic palettes use the standard. Recipe overrides forbutton,card,field,menu,selectwith full anatomy slot lists.ColorModeScriptdropped (no light/dark mode in use).Card.Root/Header/…,Menu.Trigger/Content/Item,Tag.Root/Label,Avatar.Root/Image/Fallback,Field.Root/Label/ErrorText,RadioGroup,Table,Popover,List), prop renames (colorScheme→colorPalette,isAttached→attached,noOfLines→lineClamp,spacing→gap,isDisabled→disabled), removed-export replacements (Divider→Separator,MenuButton/MenuList→Menu.Trigger/Content,useToast→createToaster+<Toaster />,Input*Element→InputGroupstartElement/endElement,useDisclosure().isOpen→.open,forwardReffromreact,ChakraProps→HTMLChakraProps).useCollection/useItem/useStacSearchconsumers updated for the new hook shape.useCollectionsrewritten to consume the shared authedApiviauseApi()instead of duplicating token-options wiring (the bridge inmain.tsxis the single source). AddedAbortControllerand dropped a fake hand-rolled debounce that wasn't actually debouncing.<ChakraProvider value={defaultSystem}>. Snapshots re-baselined.structuredClonepolyfill added tojest-setup.ts(Chakra v3 needs it; jsdom@20 doesn't ship it).throw), JsonEditor stale-closure fix via ref-stash, restored visual parity (Card padding, Heading size mapping, Tag colors, Login button colorPalette, semantic-token border shades),CollectionDetailitems-list fetch on direct page load (stac-react v1's internal state-reset onStacApirebuild was wipingcollectionsmid-init).forwardRefboilerplate dropped from 11 pure pass-through wrappers (React 19 ref-as-prop). Old commented-out v2 JSX blocks removed.Gates
usePaginateHook.test.tsremoval)client,data-core,data-widgets,data-plugins✅no-explicit-anywarnings only) ✅Visual / runtime smoke test before merge
Automated gates can't verify Chakra v3's structurally-different DOM looks right or that namespace APIs route events correctly. Recommended walk-through:
useStacSearchresets internal state when the underlyingStacApirebuilds during OIDC token load)Companion PR
@devseed-ui/collecticons-chakra@4.0.0is already on npm and is what this branch consumes. developmentseed/collecticons#30 in the collecticons repo lands two minor cleanups noticed while preparing this consumer (nested-svg in the factory, duplicate deps/peerDeps) — not blocking, land at leisure.Plan doc
Migration plan with task-level detail (now mostly of historical value):
docs/plans/2026-05-25-react-19-chakra-v3-migration.md.Out of scope (deferred to follow-ups)
PluginBoxrebuildsplugin.editSchema(values)on every keystroke;useRenderKeyfightsFastFieldwith deep-compare + remount;usePluginsre-resolves on everydatachange. Real perf wins available; don't fit in this PR.alton a few<Image>/<Avatar.Image>, NotificationButton disclosure semantics, "Spacial extent" typo.ItemMapCOG preview — tile service returning HTTP 204 for empty areas; maplibre logs decode errors on the empty body. Pre-existing; not migration-caused.