Skip to content

feat: hierarchy with ui#15769

Open
JarrodMFlesch wants to merge 277 commits intomainfrom
folder-field-column-drawer
Open

feat: hierarchy with ui#15769
JarrodMFlesch wants to merge 277 commits intomainfrom
folder-field-column-drawer

Conversation

@JarrodMFlesch
Copy link
Contributor

No description provided.

JarrodMFlesch and others added 30 commits January 19, 2026 08:48
- Create packages/payload/src/preferences/keys.ts with PREFERENCE_KEYS constants
- Replace all preference key strings with imported constants
- Rename sidebar preference key from 'sidebar' to 'nav-sidebar-active-tab'
- Update all files using preferences to import PREFERENCE_KEYS

This prevents typos and makes preference keys more discoverable and maintainable.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove CollectionsTab component (poorly named, showed collections AND globals)
- Replace with DefaultNavClient throughout for consistency
- Update Nav to fall back to DefaultNavClient when no custom tabs configured
- Pass explicit props instead of spreading to SidebarTabs
- Move test components from test/_community to test/admin
- Add e2e tests for sidebar tabs functionality

Tests cover:
- Rendering custom sidebar tabs
- Switching between tabs
- Persisting active tab in preferences
- Default nav tab behavior

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When moving a document to a new parent, descendant paths were being
duplicated instead of properly updated. The bug occurred because
`previousDocWithLocales` passed to the afterChange hook did not include
hierarchy fields (_h_slugPath, _h_titlePath), causing the path adjustment
logic to append the new parent path to the full descendant path instead of
replacing the old parent path segment.

Solution: Compute the document's previous paths before updating by:
- Fetching the previous parent document to get its paths
- Combining parent path + document's old title/slug
- Handling both localized and non-localized field configurations
- Supporting root documents (no parent) by using just the old title

This fix ensures adjustDescendantTreePaths() receives the correct previous
parent paths to properly strip old path prefixes before prepending new ones.

Before: grandchild path "root/child/grandchild" → "another-root/child/root/child/grandchild"
After: grandchild path "root/child/grandchild" → "another-root/child/grandchild"

Fixes 5 path duplication test failures.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…rafts

- Query both published and draft versions of descendants
- Calculate separate paths for each version
- Update both versions when document is published with drafts
- 3 of 4 draft tests passing
- Known issue: published version path missing child slug in one test
- Fix circular reference validation by querying new parent's tree instead of using stale data
- Add explicit draft: false to descendant queries for published version updates
- Improve computeTreeData to fetch parent when derivation fails (handles draft scenarios)
- Add hasDraftsEnabled import

Fixed issues:
- ✓ Circular reference validation now works correctly (3 tests fixed)
- ✓ Published version path calculation improved (no longer uses draft data)
- Partial: Draft version updates still need work (2 tests still failing)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- When draft-only title changes occur, update the draft version record in
  the versions table with new hierarchy fields (_h_slugPath, _h_titlePath)
- Ensures draft versions maintain correct paths when parent documents move
- Fixes parent field population by ensuring it returns ID instead of object
- All 27 hierarchy tests now passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Updated Scenario 8 from TODO to IMPLEMENTED ✅
- Added detailed flow for draft-only title changes
- Added detailed flow for updating descendants with both published and draft
- Updated Code Flow Reference section to mention draft handling
- Updated Edge Cases section to mark draft state as HANDLED ✅
- Added performance impact calculations for draft updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added "Draft Version Handling" section to Important Behaviors
- Explained how published and draft versions are updated separately
- Provided example showing independent paths for each version
- Documented performance impact of dual version updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added 6 mermaid diagrams showing hierarchy data state changes
- Covers: root/child/grandchild creation, moving documents, draft handling
- Shows actual document structure (fields and values) at each step
- Emphasizes key insight: descendants keep their parent but update tree/paths

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replaced mermaid diagrams with ASCII box diagrams
- Added clear BEFORE/AFTER states for all operations
- Used visual markers (✨ ← →) to show what changed
- Shows exact field values and calculations at each step

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…moves

- Query all descendants (both published and draft-only) when parent changes
- Update main collection with db.updateOne for better performance
- For published+draft documents: also update draft version via payload.update
- For draft-only documents: update both main collection and version record
- Add hierarchyUpdating flag to req.context to prevent infinite recursion
- Prevent parent field from being localized (must be consistent across locales)
- Add publishSpecificLocale limitation warning in documentation

All 27 tests passing
…hange

Architectural refactor to fix localization data flow issues:

**beforeChange hook (new)**:
- Handles current request locale only
- For non-localized fields: sets data directly (e.g., data._h_slugPath = 'foo/bar')
- For localized fields: sets data as string, Payload nests it under locale key
- Validates circular references before DB write
- Stores current locale in req.context for afterChange coordination

**afterChange hook (refactored)**:
- For localized fields: computes paths for OTHER locales when parent changes
- Updates all descendants when parent/title changes
- Uses db.updateOne with locale='all' and full localized object format

**updateDescendants (fixed)**:
- Uses db.updateOne with locale='all' for localized fields
- Passes full localized object (not string with locale param)
- Fixes CastError: "Cast to Embedded failed" for localized path fields

**Impact**:
- ✅ 23/32 tests passing (all non-localized hierarchy tests)
- ✅ No more CastError on localized fields
- ✅ Cleaner separation of concerns (current locale vs other locales)
- ⚠️ 9 localized tests still failing (empty strings for non-current locales)

The core architecture is sound. Remaining issue: when updating a specific
locale (e.g., 'es' after 'en'), paths for other locales get empty strings.
Needs further investigation of localized data flow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Split responsibilities: beforeChange handles current doc/locale, afterChange handles descendants/other locales
- Fix localization path truncation by using previousDocWithLocales instead of previousDoc
- Fix getTreeChanges to handle localized fields by comparing locale-specific values
- Add HIERARCHY_BRAIN.md documenting high-level design and constraints
- Tests: 28/32 passing (87.5%), up from 23/32 (72%)

Remaining issues: 3 draft-related tests, 1 test expectation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add draft detection in afterChange hook
- Draft operations only update current document
- Descendant cascade deferred to publish

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Use doc._status instead of req.context.draft for detection

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove stored _h_slugPath and _h_titlePath fields
- Compute paths on-demand from _h_parentTree
- Solves draft cascade problem naturally
- Simpler hooks with no path string manipulation
- Why stored paths + draft deferral fails
- Why alternatives don't work
- How computed paths solve the problem
- Trade-offs and benefits comparison
- Search returns no results for draft paths
- Breadcrumbs show wrong parent names
- List views show inconsistent hierarchy
- Preview URLs are incorrect
- Collection filters fail
- API responses have stale paths
- Localized drafts break per locale
- Expand why each alternative fails with concrete problems
JarrodMFlesch and others added 30 commits March 3, 2026 13:38
- Initialize field.admin in sanitizeJoinField for hierarchy-injected join fields
- Add optional chaining for field.admin in RelationshipTable
- Fix Button component to handle RSC icon payloads without isValidElement
- Render HierarchyButton icon on server to avoid hydration mismatch
- Update e2e tests for Miller columns drawer with proper selectors
- Fix integration tests for updated error messages and field names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed HierarchyFieldClient to use `value` instead of `initialValue`
for initialSelections, so the drawer expands to the current selection
when re-opened (not the initial value when the form loaded).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed from N sequential calls (one per level) to a single call
with depth parameter. Walks the populated parent chain to extract
ancestor IDs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added isPathReady state to prevent HierarchyColumnBrowser from
rendering until fetchAncestorPath completes. This eliminates the
double-loading where columns were first loaded without the path,
then reloaded with the path.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added hasFetchedPathRef to ensure ancestor path is only fetched once.
Also changed dependency from initialSelections array to firstSelection
primitive to avoid unnecessary effect re-runs from array reference changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added hasLoadedColumnsRef to ensure columns are only loaded once,
preventing double fetches from dependency reference changes or
React StrictMode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- HierarchyDrawer: loadAncestorPath uses useEffectEvent
- HierarchyColumnBrowser: loadColumns uses useEffectEvent

useEffectEvent provides stable references that access latest values
without triggering effect re-runs. Removes need for eslint-disable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
useEffectEvent functions should not be in dependency arrays.
The effect should depend on the actual reactive values.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract firstSelection as primitive to avoid effect re-runs from
array reference changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…hing

Activity exported from React is a Symbol, not a component.
Use CSS display:none to keep tab content mounted while hidden.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant