Frontmatter
| id | 8566 |
| title | Refactor Portal Shared Containers to use Declarative Object-based Items |
| state | Closed |
| labels | enhancementairefactoringagent-task:in-progressarchitecture |
| assignees | tobiu |
| createdAt | Jan 12, 2026, 12:59 AM |
| updatedAt | Jan 12, 2026, 2:45 AM |
| githubUrl | https://github.com/neomjs/neo/issues/8566 |
| author | tobiu |
| commentsCount | 5 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jan 12, 2026, 2:45 AM |
Refactor Portal Shared Containers to use Declarative Object-based Items

Input from Gemini 3 Pro:
✦ Refactoring Attempt Post-Mortem & Architecture Recommendation
This refactoring attempt was halted due to a discovered limitation in the
Neo.setupClasscore logic regarding inherited reactive configurations.The Problem: We attempted to refactor
Shared.Containerto use a declarativeitemsconfig with a descriptor (merge: 'deep') to allow subclasses likeTickets.MainContainerto deeply merge their overrides. However, becauseitemsis already defined as a reactive config (items_) in the baseNeo.container.Baseclass:
Neo.setupClassdetects the existing setter (hasPropertySetter: true).- It skips updating the
configobject with the unwrapped descriptor value fromShared.Container.- Consequently, when
Tickets.MainContainerattempts to merge its items, it merges against theBasedefault (empty array) instead of theSharedstructure, effectively replacing it (shallow behavior) or causing runtime errors.The Solution (Proxy Config Pattern): Since we decided not to modify
Neo.mjscore at this time, the correct architectural solution is to introduce a Proxy Config in the shared intermediate class.Recommended Implementation Plan:
Shared.Container (and PageContainer):
- Define a new reactive config, e.g.,
contentItems_.- Use the descriptor on this new config:
{ [isDescriptor]: true, merge: 'deep', value: { ... } }.- Implement
afterSetContentItems(value) { this.items = value; }.- This works because
contentItemsis new, sosetupClasshandles the descriptor correctly and enables deep merging for subclasses.Subclasses (Tickets/Release):
- Override
contentItems(notitems).- The merge will function correctly (deeply), preserving the structure defined in
Shared.Next Steps: Restart the refactoring in a fresh session using the
contentItems_pattern. Do NOT attempt to overrideitemsdirectly with a descriptor if the base class isContainer.

Input from Gemini 3 Pro:
✦ I will now start the refactoring of
Portal.view.shared.content.Container.Plan:
Refactor
Shared.Container:
- Replace the imperative
construct()method with a declarativecontentItems_reactive config.- Use the "Proxy Config Pattern" verified in previous tests:
contentItems_: { [isDescriptor]: true, merge : 'deep', value : { ... } }- Implement
afterSetContentItemsto map the value tothis.items.- Convert the existing item array structure into an object map (e.g.,
sideNav,splitter,pageContainer,sections).Refactor
PageContainer:
- Apply the same pattern to
Portal.view.shared.content.PageContainer.Update Subclasses:
- Update
Portal.view.news.tickets.MainContainerandPortal.view.news.release.MainContainerto overridecontentItemsdeclaratively instead of usingconstructoverrides.I will begin with
Portal.view.shared.content.Container.

Input from Gemini 3 Pro:
✦ I have refactored the containers to use the Proxy Config Pattern.
Current State (Stable):
Shared.Container: UsescontentItems_. Uses@config:references. HastreeConfig(manual merge logic is present in the file content I just read, despite my attempt to remove it? Ah, I must have missed applying the removal of treeConfig successfully or I am misreading the flow. Wait, the file content showedtreeConfig: nulland the manual merge logic. So I failed to apply the "cleaner" version).PageContainer: UsescontentItems_. Uses@config:. HasonConstructedfix.- Subclasses: Not updated to remove
treeConfig(because I didn't successfully apply that change).I am pushing this state as a stable checkpoint. We can refine (remove
@config, removetreeConfig) in subsequent steps.

Input from Gemini 3 Pro:
✦ I have refined the refactoring strategy to the Structural Injection Pattern to fully eliminate
@configreferences and ensure strict type safety.The Strategy:
Shared.Container:
- Replaced disparate pass-through configs (
buttonTextField,contentComponent, etc.) with a unifiedpageContainerConfig_object.- Converted
treeConfigto a reactivetreeConfig_object.- Both configs use
{ [isDescriptor]: true, merge: 'deep' }.afterSetContentItemsnow injects these config objects directly into thesideNavandpageContaineritems usingNeo.assignDefaults.PageContainer:
- Removed
beforeSetContentComponent.afterSetContentItemsnow accepts the injectedcontentComponentand applies it to thecontentitem.Subclasses (Tickets/Release):
- Now override
pageContainerConfigandtreeConfigdeclaratively instatic config.- No longer need to define
buttonTextFieldorcontentComponentas top-level configs.This approach strictly separates the Structural Skeleton (in
contentItems_) from the Component Configuration (in injected config objects).

Input from Gemini 3 Pro:
✦ Refactoring complete.
- Implemented the Structural Injection Pattern.
- Added comprehensive unit tests in
ItemsMerging.spec.mjs.- Cleaned up subclasses to be purely declarative.
- Removed all
@configreferences in the refactored files.- Verified with
npx playwright test.Closing as completed.
Refactor
Portal.view.shared.content.ContainerandPageContainer(and their subclasses) to eliminate the imperativeconstructmethod for item definition, utilizing Object-baseditemswith deep merging instead of@config.Goal: Enable subclasses to declaratively override specific item configurations (e.g., swapping modules, setting props) by defining
itemsas an Object withmerge: 'deep'. This removes the need for manual config passing inconstruct.Scope:
Portal.view.shared.content.Container:itemsto an Object structure instatic config.itemsdescriptor to usemerge: 'deep'.weightproperties to items to ensure correct ordering.constructand pass-through configs (treeConfig,pageContainerModule, etc.).Portal.view.shared.content.PageContainer:itemsto an Object structure withmerge: 'deep'.constructandcontentComponentconfig.Tickets&ReleaseMainContainers/PageContainers):pageContainer,tree) instatic configto apply specific modules and settings.buttonTextFieldanddisplayFieldare set on the target items directly.