refactor(examlense): dedupe model-meta + extract shared UI components #14
Merged
Conversation
Phase 1 of the pages/ reuse roadmap (results + exams list):
- Reuse scoreRollup() in ByQuestionType, FiguresComparison, and AllTasks
instead of re-implementing the earned/max reduce.
- Extract <ScoreBar> (5 call sites) and <RollupRow>, plus formatScoreSummary()
for the shared "{count} tasks · {earned}/{max} ({pct}%)" label.
- Delete dead OverallScoreCard (no importers).
- TaskBreakdownTable: compute the intentional binary perfect/zeroed coloring
once instead of inlining the conditions in both row and cell.
- Consolidate exam-status label/style/sort-rank into one EXAM_STATUS_META
table (lib/exam/exam-status), consumed by ExamStatusBadge and the ExamsTable
Status sort; removes the duplicated ready→Draft collapse rule.
Behavior-preserving; typecheck, vitest (18), and build all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the 13 CRUD mutators plus sectionIdForTask/sectionIdForBlock/ unconfirmIfNeeded out of ExamEdit into a page-local useExamMutations hook, with shared withSaveStatus + optimisticListUpdate helpers for the plain- envelope paths. Add-paths, duplicateTask, addSection, deleteSection and persistReorder keep their bespoke bodies (behavior-preserving). Drops ExamEdit from 1442 to 1127 lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the duplicated section-grouping / taskLetter / figureLabel memos and the hash-based current-section bootstrap from ExamEdit and GradingView into a shared useSectionGroups hook (parameterized by includeEmpty) plus useCurrentSectionId (editor passes introGate). Add index-based slug helpers (sectionIndexSlug, UNASSIGNED_SLUG) to exam-helpers and a unit test for the pure grouping/letter logic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both effects computed state that can be derived during render (see React's "You Might Not Need an Effect"): - AllTasksList: resolve the active section slug during render (fall back to the first section when the stored slug is empty/stale) instead of syncing it in an effect — also removes an extra render pass. - ExamsTable: derive `safePage` by clamping to totalPages during render (drops the clamp effect); reset to page 1 on sort inside `onSort`, and on the Title search prop change via the render-time prev-value pattern (drops the reset effect). Pagination controls now read `safePage`. No behavior change. Typecheck (my files) and vitest pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the byte-identical inline-edit state machine and textarea/MarkdownView toggle duplicated in TaskCard (prompt) and ContextBlockCard (content) into a shared useInlineTextEdit hook and MarkdownEditField component. Preserves each card's distinct read-view surface (TaskCard's bordered surface vs the context block's card-less hover tint). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the destructive AlertDialog scaffold (task delete + convert, context and figure delete) and the near-identical block action dropdowns with shared ConfirmDeleteDialog and BlockActionsMenu components. TaskCard passes its Duplicate + Convert-type items as menu children. (ExamEdit's section-delete dialog is migrated in the following commit alongside its other edits.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the ~150-line renderBlockItem closure into a BlockItem subcomponent with a pure blockRowDescriptor helper (relocating taskTypeIcon), and migrate the section-delete dialog to the shared ConfirmDeleteDialog. ExamEdit's render now maps items to <BlockItem/>. Drops ExamEdit to ~857 lines. The working tree already carried the useExamBundle data-hook swap (roadmap item 19); it is interleaved with these import changes in ExamEdit, so it lands here together with the new use-exam-bundle.ts it depends on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the editing/draft + Enter-blur/Escape-revert machine duplicated by InlineTitle and SectionTitleInput into a useClickToEdit hook; both compose it. Each keeps its own placeholder and commit guard (SectionTitleInput still skips the patch when the trimmed name is unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract useExamRealtime wrapping the subscribeExam + invalidate effect shared
by ExamEdit and GradingView.
- Hoist preventNumberWheelChange to lib/utils (was copied in TaskCard and
TaskGradingPanel).
- Merge ExamEdit's handleJumpToTask + handleGoToScore into one
jumpToTask(taskId, { focusScore }).
GradingView also picks up the working tree's useExamBundle swap (roadmap item
19), interleaved with the realtime change; use-exam-bundle.ts landed earlier.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold solver-model-meta into a shared model-meta module, split the admin panels into MetricsTable/PanelMessage/StatCardGrid, and share ModelPickerList across the parser/solver start-exam steps. Net -300 lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
florivn
approved these changes
Jul 16, 2026
florivn
left a comment
Contributor
There was a problem hiding this comment.
Reviewed the refactor. The shared model metadata, UI components, and extracted hooks preserve the existing behavior across the editor, grading, and results flows. The added section-group tests cover the relevant ordering and grouping cases. CI is green and I found no blocking issues. Approving.
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.
No description provided.