Rich Probes + Table Projector - #1998
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR adds a rich "projector-style" display system for probed values, introducing three interactive renderers (calculator for integers, table editor for structured data, and card view for playing cards). The system allows users to view and manipulate probed values through modal interfaces that can rewrite the underlying syntax.
Key Changes:
- Added new renderer system with
RichProbeinterface for domain-specific visualizations - Implemented three renderers:
TableRenderer,CalculatorRenderer, andCardRenderer - Added
project_tablessetting to control table projection in evaluation output - Extended projector system to support dynamic renderer selection and syntax rewriting
Reviewed Changes
Copilot reviewed 52 out of 52 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
src/web/app/common/foo |
Test data file (987 lines) - should not be committed |
test/haz3ltest.re |
Commented out 3 test suites without explanation |
src/haz3lcore/projectors/implementations/TableRenderer.re |
New table renderer with column operations (817 lines) |
src/haz3lcore/projectors/implementations/CalculatorRenderer.re |
New calculator renderer for integer arithmetic (258 lines) |
src/haz3lcore/projectors/implementations/CardRenderer.re |
New card visualization renderer (326 lines) |
src/haz3lcore/projectors/implementations/RichProbe.re |
New interface for rich probe renderers (47 lines) |
src/haz3lcore/projectors/implementations/ProbeProj.re |
Extended probe projector with renderer registry and modal support |
src/haz3lcore/projectors/ProjectorPerform.re |
Updated action handling to preserve refractor models during syntax updates |
src/haz3lcore/pretty/ExpToSegment.re |
Added table projection support in expression pretty-printing |
src/language/CoreSettings.re |
Added project_tables setting to evaluation configuration |
src/web/Settings.re |
Integrated project_tables toggle in settings UI |
| CSS files | Added styling for calculator, table, and tableprobe projectors |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,3 +1,4 @@ | |||
| [@coverage exclude_file]; | |||
There was a problem hiding this comment.
For some reason coverage breaks the regex in here. We should open an issue in the bisect_ppx repo
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 53 out of 53 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #1998 +/- ##
==========================================
- Coverage 50.91% 50.58% -0.34%
==========================================
Files 300 307 +7
Lines 39867 40769 +902
==========================================
+ Hits 20300 20622 +322
- Misses 19567 20147 +580
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 58 out of 58 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Move card types (suit, rank, card, hand) and conversion utilities into CardTypes.re, CardSyntax.re, and CardUtil.re. This prepares for the RichProbe renderer system where CardRenderer will reuse these modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ~inline parameter to lift_syntax and term_to_seg in ProjectorBase utility type. When inline=false, lift_syntax appends a trailing newline to give room for probe values. This prepares for the table projector which needs non-inline syntax rewrites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce the RichProbe module type signature for domain-specific probe visualizations. Add CardRenderer (card/hand display) and TableRenderer (interactive table with column menus, sorting, grouping, filtering, and type-aware transformations). Also add WebUtil.empty_hole_svg helper used by TableRenderer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Table projector kind and TableProj module for rendering lists of labeled tuples as read-only tables. Register in ProjectorInit and add to context menu. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add rich_probes field to CoreSettings.Evaluation and add core_settings to ProjectorBase.View.args so projectors can access feature flags. Thread core_settings from globals through ProjectorView and RefractorView to all projector view functions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add packed_renderer system for heterogeneous RichProbe storage using sexp serialization. Register TableRenderer and CardRenderer as available renderers. Add renderer badges to probe value views when rich_probes is enabled. Add modal overlay for interactive rich probe display. Change ProbeProj model from unit to probe_model tracking active renderer state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add proj-table.css for basic table styling (borders, zebra rows, sticky headers) and proj-tableprobe.css for rich probe table interactions (column menus, modal backdrop, add-column button). Add table CSS variables to variables.css. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add RichProbes evaluation action and wire it to a toggle in the nut menu. When enabled, probe value views show renderer badges for compatible rich probe visualizations (table, card). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add projector kind to SetSyntax action so refractors (probes) can properly rewrite underlying syntax. For refractors, select the full term range and replace it with the new syntax, then re-add the manual refractor. This enables table column operations (drop, rename, sort, etc.) to actually modify the program. Also add CSS for renderer badges (background, hover) and z-index rule for modal stacking context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous implementation unconditionally added a manual refractor after syntax replacement, which broke ephemeral (auto) probes. Now properly distinguishes: - Manual refractors: replace syntax and re-add manual with preserved model - Ephemeral refractors: replace syntax only, auto system re-detects Also add ~model parameter to Refractors.mk_entry and ZipperBase.add_manual to preserve refractor state across syntax rewrites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Atom.compare_builtin: cls -> option(builtin name). BuiltinsBase.mk_compare now sources names from here, so the cls -> name mapping has one source. Folded string_compare into the mk_compare list. - Operators.numeric_bin_op: lift op_bin_num to op_bin given an Atom.cls. Moved out of TableTransforms. Test_Atom covers compare_builtin in addition to conversions_from. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Atom.re now owns the list of (kind, cmp) pairs that need an Ord builtin. BuiltinsBase iterates over Atom.compare_builtins and assembles the BuiltinsUtil.fn records, so the per-class data has a single source. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the cls switch in mk_compare in favor of Atom(cls) |> Typ.fresh. Aliased the top-level Typ as LangTyp since `open Fresh` shadows it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the cls-shaped Typ.fresh call site in BuiltinsBase, hidden by the LangTyp alias because `open Fresh` shadows the top-level Typ here. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Atom.compare_builtins now exposes list((name, compare_entry)), mirroring Atom.converter_builtins' (name, builtin) shape. - BuiltinsADT.of_atom_compare turns an entry into a BuiltinsUtil.fn (it needs Ord.t, which lives here). - Builtins.re maps it in alongside the converter mapping; BuiltinsBase no longer participates in compare-builtin assembly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- BuiltinsADT now owns invert_ord and exposes ord_builtins (invert_ord + per-cls compares from Atom.compare_builtins). BuiltinsBase no longer participates in Ord-related wiring. - Builtins.re maps the bundle through fn_builtin for consistency with neighboring rows. - TableTransforms.sort_column references BuiltinsADT.invert_ord.name instead of a string literal. - Atom.compare_of_cls is the single exhaustive switch; compare_builtin and compare_builtins derive name and list from it (and from cls_string_lower) -- adding a new cls forces a compile-time decision. - New Test_BuiltinsADT covers invert_ord and of_atom_compare end-to-end; new Test_Operators covers numeric_bin_op. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Unify len_seg/seg_of_exp/abbreviated_seg_of across ProbeProj and TableCore. Width is now measured in Unicode columns everywhere, and abbreviation strips both ascriptions and projector wrappers in both call sites. Also drops TableCore's dead length_cls (its emitted size classes had no matching CSS selectors).
handle_key already knows the action count, so it computes the wrapped target index and dispatches MenuSelect(new_idx) directly. This removes the unbounded idx growth in MenuDown and the separate MenuUp/MenuDown actions entirely.
Reset on any failure still — the record is just transient UI state — but Unknown_renderer and malformed-payload Failures now print a diagnostic line so they aren't completely silent in dev.
Both ContextMenuListener and ColumnMenuListener attached a document-level capture-phase pointerdown for click-outside, plus near-identical state-tracking in sync. Move the shared machinery into MenuListener.Make and parameterize over menu class, optional keydown dispatch, and optional scroll-into-view. Each former module is now an 8-line config instantiation.
Tables slide reframes the gradebook example around "tables are lists of labeled tuples" and adds a projected view of the same data. Probes slide adds a rich-probes section with an inventory sample whose "View as table" modal is opened by default.
Thread the active renderer's id into probe_ctx so the sample-menu entry flips to "Hide <id>" when that renderer's modal is already open. Click behavior is unchanged — it dispatches ToggleModal either way — but the label now reflects what the click will do.
Mirror ProjectorInfo.utility.term_to_seg, which already sets project_tables: false. After CoreSettings.Evaluation.init flipped project_tables to true, the test's settings auto-wrapped abbreviated list-of-records output in ^^table(...), exceeding the budget. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Combine column menu with ContextMenu |
Splits ContextMenu.re into a generic Util.Menu framework (state, item/Action/Submenu/Back/Divider, render, keyboard) and editor-specific contents (Shortcuts, *_data builders, caret positioning). TableRenderer's column menu now uses the same module and adopts .context-menu styling, including path-driven submenus. Keyboard dispatch for the editor menu routes through ContextMenuListener at document capture phase instead of the bespoke handler in CodeEditable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Menu.direction_from_id/direction_of/space_from helpers that compute an open_direction from any element's bounding rect against the #main viewport, and applies them to the column menu so it opens upward and/or leftward near viewport edges. The editor menu's .open-* classes carry caret-anchored transforms, so the column menu uses its own .cm-* classes keyed to the th cell corners (top/bottom × left/right) with matching border-radius and animation transform-origin. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ArrowRight steps into a Submenu row when it's the selected item; ArrowLeft pops out of a non-root path, targeting the first visible Back row's destination (or [] as a fallback). Editor menu has no submenus so both arrows fall through to Unhandled there; column menu's adapter already maps EnterSubmenu/BackSubmenu to ShowSubmenu, so navigation works without further changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Submenus now carry their children inline (Submenu({label, children}))
instead of a target_path string. Util.Menu walks the path through the
tree, synthesises the ← Back row when nested, and clamps the selected
index against the visible row count internally — so callers no longer
build per-path item lists or track separate clamping.
Action variants simplify too: EnterSubmenu carries one label (pushed
onto the path), BackSubmenu has no arg (pops one level). Menu.update
is pure now (no items parameter), since clamping happens at render and
keyboard-dispatch time.
TableRenderer's projector model drops the (col, path, sel_idx) tuple
for (col, Menu.t) and consolidates ShowSubmenu/MenuSelect into a single
MenuAction(Menu.action) variant. build_column_menu produces the whole
tree once per render; menu_at/items_at lookups are gone.
ContextMenu.WithContext.update becomes a one-liner forwarding to
Menu.update; clamping is no longer its concern.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
handle_key was dispatching MenuUpdate(Up/Down), which decrement the raw stored selected_idx via Menu.update. After a Down past the visible count, the stored value drifts beyond the cap; render clamps visually but the next Up walks back from the stale number, taking N keypresses to start moving. Convert ArrowUp/Down to SetSelected(clamped ± 1) inside handle_key — the clamped current index is already computed there. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s bold The shared .context-menu rule didn't set font properties, so each menu inherited from its container — the editor menu picked up .code's normal weight + code font, but the column menu inherited <th>'s bold default and looked heavier than the editor menu. Set font-family / weight / size / line-height explicitly on .context-menu. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The column menu sits inside a <th>, which browsers default to font-weight: bold. That weight inherits through the menu's DOM and bolded all the menu rows compared to the editor context menu (rendered inside a non-th container that doesn't have the UA bold). Wrap the header label in a .column-label span and move the header's font properties (weight 600, code-font, BR4 color) onto that span. Reset .table th's font-weight to normal so the UA default doesn't inherit any further. The label still renders bold; nested children (the menu) inherit a clean 400. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the local vertical_dir/horizontal_dir/open_direction/available_space types and get_available_space/determine_direction — they duplicated Util.Menu's. get_direction now treats the caret as a zero-size anchor at (caret_left, caret_bottom) and calls Menu.space_from + Menu.direction_of. direction_class stays local since the editor menu's .open-* CSS carries caret-anchored transforms that don't apply to the column menu's th-anchored .cm-* classes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NutMenu.submenu wrapped the menu in top-menu-item/submenu-icon/icon/ submenu divs whose CSS is scoped to #top-bar — none of those rules apply inside the editor. The styling that actually works comes from .context-menu .group .contents …, so just emit those two divs directly and drop the NutMenu dependency. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ColumnMenuListener's capture-phase pointerdown handler closes the menu whenever the click target isn't inside the menu DOM. The ⋮ button isn't inside the menu — it sits in the th cell — so a click on it was firing the close path before the button's own on_click reached ShowMenu, which then saw menu_state=None and opened the menu again. Net effect: clicking ⋮ to "close" reopened it. Tag the ⋮ button with `menu-trigger` and skip the close-on-outside path when the click target is under that class. ShowMenu(i) already toggles when the same column is open; now it can actually fire. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ExpToSegment.Settings.t gained project_tables (Table Projector from dev #1998). Set to false for TermEdit round-trip to preserve raw term structure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds Rich Probes (interactive domain-specific visualizations of probed values), the Table Projector (renders lists of labeled tuples as interactive tables), and auto-label support so the table projector works with unlabeled / out-of-order tuples whose labels come from the expected type.
Ported from
tables-study(Rich Probes + Table Projector) plus PR #2207 (auto-labeling via elaboration), extracting only these features without pulling in unrelated changes (tutorials, pretty printer rewrite, dynamic statics, etc.).New features
CardRenderer — renders probed card/hand values with visual card sprites and chooser UIReverted; the card projector stays as ondev.Projectorproblem category; errors appear in the cursor inspector and problem sidebarSettings toggles — "Rich Probes" and "Tables" in Value Display settings menuSettings toggle — "Tables" in Value Display settings menu. (The
rich_probesfeature flag was removed in13fe26eddd; rich-probe renderers are now always available via the sample context menu.)Comparison builtins —int_compare,float_compare,sint_compare,nat_comparefor table sortingComparison builtins —
int_compare,float_compare,sint_compare,nat_compare, plusinvert_ordfor descending sortRich-probe UX
Rich-probe renderers are activated by a per-row badge icon to the left of each probe value.Rich-probe renderers are surfaced as action-items ("View as …") in the existing sample context menu, one per compatible renderer, next to "Pin enclosing call" / "Step into". When activated, the renderer view opens in a floating modal anchored below the sample — the active menu flips to
position: relativeand the flex row top-aligns (scoped via:has(.modal-backdrop)) so the modal stacks cleanly below the menu rather than overlapping it. The modal has a floating×close button outside its top-right corner. To keep the sample's outline/background from wrapping the now-in-flow menu, visual fills (background-color,outline,border-radius,box-shadow) moved from.sampleto.value; specificity is preserved via.sample:has(.value.X) .valueselectors, so non-rich probes render identically todev.Infrastructure changes
Refractor & syntax rewrite plumbing (Rich Probes):
~inlineparameter onlift_syntax/term_to_seg— controls whether syntax rewrites use multi-line formattingcore_settingsplumbed throughProjectorBase.View.args→ProjectorView→RefractorViewSetSyntaxaction gainskindparameter for refractor-aware syntax rewriting (select term range, replace, preserve manual/ephemeral refractor state)Refractors.to_projectornow receives real syntax fromTermDatainstead of dummy whitespaceExpToSegment: newline before|>when not inline,project_table_iffor auto-projecting tables (with graceful fallback), reverse application parenthesization fixMakeTerm.for_projection:Anycase now returns the underlying exp for non-tuples (enablesproject_table_if)Refractors.mk_entryandZipperBase.add_manualgain~modelparameter to preserve state across rewritesZipperBase.update_refractor— applies updates to bothmanualsandephemeralsmaps so model state persists for auto-probesTableTransforms.apply_transformsextracted as a public function for testabilityElaboration & error plumbing (Auto-labeling, #2207):
elaborate_syntax: boolflag on theProjectormodule type (like the existingdynamicsflag)info.elaborated— elaborated sub-expression looked up by inner expression ID, passed to projectors at view timeView.t.error: boolflag, kept separate fromModel.statusso the upcomingViewCachecan key on status without view-time state leaking inerror: (model, info) => option(error)function onProjectormodule type (renamed fromdiagnose) for reporting render-time errorsCachedSyntax.projector_errorsmap computed alongside shape_map, plusrefresh_shapesrecomputing shapes and errors after statics update (gated onstatics !== old_statics)Exp.find_by_idutility moved fromProofHackstoExp.reContextMenu.Projectors.is_applicableconsults the elaborated form, so "Add Table" appears for auto-labeled lists;actions_datakeeps the current projector's kind available so "Remove Table" stays reachable once elaboration breaksMisc:
WebUtil.empty_hole_svg— helper for rendering empty-hole indicators in table headers without labelsNotes
devin7694d63e8b. Notable accommodations for upstream API drift:MakeTerm.from_zip_for_semandCachedStatics.initnow require~root(threaded throughProjectorPerform.goandCompositionViewcollapse helpers);ExpToSegment.Settingsgot a newproject_tablesfield added to derivation/test settings records.Test plan
make devcompiles cleanlymake test-quick)Place probe on table expression, enable "Rich Probes" — badge appears on probe valuesPlace probe on table expression, enable "Rich Probes" — dropdown menu shows "View as table" entryPlace probe on table expression — sample context menu shows "View as table" entry (rich probes always on)Click badge — modal table with interactive column menus (drop, rename, sort, etc.)Click "View as table" — modal table opens below the menu with interactive column menus (drop, rename, sort, etc.)Card renderer works on card/hand expressions(CardRenderer reverted)Settings toggles for "Tables" and "Rich Probes" work correctly"Tables" settings toggle works correctly🤖 Generated with Claude Code