Skip to content

Add JSON rubric editor and modernize grading UI with additive scoring - #16

Merged
stevevanhooser merged 29 commits into
masterfrom
claude/grader-revamp-ps-1-1-ufje79
Sep 5, 2026
Merged

stevevanhooser merged 29 commits into
masterfrom
claude/grader-revamp-ps-1-1-ufje79

Conversation

@stevevanhooser

Copy link
Copy Markdown
Contributor

Summary

This PR introduces a complete JSON-based rubric system for the vhlab grading toolbox, including a new interactive rubric editor, modernized grading UI with additive scoring and comment banks, and supporting infrastructure for loading/saving rubrics.

Key Changes

New Features

  • vhgraderubriceditor.m – Full-featured uifigure-based editor for JSON rubric files with:

    • Left panel: items list, assignment metadata (name, URL, points model), shared comment bank
    • Middle panel: comprehensive item editor (name, subfolder, filename, points, description, skills, code, autograder config, rubric criteria, per-item comment bank)
    • Right panel: totals and validation display
    • Toolbar: Open, Save, Save As, New Item, Delete Item operations
  • vhgradeloadrubric.m – Parses JSON rubric files into struct arrays compatible with the grading pipeline, extracting:

    • Assignment metadata (name, URL, points model, shared comment bank)
    • Per-item fields: name, subfolder, filename, points, description, skills, code, code files
    • Autograder configuration (response_name, vartest, anyvartest types)
    • Rubric criteria and per-item comment banks
  • vhgradesaverubric.m – Inverse of vhgradeloadrubric; writes struct arrays back to pretty-printed JSON

  • vhgradesandboxrun.m – Isolated code execution environment that runs student code in a function-scoped workspace, preventing pollution of the base workspace while capturing all created variables

UI Modernization

  • vhgraderesponsegui.m – Complete rewrite from legacy uicontrol to modern uifigure with:
    • Additive rubric scoring: checkboxes for each criterion, points awarded per selection
    • Dual comment banks: item-specific and assignment-shared, with points deltas
    • Free-text comment boxes for items not covered by the bank
    • Auto-computed points earned (still manually editable)
    • Cleaner layout with response display, rubric panel, and comment bank side-by-side

Core Grading Updates

  • vhgradequestion.m – Extended to support:
    • Rubric-based additive scoring (Rubric_awarded field)
    • Comment bank selection tracking (Comments_selected field)
    • Autograder score details (checks_passed, checks_points)
    • Integration with vhgradesandboxrun for isolated code execution
    • Backward compatibility with classic two-text-box grading

Testing & CI

  • test_vhgradeloadrubric.m – Validates JSON parsing, metadata extraction, autograder expansion
  • test_vhgradesaverubric.m – Round-trip tests for JSON serialization
  • test_vhgradesandboxrun.m – Verifies isolated execution, error handling, workspace isolation
  • test_vhgradequestion_auto.m – End-to-end autograder happy-path tests
  • fixtures/tiny_rubric.json – Test fixture with response_name and anyvartest examples
  • .github/workflows/matlab-tests.yml – CI pipeline for automated test runs

Implementation Details

  • Rubric criteria and comment banks use struct arrays with .name/.points and .label/.text/.points_delta fields respectively
  • Autograder checks are expanded from JSON into a Parameters struct array compatible with existing vhgradequestion logic
  • The sandbox runner uses onCleanup to guarantee directory restoration even on error
  • JSON schema supports nested comment banks (item-level + shared assignment-level)
  • All new code maintains backward compatibility with existing grading workflows

https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1

New:
- vhgradeloadrubric.m: reads a JSON rubric into the classic Parameters
  struct plus new Rubric / Comment_bank / Shared_comment_bank fields.
- vhgradesaverubric.m: writes the same shape back to JSON, for the
  forthcoming rubric editor.
- vhgradesandboxrun.m: runs student code in a function-scoped workspace
  so it never touches base; returns the resulting variables as a struct.

Changed:
- vhgradequestion.m: runs the student's Code through the sandbox and
  reads autograder variables from the returned struct. Autograder checks
  now record per-check pass/fail and (when the JSON provides per-check
  points) award partial credit up front. Rubric / Comment_bank are
  passed through to the GUI on grade.
- vhgraderesponsegui.m: rewritten as a uifigure with a rubric-criteria
  checkbox panel and an additive comment-bank checkbox panel (item bank
  first, then the assignment-shared bank). Selected criteria and comment
  deltas sum into an auto-computed Points earned that is still editable.
  Two free-text comment boxes remain for anything the bank does not
  cover. Falls back gracefully when no rubric/bank is supplied.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
A uifigure app that opens, browses, edits, and saves JSON rubric files
consumed by vhgradeloadrubric.

Layout:
- Toolbar: Open / Save / Save As / New item / Delete item.
- Left column: items list, assignment metadata (name, url, points
  model), and the assignment-wide shared comment bank.
- Middle column: editor for the selected item — name, subfolder,
  filename, points possible, description, skills, code, code files,
  the autograder spec (type dropdown; response_name field OR a checks
  table for vartest/anyvartest with per-check points), the rubric
  criteria table, and the item-specific comment bank.
- Right column: live totals per item plus a validation panel that
  flags items whose rubric criteria don't sum to points_possible.

Save/Save As write via vhgradesaverubric.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Tests (tests/): a self-contained JSON fixture (tiny_rubric.json) plus
four test files, all using MATLAB's matlab.unittest function-based
form so they can be picked up by runtests or matlab-actions/run-tests:

- test_vhgradeloadrubric   : metadata, item count, response_name and
                             {any}vartest translation, rubric parsing,
                             per-item and shared comment banks.
- test_vhgradesaverubric   : load → save → reload round-trip preserves
                             metadata, items, weighted checks, and the
                             response_name field.
- test_vhgradesandboxrun   : empty code, simple variables, error path,
                             base-workspace isolation, pwd restoration
                             (including on error), and non-leakage of
                             internal reserved names.
- test_vhgradequestion_auto: end-to-end all-checks-pass branch (which
                             bypasses the GUI) — verifies full credit,
                             grade caching, and no base-workspace leak.

Failing / partial-credit branches of vhgradequestion open the GUI and
would block a headless runner, so they are intentionally not covered.

runalltests.m collects and runs everything for interactive use.

CI (.github/workflows/matlab-tests.yml): runs setup-matlab and
run-tests on ubuntu-latest, uploads the JUnit report as an artifact.
Uses MATLAB's free CI license (public repo).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
CI (#16, run #1) surfaced a real bug: every
end-to-end vhgradequestion test failed with

    Output argument "ws" (and possibly others) not assigned a value
    in the execution with "vhgradesandboxrun" function.

Root cause: vhgradesandboxrun ran `eval(codeText)` in its own function
scope. When the item's Code contained `clear` (as PS 1.1's Q2 does —
"close all; clear; q2;") the clear wiped the sandbox function's own
ws/err/startDir locals, so on return MATLAB found the output arguments
unassigned.

Fix: move the eval and the workspace snapshot into a nested subfunction
(i_evalAndSnapshot). Because it is a separate function scope, a `clear`
inside the student code only wipes the subfunction's own locals — the
caller's ws/err/startDir/cleanup are safe. The subfunction rebuilds its
snapshot state after the eval so its own outputs are also robust.

Also added test_vhgradesandboxrun/testStudentClearDoesNotBreakOutputs as
a targeted regression test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
MATLAB rejects Layout.Column = [N N] with "must be a positive integer
or a 1x2 increasing array". The response_name edit field only needs
one column; use Column = 4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
The action buttons (Save / Full credit / Missing / Cancel) were four
stacked entries inside a 60px-high bottom row, which squeezed them to
invisibility on standard displays. Move them to their own dedicated
row 5 as a horizontal strip spanning both columns, and grow the fig
from 720 to 820 tall so the two free-text comment boxes and the
points panel get real room in row 4 as well.

Also give the points panel explicit padding/spacing and a fixed
80px editfield width so it doesn't collapse to one line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
…edit

vhgradeoverview(parentDir, items):
- Dashboard uifigure with an item-completion summary and a
  students × items uitable. Each cell shows "N of M" or "—" and is
  color-coded (red = ungraded, green ≥90%, yellow ≥50%, orange below).
- Toolbar buttons operate on the current selection:
    * Grade this cell         (single student × single item, force=1)
    * Resume item (ungraded)  (walk only ungraded students for the
                                selected item column)
    * Regrade full item       (force-regrade every student for the
                                selected item column)
    * Change directory        (uigetdir; rescans)
    * Refresh                 (rescan disk; useful after external edits)
- Each grading action writes a fresh summary.txt via vhgradesummary
  and then rescans so the table reflects reality.

vhgraderesponsegui: Save and Full-credit now refuse to persist unless
the grader has left at least one indication of a comment — a rubric
criterion checked, a canned comment ticked, or free text in either of
the two comment boxes. If none, a uialert explains and the Missing/0
button remains the correct choice for items the student didn't do.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
MATLAB's uitable addStyle takes (style, targetType, targetIndex),
not (style, row, col). Batch cell indices per color bucket and issue
one addStyle call per bucket — same visual, one call each instead of
n*m calls, and no more "Third argument must be 'cell', 'row', 'column',
or 'table'" error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Two fixes for the "Grade this cell does nothing" symptom:

1. vhgradeoverview: initialize selRow/selCol to 0 so the guard alert
   fires when the user hasn't clicked a cell yet. Update the toolbar
   selection label to prompt "(click an item cell in the table first)"
   in that state, instead of silently grading student 1 × item 1.

2. vhgradequestion: call figure(h) on the response GUI right before
   uiwait so it comes to the front. When launched from vhgradeoverview
   the new grader window was opening behind the overview and looked
   like nothing had happened.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
The action buttons used cached selRow/selCol values updated by
CellSelectionCallback. After a Save + refreshScan reset the table
Data, the callback did not always fire again on the next click in
R2024b uifigures, so the cached selection went stale and the buttons
either did nothing or acted on the previously-graded cell.

Fix: switch the table to SelectionType='cell', Multiselect='off', and
read tbl.Selection directly in a small liveSelection helper each time
a button is pressed. That way the buttons act on whatever is actually
highlighted in the table right now, regardless of callback timing.

Also swap CellSelectionCallback for SelectionChangedFcn (its modern
equivalent) purely for updating the label.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Two related fixes for cells/columns that felt like "nothing happened":

1. vhgradequestion now sets grade.Autograded_only = true on the two
   paths that save a grade and return WITHOUT opening the manual GUI
   (all autograder checks passed → full credit; or a response_name
   section was missing in response.md → 0 with an error comment).

2. vhgradeoverview:
   * Renamed and colored buttons so their purpose is unambiguous:
       - "Grade one cell (student × item)"
       - "Grade this item for all ungraded students"        [blue]
       - "Regrade this item for ALL students (force)"
     Each has an explanatory Tooltip.
   * After onGradeCell finishes, if the saved grade is flagged
     Autograded_only, pop an info uialert that says "saved without
     opening the manual grader — result: N/M — reason: …". So an
     auto-graded cell no longer looks like a dead click.
   * Split the top strip into a proper toolbar row plus a hint row
     showing the parent directory on the left and the current cell
     selection on the right (fixes an earlier aliasing mistake where
     pathLbl and selLbl pointed at the same label).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
itemKind(item) classifies each rubric item by what the grader will
actually see:
  resp        - written response only (response_name, no Code)
  code        - runs student Code and/or checks variables
  code+resp   - runs Code AND asks the grader to read a response
  manual      - grader must judge with no autograder or code

The tag prefixes the column headers ("[resp] Q1.1 Table of runs") and
every summary-panel line, and a legend row explains the tags. So a
grader can tell at a glance whether a question needs them to read
prose, look at code output, or both.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Shared helper:
- vhgradeitemkind.m: extracted the classifier so both the overview
  and the grader GUI use the same rules. Removed the duplicate local
  copy from vhgradeoverview.

Overview toolbar row 2 (new):
- "Refresh all summaries" walks every student folder and rewrites
  GRADING/summary.txt via vhgradesummary, with a uiprogressdlg.
- "Open selected student's summary" opens that student's summary.txt
  in the OS default text app (falls back to the MATLAB editor).
- "Export cohort CSV…" writes a wide CSV: one row per student, one
  column per rubric item plus totals and percent, at a location the
  user picks with uiputfile. Values are csvEscape'd.

Grader window:
- vhgraderesponsegui: tag the window title bar and the big item
  header with the kind, e.g. "Grading [code+resp] Q3.1-3.4 Examining
  graphs / advise your friend", so the grader can tell at a glance
  what they're being asked to look at.

Note on summary triggering: vhgradesummary was already re-run after
every Grade-cell and each iteration of Grade-column; there was no
button to regenerate it manually. The Refresh-all button covers the
"I edited things outside the GUI" case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
The pre-GUI code-preview msgbox called text2cellstr(inputitem.CodeFiles{i})
with a bare filename. That used to work because vhgradequestion cd'd
into the student's subfolder up front — but after the sandbox rewrite,
the cd lives inside vhgradesandboxrun and pwd is restored before the
GUI section runs. Result: for every anyvartest item with a Code file
(e.g. Q2.2 q2.m), grading errored with "Could not open file q2.m".

Fix: build the full path from vhgradedirname + grade.Subfolder +
filename, check isfile first, and put a friendly placeholder in the
code window if the student didn't include the file at all.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
When the autograder ran and some checks failed, vhgradequestion set
inputitem_alt.Comment_1_default to grade.Comment_1, which is a cell
array of per-check summary lines. vhgraderesponsegui then wrapped it
with char(), producing a 2×N char matrix, and splitLines fed that to
regexp — which only accepts a single char row, a cellstr, or a
string array. That crashed the grader window on partial autograder
credit (e.g. Q2.2 when one variable was missing).

Fix:
  * Don't wrap Comment_1_default / Comment_2_default with char() —
    hand them to splitLines as-is.
  * Teach splitLines to handle all five shapes (empty, char row,
    char matrix, cellstr, string array).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
When the sandbox raises, the grader now sees the error in two places:

1. The Code window (msgbox) that opens alongside the grader now
   starts with a "*** CODE ERROR ***" section listing the failure
   before the source of the file(s). Grader sees the error immediately.

2. Comment 1 (the first free-text box in the grader) is prefilled
   with the same message on its own lines instead of being flattened
   into a semicolon-separated single line. Because Save concatenates
   the free-text box into grade.Comment_1, the error naturally leads
   the final saved comment unless the grader edits it out.

The formatter (formatCodeError) uses the exception message and the
first stack frame ("At: name (line N)") when available.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
vhgradeassignment used to addpath(genpath(studentDir)) around each
student, then rmpath, so any helper files the student wrote (or the
data files their code loads) were found. My overview callbacks
skipped that dance and called vhgradequestion directly, so student
scripts errored on missing helpers even though the files were in
their repo.

Fix: introduce withStudentOnPath(parentDir, studentDir), which

  1. Removes every path entry under parentDir first (so an earlier
     student's helpers cannot shadow this one's).
  2. Adds genpath(studentDir) for the duration of the returned
     onCleanup handle.

onGradeCell and onGradeColumn both use it around each grading call,
in a try/catch so the cleanup runs on error too. The path is
restored (student folder rmpath'd) as soon as grading of that
student finishes, so nothing leaks between students or after the
overview session ends.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
When student code errored AND the item had an autograder, Step 3
built the checks summary and overwrote inputitem_alt.Comment_1_default
with it — clobbering the code-error prefill set by Step 1. As a
result the Additional Comments box only showed the checks summary
and the grader had to retype (or worse, screenshot) the error
message they saw in the code preview window.

Fix: compose the Comment 1 prefill from both pieces — the code-error
message first (each line preserved), a blank line, then the checks
summary. The error message now lands in the free-text comment box
and flows into the saved grade.Comment_1 (and therefore into
GRADING/summary.txt) unless the grader edits it out. Nothing new
about the msgbox — the error was already at the top of the code
window.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Grader window (vhgraderesponsegui):
- New "Insert canned comment" dropdown + "Insert → Comment 2" button
  in the Additional-comments panel. Picking a bank entry and clicking
  Insert appends the FULL comment text to the second free-text box so
  the grader can tweak it before Save. Delta stays with the checkbox
  behavior; the insert is for customising the wording only.
- New "Skip this student" button (closes window without saving,
  batch continues).
- Renamed / colored "Cancel" as "Cancel (stop batch)" — it now sets a
  vhgrade_cancelBatch flag on groot and closes. The OS window-close
  (X) is routed through the same handler so an accidental close
  cannot silently move on to the next student.
- Tooltips on every action button spelling out what each does.

Batch runners honor the cancel flag:
- vhgradeoverview.onGradeColumn checks the flag after each student
  and breaks with a uialert "Batch cancelled" toast. Resets the flag
  at start and end.
- vhgradeassignment now also checks the flag after each item and
  after each student, prints a stopped-by-user message, and resets
  the flag at start and end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
vhgradequestion:
- Snapshots findall(groot,'Type','figure') at entry and installs an
  onCleanup that closes only the newly-created figures on exit — via
  close(h,'force'), then delete(h) as a fallback. So any plot the
  student's code opened (or the msgbox code preview) is closed when
  grading of that item finishes, on every path (autograder short
  circuit, GUI save/cancel/error). Figures that existed before the
  grade — including the vhgradeoverview dashboard uifigure — are
  left alone.

vhgradeoverview.onGradeCell:
- Autograded_only cases no longer pop a uialert; they log a single
  line to the command window in the form:
    [vhgradeoverview] auto-saved (no UI needed) — <student> / <item>
    : N of M
      <comment_1 flattened to one line>
  So batch grading through auto-passing students doesn't require
  clicking an OK on every one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
msgbox renders its text as a static label, so the grader could not
select or copy the student's code. Replace it with a small uifigure
holding a uitextarea in the platform's fixed-width font, Editable
off. Editable off in uitextarea still permits selection and
Cmd/Ctrl-C, so the grader can copy the code (or the error message
at the top) into a message.

Interface is the same as before — makeCodeWindow returns the fig
handle, which vhgradequestion still delete()s after the grade
saves. The figsBefore/onCleanup snapshot in vhgradequestion already
covers this window if the grader ever leaves it lingering.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
vhgraderesponsegui.m uses the no-end-on-local-functions convention.
I added `end` on the three new locals (onCancelBatch, insertCanned,
ternaryStr). MATLAB rejects mixing the two conventions in one file:

  The function "ternaryStr" was closed with an 'end', but at least
  one other function definition was not.

Remove the ends on those three locals so the file loads again.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
A long comment bank (item + shared entries) could overflow the fixed
right-hand panel and the trailing entries were invisible with no way
to reach them. Make both the Rubric and Comment-bank panels scroll:

- Set Scrollable='on' on the enclosing uipanel and on its
  uigridlayout child.
- Give each row RowHeight 'fit' so every checkbox takes its natural
  height (previously equal '1x' rows squeezed everything).

Grader can now scroll the panels independently when a bank grows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Two related fixes for code items where the student's code doesn't run
or produces wrong values:

1. needsGui: For a code item with weighted autograder checks and no
   rubric, a partial failure previously left grade.CodeError=0 and
   needsGui=false — so the grade was saved as partial credit
   silently and the grader never got to see the code or override it.
   Track autograderNotFullyPassed in Step 3 and include it in the
   needsGui expression. Now any autograder run that didn't cleanly
   pass opens the grader window.

2. Code window: was opened unconditionally whenever needsGui fired,
   even for pure response items (uifigure with just a space in it).
   Only open it when the item actually has code files or a code
   error to display — haveAnyCode gates the makeCodeWindow call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
CloseRequestFcn is wired to onCancelBatch so hitting the OS window X
stops the batch (previously an accidental close moved on silently).
But close(fig) ALSO invokes CloseRequestFcn, so every Save / Full
credit / Missing / Skip call was routing through onCancelBatch too —
grade was saved but the batch stopped after each student.

Switch those handlers to delete(fig), which bypasses CloseRequestFcn.
Cancel still calls onCancelBatch (which itself uses delete). The X
button still fires CloseRequestFcn and stops the batch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Adds a WindowKeyPressFcn (onShortcut) that dispatches to the existing
ButtonPushedFcn callbacks:

  ⌘/Ctrl-S  → Save
  ⌘/Ctrl-F  → Full credit
  ⌘/Ctrl-M  → Missing / 0
  ⌘/Ctrl-K  → Skip this student
  Escape    → Cancel (stop batch)

Detects Cmd on macOS and Ctrl on Windows/Linux via the Modifier list
provided by the key-press event. Button labels are platform-aware
too — they render as "Save  (⌘S)" on Mac and "Save  (Ctrl+S)"
elsewhere, so shortcuts are discoverable without opening a help.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
With seven-column-wide rubrics the Total column ends up far from
the leftmost Student column, so reading which student a total
belongs to requires scrolling back. Append a second Student column
after Total, showing the same folder name, so a total is always
next to a student name no matter how wide the table gets.

liveSelection now recognises both Student columns and the Total
column as "not an item cell" and gives an appropriate hint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
Item cell content is short — "N / M" or "—" — but 'auto' column width
in R2024b uifigure uitables measures the header string ("[code+resp]
Q3.1-3.4 Examining graphs / advise your friend") instead, blowing
each column out to ~250 px. With 7 items that pushes the trailing
Student column past the visible area and the user has to scroll
right to see it.

Pin each item column to a fixed 105 px so seven items + Student +
Total + Student all fit comfortably in the 1300 px window with room
to spare. Column labels wrap or truncate within the header cell,
which is fine because the assignment metadata pane at the top of the
window carries the full item names.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
…nfirm

Wraps vhgradepost2github behind a button on the overview's second
toolbar row (styled orange like Cancel). The push is intentionally
hard to trigger by accident:

1. Prompt (inputdlg) for the GitHub repository prefix — defaults to
   "vanhooser-data-analysis-stats-workshop/", editable.
2. uiconfirm shows a warning-icon dry-run:
     * Lists the git commands that will run in each student folder.
     * Prints the example push URL that would go to the first
       student, so the user can double-check the prefix.
     * Options: Cancel (default) / Commit only (rehearsal) / Commit
       & push. "Commit only" runs the add+commit path with push=0
       so you can dry-run the commit before releasing.

While the loop runs, a uiprogressdlg tells the user the git output
is going to the Command Window. On completion a success alert
reports the number of student folders touched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdicP8VMU1mSYczRqZmAo1
@stevevanhooser
stevevanhooser merged commit c0f7333 into master Sep 5, 2026
2 checks passed
@stevevanhooser
stevevanhooser deleted the claude/grader-revamp-ps-1-1-ufje79 branch September 5, 2026 21:05
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.

2 participants