Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Redesigns the Models “table” view projection UX to be inline (projection text input + add/remove columns) and persists projection selections per model, along with associated layout/styling updates.
Changes:
- Add per-model projection persistence and new projection manipulation methods (add/remove/reset/all, parse projection input).
- Replace the old projection modal/table header UI with an inline projection bar + add-field dropdown.
- Refactor layout/CSS for the table/json views and the documents header area.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| frontend/src/models/models.js | Adds projection persistence/parsing and new projection UI state/handlers; updates URL sync behavior. |
| frontend/src/models/models.html | Replaces projection modal with inline projection controls and add-field dropdown; adjusts table/json rendering. |
| frontend/src/models/models.css | Introduces new projection/table/dropdown styling and layout changes for documents area. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Open this Document | ||
| </button> | ||
| <list-json :value="filterDocument(document)" :references="referenceMap"> | ||
| <list-json :value="document" :references="referenceMap"> |
There was a problem hiding this comment.
JSON view now passes the full document to <list-json>, but referenceMap is built from filteredPaths and will be incomplete for fields not in the current projection (and it also ignores the projection entirely). Either pass filterDocument(document) again, or update referenceMap/JSON rendering to be based on schemaPaths (or the active projection) so references and projection behavior are consistent.
| <list-json :value="document" :references="referenceMap"> | |
| <list-json :value="filterDocument(document)" :references="referenceMap"> |
|
|
||
| .models .projection-tab .remove-field { | ||
| flex-shrink: 0; | ||
| } | ||
|
|
||
| .models .projection-tab-add .add-field-btn { | ||
| min-width: 100px; | ||
| } |
There was a problem hiding this comment.
The .projection-tab / .projection-tab-add rules appear unused by the updated markup (no matching classes in models.html). If they’re leftovers from the previous projection UI, consider removing them to keep the stylesheet aligned with the new structure.
| .models .projection-tab .remove-field { | |
| flex-shrink: 0; | |
| } | |
| .models .projection-tab-add .add-field-btn { | |
| min-width: 100px; | |
| } |
| class="add-field-option" | ||
| @click="addField(path)" |
There was a problem hiding this comment.
The add-field dropdown options are rendered as clickable <li> elements without keyboard focus/interaction support (no tabindex, no role, no @keydown). This makes the field picker hard/impossible to use without a mouse. Consider rendering each option as a <button type="button"> (or add appropriate ARIA roles + keyboard handlers) so the dropdown is accessible.
| class="add-field-option" | |
| @click="addField(path)" | |
| class="add-field-option" | |
| role="button" | |
| tabindex="0" | |
| @click="addField(path)" | |
| @keydown.enter.space="addField(path)" |
| model(newModel) { | ||
| if (newModel !== this.currentModel) { | ||
| this.currentModel = newModel; | ||
| if (this.currentModel != null) { | ||
| this.initSearchFromUrl(); |
There was a problem hiding this comment.
The new model watcher calls the async initSearchFromUrl() without awaiting or handling errors. Since initSearchFromUrl/getDocuments can throw, this can lead to unhandled promise rejections when the prop changes. Consider making the watcher async and await the call, or void this.initSearchFromUrl().catch(...) with appropriate user-facing error handling.
| model(newModel) { | |
| if (newModel !== this.currentModel) { | |
| this.currentModel = newModel; | |
| if (this.currentModel != null) { | |
| this.initSearchFromUrl(); | |
| async model(newModel) { | |
| if (newModel !== this.currentModel) { | |
| this.currentModel = newModel; | |
| if (this.currentModel != null) { | |
| try { | |
| await this.initSearchFromUrl(); | |
| } catch (err) { | |
| this.error = err && err.message ? err.message : String(err); | |
| } |
| if (this.$route.query?.sort) { | ||
| const sort = eval(`(${this.$route.query.sort})`); | ||
| const path = Object.keys(sort)[0]; |
There was a problem hiding this comment.
initSearchFromUrl parses this.$route.query.sort via eval, so a crafted URL can execute arbitrary JS in the page. Consider encoding sort as JSON (quoted keys) and using JSON.parse with validation, or parsing a constrained field:direction format instead of evaluating code.
| } else { | ||
| this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id'); | ||
| } | ||
| this.selectedPaths = [...this.filteredPaths]; |
There was a problem hiding this comment.
selectedPaths is still being maintained here, but after removing the projection modal it no longer appears to be read anywhere in the component/template. Consider deleting selectedPaths from state and removing these assignments to avoid confusing future maintenance.
| this.selectedPaths = [...this.filteredPaths]; |
| .models .projection-column-def:hover .column-remove { | ||
| opacity: 1; | ||
| } | ||
|
|
There was a problem hiding this comment.
.column-remove is hidden via opacity: 0 unless the header is hovered, but there’s no corresponding :focus/:focus-visible rule to reveal it for keyboard users. This can make the remove button effectively invisible when tabbing through controls. Consider also showing it on focus (or not hiding it purely on hover).
| .models .projection-column-def .column-remove:focus, | |
| .models .projection-column-def .column-remove:focus-visible { | |
| opacity: 1; | |
| } |
| font-size: small; | ||
| padding: 0; | ||
| margin-right: 1em; | ||
| .models .documents-container { |
There was a problem hiding this comment.
Tailwind for styles please to be consistent. There are some places where we still use CSS but we should default to tailwind going forward.
| this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id'); | ||
| } | ||
| } else { | ||
| this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id'); |
There was a problem hiding this comment.
I don't like the behavior that _id is the only field shown by default, I think it is more reasonable to show all fields by default
There was a problem hiding this comment.
I have to disagree here. Just using ZEVO as an example, there are some many fields that the table would start massive to begin with, and then the user would have to figure out what needs to be trimmed from the view. Whereas with this way, The user can add into the view what they want shown. I wish we had more default fields so that the table wasn't so barren on load, but there's only so much we can control.
There was a problem hiding this comment.
The problem though is that the _id field is largely useless, at a glance you can't deduce any meaningful information from a list of _ids. And not seeing anything useful to begin with is not great user experience.
You can control the order that fields show up in the Mongoose Studio table view by rearranging fields in your Mongoose schemas, so that can help you ensure that you only see what you care about.
A couple of potential middle grounds:
- Show all fields by default, but when entering in projection mode deselect all and ask the user to select which fields they want.
- Use AI to reorder / prioritize the fields so the default projection can show the 4-5 "most important" fields according to what the LLM determines is most important.
| const obj = eval('(' + trimmed + ')'); | ||
| const inclusionKeys = Object.keys(obj).filter(k => obj[k]); | ||
| const exclusionKeys = Object.keys(obj).filter(k => !obj[k]); | ||
| if (exclusionKeys.length > 0 && inclusionKeys.length === 0) { |
There was a problem hiding this comment.
Projections are a little more tricky than this, especially if you allow arbitrary input text. For example, there's things like $slice, $elemMatch, etc. https://www.mongodb.com/docs/manual/reference/operator/projection/slice/ . In the interest of keeping things clean, we may want to disallow using MongoDB projection syntax and instead support Mongoose's: name age is a projection that includes "name" and "age" and excludes everything else; -name -age is a projection that excludes name and age and includes everything else.
|
Oh and 1 more thing: also need projections for the JSON view |

No description provided.