Skip to content

Allow non-leaf nodes as correct answers - #16

Merged
aar0npal merged 9 commits into
mainfrom
feat/non-leaf-node-answers
Jun 9, 2026
Merged

Allow non-leaf nodes as correct answers#16
aar0npal merged 9 commits into
mainfrom
feat/non-leaf-node-answers

Conversation

@aar0npal

@aar0npal aar0npal commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Problem / Intent

Tree tests currently only allow leaf (final-destination) nodes as correct answers. This prevents study owners from setting a parent category as the intended destination — a valid scenario when the goal is to reach a section rather than a specific page.

Approach

The feature is gated by a per-study allowNonLeafAnswers toggle (mirroring randomizeTasks) so existing studies are entirely unaffected when off.

On the participant side, parent rows gain a "Select" button that reveals on hover/focus (desktop) or when the node is expanded (touch), with a gradient text-fade so long labels never collide with the button. The default click on a parent still toggles expand/collapse.

A selected_link column is added to tree_task_results and written at submission time (for both leaf and non-leaf answers). Scoring and Recalculate Stats now compare against this stored value directly, eliminating the resolver heuristic for any new result. Legacy rows (NULL) continue using the existing resolver unchanged.

A secondary fix lands in passing: the setup answer picker was computing non-leaf paths with a different sanitizer than the one tree-tab.tsx uses for leaf links (sanitizeTreeTestLink). This would have caused byte mismatches between owner-saved answers and participant selections — now both sides use the same function.

aar0npal added 2 commits June 8, 2026 23:59
- Schema: add `allow_non_leaf_answers` (treeConfigs) and `selected_link`
  (treeTaskResults) columns; migration 0007_happy_cammi.sql
- Types: add allowNonLeafAnswers to StudyFormData.tasks and TreeTestConfig
- Actions: thread allowNonLeafAnswers through saveStudyData / loadStudyData /
  loadTestConfig; persist selectedLink in storeTreeTaskResult; prefer stored
  selectedLink over the resolver heuristic in recalculateStudyResults and the
  answer-changed branch of saveStudyData (legacy NULL rows still use the resolver)
- Setup tasks tab: add allowNonLeafAnswers Switch; fix getAllPaths to use
  sanitizeTreeTestLink (resolves consistency bug) and emit non-leaf paths when
  the setting is on; drive checkPathInTree off availablePaths so picker and
  validator always agree; make leaf-only hint text conditional
- Participant tree (tree-test.tsx): when allowNonLeafAnswers is on, parent rows
  gain a sibling "Select" button revealed on hover/focus (desktop) and when
  expanded (touch) with gradient text-fade; default click still toggles;
  selectedLink is now persisted on every submission (leaf and non-leaf)
- results-actions: add comment clarifying expectedParentPaths derivation for
  non-leaf correct answers
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
usabilitree Ready Ready Preview, Comment Jun 9, 2026 11:35pm

@aar0npal
aar0npal marked this pull request as ready for review June 9, 2026 06:14
@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds optional support for non-leaf (parent category) nodes as correct answers in tree tests, gated by a new per-study allowNonLeafAnswers boolean. A selected_link column is also added to tree_task_results so future scoring bypasses the path-resolver heuristic entirely.

  • Schema & migration: allow_non_leaf_answers (default false, NOT NULL) added to tree_configs; selected_link (nullable text) added to tree_task_results. Existing studies are unaffected by default.
  • Participant UI: When the toggle is on, parent rows in tree-test.tsx gain a "Select" button; handleSelectNonLeaf constructs the canonical path with the same sanitizeTreeTestLink used for leaves, writes selected_link at submission, and correctly propagates pathTaken for directPathTaken scoring.
  • Setup / scoring: getAllPaths in tasks-tab.tsx now also enumerates non-leaf paths when the toggle is on (using sanitizeTreeTestLink consistently with the participant side), and saveStudyData / recalculateStudyResults both prefer result.selectedLink over the resolver for new results while gracefully falling back for legacy NULL rows.

Confidence Score: 4/5

Safe to merge for new studies; existing live studies that retroactively adopt non-leaf answers and trigger a stats recalculation on legacy results may see incorrect success flags for those old rows.

The feature is well-scoped and the happy path (new participants on new studies with the toggle enabled) is correctly implemented end-to-end. The one concern is collectValidLinks which exclusively gathers leaf node.link values — if a study owner switches an existing study to non-leaf answers and recalculates (or saves on a live study), the path-resolver fallback for selectedLink = NULL rows can never match a non-leaf expected answer, silently forcing those legacy results to unsuccessful. New results are not affected because selectedLink is written at submission time.

src/lib/treetest/actions.ts — specifically the collectValidLinks helper and the rescoring loops in saveStudyData and recalculateStudyResults that rely on it for NULL-selectedLink rows.

Important Files Changed

Filename Overview
src/components/tree-test.tsx Core participant UI change: adds handleSelectNonLeaf with correct sanitizeTreeTestLink path construction, visual state tracking, and pathTaken dedup via appendPath. The immediate onSelect call after setSelectedLink is intentional — dialog overlay prevents re-interaction with the tree while the confidence modal is open.
src/lib/treetest/actions.ts Ownership checks (eq(studies.userId, user.id)) are properly maintained in both loadStudyData and recalculateStudyResults. Scoring correctly prefers result.selectedLink over the resolver for new rows; legacy NULL rows fall back to resolveSelectedLink which only covers leaf links, which is acceptable since non-leaf selection was impossible before this PR.
src/app/(main)/treetest/setup/[id]/_components/tasks-tab.tsx getAllPaths now includes non-leaf paths when allowNonLeafAnswers is on, using sanitizeTreeTestLink consistently with the participant side. Validation UI clears correctly when the toggle changes. Minor: checkPathInTree takes an unused _nodes parameter (already deferred to availablePaths set) — cosmetic only.
src/components/tree-preview.tsx Preview component mirrors the participant UI faithfully. handleSelectNonLeaf only updates local selectedLink (no onSelect submission) which is correct for a preview context. The allowNonLeafAnswers checkbox syncs state bidirectionally with the tasks tab via onAllowNonLeafAnswersChange.
drizzle/0007_happy_cammi.sql Clean migration: allow_non_leaf_answers defaults to false NOT NULL (preserves existing study behavior); selected_link is nullable text (correct for legacy rows). SQLite treats false as integer 0 since 3.23.0.

Fix All in Claude Code Fix All in Codex

Reviews (3): Last reviewed commit: "style: fix oxfmt formatting failures in ..." | Re-trigger Greptile

Comment thread src/lib/treetest/actions.ts Outdated
Comment thread src/components/tree-test.tsx
- Restore ownership checks in loadStudyData and recalculateStudyResults
  that were accidentally committed from local uncommitted edits (P0)
- Set Navigation's internal selectedLink in handleSelectNonLeaf so the
  selected styling (green border/button, persistent fade) activates for
  non-leaf selections (P2)
@aar0npal

aar0npal commented Jun 9, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review

…s toggles

Validation badges previously persisted after flipping the toggle, showing
green for non-leaf paths that were no longer selectable by participants.
- Move the "Select" button before the expand/collapse chevron so the
  chevron stays flush right like every other row
- Mirror the non-leaf select behavior in the tree tab's Interactive Tree
  Preview, with an "Allow non-leaf nodes as answers" checkbox (two-way
  bound to the study setting) next to "What participants will see"
- Clear stale validation results via effect so toggling the setting from
  either tab resets validation badges
Focus-based reveal was unreliable on touch devices. Below the sm
breakpoint the button (and label fade) is now always visible; sm+ keeps
the hover/focus reveal.
The accordion auto-collapse shifts layout under the finger on tap, and
mobile browsers re-evaluate sticky :hover at the old touch point — a
child row (or its Select button) under that point lit up as if focused.
Gating hover styles behind @media(hover:hover) removes the artifact;
desktop behavior is unchanged.
@aar0npal

aar0npal commented Jun 9, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review

@aar0npal
aar0npal merged commit 6055a19 into main Jun 9, 2026
4 checks passed
@aar0npal
aar0npal deleted the feat/non-leaf-node-answers branch June 9, 2026 23:36
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.

1 participant