Repository navigation
intent: a list column may show a record's child rows - list: [skills.skill] renders the children's texts as chips (#8295) - #8303
Merged
Conversation
…skill] renders the children's texts as chips (#8295) A record's children were invisible in every generated list: list: accepted a field, a to-one or subset relation, or a label's Name, and refused a oneToMany ("has no column on the entity"), so the reader opened each row to learn what it held. A list: entry may now be a collection path, <collection>.<property> - a oneToMany of the entity (a manyToMany arrives as the oneToMany to its link entity) and a field or to-one of its child - or a bare <collection>, meaning <collection>.Name, which needs the child's label:. The parser refuses, naming the token, an unknown collection or property, a path deeper than one hop, a property that is itself a collection, a bare collection without the child's label, a child not referencing the entity through exactly one to-one, and a column name colliding with a declared property. A label: on the collection is accepted once the list shows it, and heads the column. EdmIntentGenerator emits one collectionColumns entry per path (a structured attribute, so it survives the .edm), its name (SkillsSkill / Skills) standing in listOrder where the author wrote it; ModelParameterProcessor places it in listProperties and a register's columns, and resolves the child's and the to-one target's generated classes and the child's order. The generated controller's list, POST /search and GET /{id} fill a @transient List<String> on the entity per page - the child's repository by Criteria.in(<fk>, keys), then a to-one's targets by their keys - never a read per row. It reads through the repositories rather than a raw join so a multilingual child or target is translated, as its dropdown is; transient, a write carrying it changes nothing. The power list, a composition child's detail panel and a register without show: render the texts as chips (no sort, no filter); the export joins them with ", ". The panel tables are markup shared by every model, so their chip branch is gated on rendersCollectionColumns, set only for a model that renders a collection column: a model without one generates as before. The my/partner lists and the form/document summary lines leave the column out. Verified: ListColumnsIntentTest, EdmIntentGeneratorTest, ModelParameterProcessorTest (engine-intent 1884, ide-template 318 green); IntentEmissionCoverageIT (list, search, get, a PUT carrying the column) and the new CollectionColumnHarmoniaIT (chips in the panel, the power list and the register) green; formatter:validate with the cache wiped; release javadoc on engine-intent and ide-template. Co-Authored-By: Claude Opus 5.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.
Cause
A record's children were invisible in every generated list.
list:accepted a field, a to-one orsubsetrelation, or a label'sName, and refused aoneToMany("has no column on the entity"), so the reader had to open each row to learn what it held.Change
Implemented as the issue's decisions describe, with one deviation in how a to-one's text is read (below).
Syntax (parser). A
list:entry may be<collection>.<property>or a bare<collection>(=<collection>.Name, which needs the child'slabel:). AmanyToManyarrives as theoneToManyto its link entity. The parser refuses each of the following with a message naming the token:A
label:on the collection used to be refused ("renders no control of its own"). It is now accepted once the list shows the collection, and it heads the column.Model.
EdmIntentGenerator.buildCollectionColumnsemits onecollectionColumnsentry per path. It is a structured attribute, so it survives the.edm. Each entry carries the child entity and its perspective, the child's FK back, the property shown and, for a to-one, its target's display property. The column's name (SkillsSkill, orSkillsfor a bare collection) stands inlistOrderwhere the author wrote it.ModelParameterProcessorthen:listPropertiesand in a register's columns;orderBy:, else its primary key.Server. The controller's list,
POST /searchandGET /{id}fill a@Transient List<String>on the entity, once per page and never once per row: the child's repository byCriteria.in(<fk>, keys), then for a to-one the target's repository by the distinct target keys. Because the field is transient, a write that carries it changes nothing.Deviation from decision 2: the issue says "one query, joined to the to-one's target". I read through the two repositories instead (two reads per page for a to-one, still none per row), for two reasons:
JavaRepositoryhas no projection query.For the same reason, no new method was needed in
Repository.java.template.UI. The column renders as chips in the power list (no sort; filter type
none, so it is not offered as a filter), in a composition child's detail panel, and in arelated:register withoutshow:. The export writes", "-joined text. The header is the collection's label, with an en-US catalog entry. The panel tables are markup shared by every model, so their chip branch is gated onrendersCollectionColumns, which is set only for a model that renders a collection column anywhere.detailPanel.cellValuealso joins alistcolumn for any text context. The my and partner lists and the form/document summary lines leave the column out, as scoped.Verification
Run and green:
ListColumnsIntentTest:aCollectionPathIsAListColumn,aBareCollectionNeedsTheChildsLabel,anUnknownOrDeeperPathIsRefused.EdmIntentGeneratorTest.aCollectionPathEmitsACollectionColumn: the emitted column, itslistOrderposition, and a register reproducing it.ModelParameterProcessorTest.aCollectionColumnIsListedAndResolvedForTheController.IntentEmissionCoverageITwith the issue's model generated, compiled and running:/searchandGET /{id}answerSkillsSkill: ["Java","SQL"]for ann and[]for a member without skills;SkillsGrantedByanswers the field's values;PUTcarryingSkillsSkill: ["COBOL"]leaves the skills as they were.CollectionColumnHarmoniaIT(new, browser): ann's row shows the chips Java and SQL in the Team page's TeamMember panel. The same chips show in a root entity's power list and in a register on another entity's page. The issue's TeamMember is a composition child, which gets no power list page of its own, so the test adds an Employee/Department pair for the list and the register.mvn -T 1C formatter:validatewith the formatter cache wiped.Not verified:
.testmanifest'slistnow names the collection column (it is the.model'slistOrder). I could not check how the external app-test SDK's list flow treats a name that is not a field.Fixes #8295
🤖 Generated with Claude Code