Separate document numbering from scored item numbering - #49
Merged
Conversation
`isDescription` marked a document as unscored and unnumbered, and the question counter, credit extraction, shuffle anchoring, and per-item attempt button already honored it. The item *indexing* did not: `getNumItems` and `getItemSequence` counted descriptions, while `extractActivityItemCredit` excluded them. Those two indexings feed different fields of the same `SPLICE.reportScoreAndState` message, and the host stores per-item state keyed by the scored numbering. With any description present, item state was therefore written to the wrong item. Split the two concepts: - Document sequence (`getDocSequence`/`getNumDocs`, renamed from `getItemSequence`/`getNumItems`, whose doc comment already said "documents") — every rendered document, descriptions included. Drives mounting, pagination, and hide/keep-live. - Scored item sequence (`getScoredItemSequence`/`getNumScoredItems`) — descriptions excluded, in render order. Indexes `doenetStates` and `itemAttemptNumbers`, and produces `item_updated` and `new_doenet_state_idx`. `getScoredItemSequence` filters `getDocSequence` rather than recursing separately, which makes the subsequence invariant structural. A description now reports nothing on state update: it holds no slot in `doenetStates` and its state is not persisted, so emitting a report would cost a save round trip for a row `loadState` ignores. `new_attempt_for_item` is unchanged — it is derived from `item_scores`, so it is already in original order and description-free. Descriptions inside a `select` are rejected rather than silently miscounted: `extractSelectItemCredit` scores a single-document select regardless of `isDescription`, and `propagateStateChangeToRoot` averages over all selected children without filtering. Saved state cannot collide with the new indexing: `createSourceHash` hashes `isDescription`, so a source containing a description necessarily has a different hash and starts fresh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3bUTUym3xhuXLegDyTNZ2
Collapse the redundant document-sequence membership check in `updateSingleState`: a stale id and a description both take the same no-op path through the scored item sequence lookup. Let `Activity.tsx` rely on the out-of-range index falling back rather than branching on the `-1` sentinel twice, and express the select and sequence counters uniformly. Add a test for a new item attempt on a document that is no longer in the activity, and pin the reported `item_scores` doc ids in the Cypress description test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3bUTUym3xhuXLegDyTNZ2
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3bUTUym3xhuXLegDyTNZ2
Rename `itemsRendered` to `docsRendered` in `Viewer` and correct the comments and JSDoc that still said "item" where a document (descriptions included) is meant. Add a test exercising the description/scored split over several shuffled attempts for a bare root description, a sequence of only descriptions, a description as the last child, descriptions on both ends, and a sequence nested in a sequence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3bUTUym3xhuXLegDyTNZ2
cqnykamp
pushed a commit
to cqnykamp/DoenetApps
that referenced
this pull request
Aug 20, 2026
A Description checkbox on each document card in the problem set editor, disabled (but still showing its state) once the content is read-only, and a distinct icon (`MdNotes`, slate) so descriptions read as structurally different from the problems around them — shape and colour, not colour alone. The client compiler honours `isDescription` for a direct child of a problem set and never repeats a description, mirroring the server twin so that every path compiles a problem set identically. Both the Description and Repeat controls explain themselves. The text rides on `aria-label`, not only the tooltip, so it is announced on focus rather than being hover-only, and both share the `HoverFocusTooltip` wrapper that owns the keyboard behaviour and overflow placement. Bumps `@doenet/assignment-viewer` to 0.1.0-alpha-18 (Doenet/assignment-viewer#49), which is published. That release separates the document sequence from the scored item sequence, so `doenetStates`, `itemAttemptNumbers`, and the reported `item_updated` are keyed by scored item — the numbering this app stores per-item state by. Descriptions depend on it: without it, item state for a problem set containing one is written to the wrong item. Requires the descriptions API, which requires the `content.isDescription` column. Not covered: the assigned-student persistence round trip (answer, reload, confirm the answer restores into the right problem) and the gradebook column count were not exercised against a real assignment. The viewer's own tests pin the numbering and reporting these depend on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
isDescriptionmarks a document as unscored and unnumbered. The question counter, credit extraction, shuffle anchoring, and the per-item attempt button all honored it already — but the item indexing did not.getNumItemsandgetItemSequencecounted descriptions as items, whileextractActivityItemCreditexcluded them. Those two indexings feed different fields of the sameSPLICE.reportScoreAndStatemessage:item_scores[].shuffledOrder— scored items onlyitem_updated/new_doenet_state_idx— position in the document sequence, descriptions includedThe host (DoenetApps) stores per-item state keyed by the scored numbering, and rebuilds
doenetStates/itemAttemptNumbersfrom rows ordered byshuffledItemNumber. So with any description present, item state was written to the wrong item, and the two arrays were sized and indexed inconsistently.Descriptions were never reachable from the host UI, so this never fired in production — but it blocks adding them, which is what DoenetApps wants to do.
Change
Split the two concepts that were sharing one sequence.
getDocSequence/getNumDocscheckHidden,checkKeepLivegetScoredItemSequence/getNumScoredItemsdoenetStates,itemAttemptNumbers,item_updated,new_doenet_state_idxThe first pair is a rename of
getItemSequence/getNumItems— no behavior change; the existing doc comment ongetNumItemsalready said "the number of documents that will be rendered".The scored ordering is the shuffled one, because the host rebuilds both arrays from rows ordered by
shuffledItemNumber asc.getScoredItemSequenceis implemented by filteringgetDocSequencerather than by a separate recursion, which makes the subsequence invariant structural and sidesteps theslice(0, numToSelect)branch.Other behavior:
doenetStatesand its state is not persisted, so emitting a report would cost asaveScoreAndStateround trip per keystroke for a rowloadStateignores. The host tolerates a missingitem_updated, so this is a no-op there.new_attempt_for_itemis unchanged. It is derived fromitem_scores, so it is already in original (unshuffled) order and already description-free. It legitimately differs fromitem_updated; a test pins that so it is not "fixed" later.selectnow throw instead of silently miscounting.extractSelectItemCreditscores a single-document select regardless ofisDescription, and the select branch ofpropagateStateChangeToRootaverages over all selected children without filtering.Vieweralready calls this inside a try, so it surfaces as the existing "Error in activity source" banner.updateSingleStateandgenerateSingleDocSubActivityAttemptnow return the state unchanged when the id is missing from the scored item sequence — whether because it is a description or because it is stale (an in-flight save from a just-regenerated attempt, or a select that re-picked its children).generateSingleDocSubActivityAttemptpreviously let such an id fall through and surface as an error banner.Activity.tsxfalls back toitemAttemptNumber1 and noanswerResponseCountsfor a description, instead of reading whateveritemAttemptNumbers[-1]gave (undefined→NaNattempts-left, masked only because the button is hidden).Compatibility
No change to
ExportedActivityState,isExportedActivityState, orsourceHash— only the length and meaning of two arrays narrows.Old saved state cannot collide with the new indexing:
createSourceHashhashesisDescription, andvalidateStateAndSourcerejects a hash mismatch, so any source containing a description necessarily has a different hash and starts fresh. Sources without descriptions compile and index exactly as before.Tests
npm run test— 57 pass (47 pre-existing unchanged, which is itself the compatibility evidence: every description-free fixture still asserts the sameitem_updated,new_doenet_state_idx, anditemAttemptNumbers).New unit tests use the existing
seqWithDes.jsonfixture:i, theshuffledOrderofscoredSeq[i]inextractActivityItemCreditequalsi + 1. This is the exact invariant the host'sshuffledItemNumberdepends on; it catches any future reimplementation that drifts.postMessage)item_updatedstrictly less than its document index and equal to its ownshuffledOrdergenerateSingleDocSubActivityAttemptbumps only its own scored index, andnew_attempt_for_itemmay differ fromitem_updatedselWithDes.jsonfixture →getNumScoredItemsthrowsgenerateSingleDocSubActivityAttemptfor a description, and one for a document not in the activity at all, are both identity-equal no-ops that set noerrMsgand post no messageNew Cypress component test (
ActivityViewer.descriptions.cy.tsx): page count includes the description, attempt buttons exist only for scored items, the dialog on the first problem reads "problem 1" (not 2), and reporteditem_updatedis 1 with the description absent fromitem_scores.Version
Bumped to
0.1.0-alpha-18. DoenetApps needs this published before its companion PR can be merged.