Model & Properties
The model tree and the properties grid are the everyday surface of Semanticus: browse every table, measure, column and relationship, then edit names, descriptions, formats and any other property. It is the same live model your AI Assistant reads and writes over MCP, on one shared undo timeline.

The grid includes model settings as well as tables, columns, measures, relationships and other objects. Selecting a measure also exposes its TMDL editor, so schema and expression work stay in one place.
#In the Studio
The model tree is a native VS Code tree (not a webview), so it behaves like the rest of your editor. It expands to every object kind: each table, and under it that table's measures, columns, hierarchies and partitions, plus top-level folders for relationships, roles, perspectives and calculation groups (with their items).
A curated right-click menu over every object type does the common authoring jobs: create, script to DAX or TMDL, hide or show, and delete, with multi-select for bulk actions. Find in Model and Reveal in tree get you to an object fast, and you can mark a date table or generate a time-intelligence suite straight from the menu.
Selecting an object opens the Properties grid, a dedicated webview with typed editors (text, checkbox, number, dropdown), search and filter, collapsible categories, and multiline editors that grow as you type. Invalid input is flagged inline. Select several objects at once and the grid shows a tri-state, multi-select bulk edit, so one change lands on all of them. Editing an object's Name renames it and rewrites every DAX reference automatically, so a rename never leaves a broken measure behind.
#Real choices, not blank boxes
The fields you fill most often come prefilled with real options. Display Folder suggests the folders already in use, the object's own table first and then the rest of the model, so an existing folder structure stays consistent instead of fragmenting on typos. The Data Type dropdown lists exactly the types the engine can actually set (it is fed by the engine's own allow-list, so the pick list can never offer something the write would refuse), and on a calculated column it is locked with a plain reason, because a calculated column's type comes from its DAX. Format string on a measure or column opens a picker over the curated format library, grouped by category with an example of what each format renders. Free text stays available everywhere; the pickers are a head start, not a cage.
#Over MCP
Your AI Assistant walks the same tree and edits the same objects by name:
list_objectslists child objects: pass an empty ref to list tables, or a table ref (liketable:Sales) to list its measures and columns.get_objectreads an object's properties by ref, including its description and the authored metadata that answers questions like "does this table have a description?".get_propertiesreturns the same editable surface the grid shows (name, display name, category, kind, current value, enum options, read-only flag), andset_propertywrites one property back by name, taking the string form of the value (true/falsefor a checkbox, the enum name for a dropdown, the number for a numeric field).set_descriptionwrites a business description (keep it front-loaded and under ~200 characters, because Copilot truncates), andrename_objectrenames with the same safe DAX rewrite as the grid.search_modelfinds objects by their name, description or DAX across tables, measures, columns, hierarchies, calculation items, functions, roles and perspectives, and returns each hit's ref so the agent can act on it (the full search surface, with match modes, filters and safe replace, is on Editing & navigation).list_measuresandlist_columnspull the whole authoring surface in one call for an audit, andget_model_objectsreturns every table with its measures, columns and hierarchies in a single read.
# the agent, browsing then editing one measure
list_objects # -> the tables
list_objects table:Sales # -> Sales measures + columns
get_object measure:Sales/Margin # -> properties, incl. description
set_description measure:Sales/Margin "Gross margin as a percent of net sales."
rename_object measure:Sales/Margin "Margin %" # every DAX reference rewritten
#Format strings, from a library
Formats are where hand-typed strings go wrong: the glyphs, the scaling commas, the sign handling. The measure's Format string picker is backed by a curated library of about 90 number formats across 17 categories (whole numbers, decimals, percentages, currency, K/M/B scaling, signed variance, change indicators, accounting, dates, times, durations, KPI glyphs, ratios, basis points and more), each with a live example of what it renders. The twenty or so most common are pinned first, and a custom escape hatch remains for anything else.
The library carries both kinds of format. A static format is a literal string
(set_measure_format). A dynamic format is DAX that returns a format
string per filter context (set_measure_format_expression): that is how one measure renders
500 as "500" and 5,000,000 as "5M", or switches format per selected currency. Browse the catalog with
list_format_templates, which works offline and with no model open.
#Editing a dynamic format
Every measure's Properties grid carries a Format expression row that shows at a glance whether a dynamic format exists, with a preview of the DAX. Edit expands it into a lightweight DAX editor: monospace, an insert-from-template picker over the library's dynamic entries so the common patterns start from working DAX, and a balanced-quotes-and-parentheses check before Apply. Applying writes through the engine like any other edit (undoable, visible in Edit History, broadcast to the other door), and clearing the expression honestly restores the static format string. Dynamic format strings need compatibility level 1601: below that, the row says so plainly instead of showing a dead control.
#Update Schema: when the source moved under you
Sources drift: a column is added upstream, one is dropped, a type changes. Right-click a table and choose Update Schema. Semanticus re-reads the table's source columns schema-only (no data is imported), diffs them against the model, and shows the drift grouped as added, removed and type-changed, each with a checkbox. Apply the subset you accept as one undoable change; anything that cannot apply (a calculated column, a name clash) is skipped with its reason instead of failing the whole batch.
get_source_schema table:Sales # re-read the source's columns, schema-only
diff_schema table:Sales # added / removed / type-changed vs the model
apply_schema_update table:Sales items=[...] # apply the accepted subset, one undoable step
All three are free. The probe needs a reachable SQL or Fabric source; offline, or on a source it
cannot read, it says so plainly instead of guessing, and you can hand diff_schema a column
list to diff against instead. Calculated columns are never flagged: they are derived from DAX, not from
the source.
#One tree, both doors
Whether you rename in the grid or your AI Assistant calls rename_object, the change lands on the
same session and broadcasts a model/didChange event, so the other door redraws immediately
and the edit joins one shared undo timeline. You can veto anything with a single undo, no matter which
door made it, because it is one model, not a copy.
A rename rewrites every DAX reference within the model. Bindings that live outside the model (a report's field references) cannot be rewritten from here, so review renamed fields that reports use before you ship.
Semanticus