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; | |
| } |
frontend/src/models/models.html
Outdated
| 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; | |
| } |
| 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 |
|
The merge from main undid a lot and the feedback here indicates that another swing needs to be taken so its just better to open a new PR at this point |
|
Scratch that, its because of how it looks without a workspace |

No description provided.