Skip to content

Island rhythms/projection redesign#166

Open
IslandRhythms wants to merge 3 commits intomainfrom
IslandRhythms/projection-redesign
Open

Island rhythms/projection redesign#166
IslandRhythms wants to merge 3 commits intomainfrom
IslandRhythms/projection-redesign

Conversation

@IslandRhythms
Copy link
Contributor

No description provided.

@vercel
Copy link

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
studio Ready Ready Preview, Comment Feb 13, 2026 11:17pm

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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">
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<list-json :value="document" :references="referenceMap">
<list-json :value="filterDocument(document)" :references="referenceMap">

Copilot uses AI. Check for mistakes.
Comment on lines +384 to +391

.models .projection-tab .remove-field {
flex-shrink: 0;
}

.models .projection-tab-add .add-field-btn {
min-width: 100px;
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
.models .projection-tab .remove-field {
flex-shrink: 0;
}
.models .projection-tab-add .add-field-btn {
min-width: 100px;
}

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +256
class="add-field-option"
@click="addField(path)"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
class="add-field-option"
@click="addField(path)"
class="add-field-option"
role="button"
tabindex="0"
@click="addField(path)"
@keydown.enter.space="addField(path)"

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +120
model(newModel) {
if (newModel !== this.currentModel) {
this.currentModel = newModel;
if (this.currentModel != null) {
this.initSearchFromUrl();
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Comment on lines 432 to 434
if (this.$route.query?.sort) {
const sort = eval(`(${this.$route.query.sort})`);
const path = Object.keys(sort)[0];
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
} else {
this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id');
}
this.selectedPaths = [...this.filteredPaths];
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
this.selectedPaths = [...this.filteredPaths];

Copilot uses AI. Check for mistakes.
.models .projection-column-def:hover .column-remove {
opacity: 1;
}

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.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).

Suggested change
.models .projection-column-def .column-remove:focus,
.models .projection-column-def .column-remove:focus-visible {
opacity: 1;
}

Copilot uses AI. Check for mistakes.
Copy link
Member

@vkarpov15 vkarpov15 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overflow behavior on this table needs some work: Horizontal scrollbar doesn't show up for large data sets because you can't scroll to the bottom (video 1)

Screencast.from.2026-02-25.21-34-58.mp4

Also the "copy" tooltip for individual fields gets clipped by overflow: hidden:

Image

font-size: small;
padding: 0;
margin-right: 1em;
.models .documents-container {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Show all fields by default, but when entering in projection mode deselect all and ask the user to select which fields they want.
  2. 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@vkarpov15
Copy link
Member

Oh and 1 more thing: also need projections for the JSON view

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants