From 9961e89eaf37c512aca9ae131fa772173e0c3805 Mon Sep 17 00:00:00 2001 From: Aadarsh Padiyath Date: Sun, 16 Aug 2026 18:26:22 -0400 Subject: [PATCH] Fix CodeTailor clipboard copy including distractor Parsons blocks The static/backup Parsons example path sent raw editor markup (--- separated blocks with #distractor/#paired/#settled tags) as the "solution" text, which the copy-to-clipboard button then copied verbatim, producing non-compiling code. Filter distractor blocks and scaffolding tags out of the solution text while leaving the raw markup embedded in the puzzle widget's HTML untouched. Co-Authored-By: Claude Sonnet 5 --- bases/rsptx/book_server_api/routers/coach.py | 31 +++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/bases/rsptx/book_server_api/routers/coach.py b/bases/rsptx/book_server_api/routers/coach.py index 5431f2aaf..10daf4eaf 100644 --- a/bases/rsptx/book_server_api/routers/coach.py +++ b/bases/rsptx/book_server_api/routers/coach.py @@ -112,6 +112,11 @@ def extract_parsons_code(html_block): the code inside
...
-- the question prompt text lives in a sibling
and must be excluded, otherwise it gets treated as (and rendered as) one of the draggable blocks. + + This is the raw Parsons editor markup ("---"-separated blocks, some tagged + "#distractor"/"#paired"/"#settled"/"#tag:...;...;"), still needed as-is by + the Parsons puzzle widget (parsons.js parses this same markup client-side). + Use extract_parsons_solution() to get compilable solution code instead. """ pre_match = re.search( r']*class="parsonsblocks"[^>]*>(.*?)', html_block, flags=re.DOTALL @@ -134,6 +139,30 @@ def extract_parsons_code(html_block): return result +def extract_parsons_solution(parsonsexample_code): + """ + Given the raw Parsons editor markup (as returned by extract_parsons_code), + return only the compilable solution code: "---"-separated blocks tagged + "#distractor"/"#paired" are dropped entirely (they aren't part of the + correct solution), and scaffolding tags ("#settled", "#tag:...;...;") are + stripped from the remaining blocks. Mirrors the block-parsing logic in + runestone/parsons/js/parsons.js::initializeLines. + """ + blocks = parsonsexample_code.split("---") + clean_lines = [] + for block in blocks: + if "#distractor" in block or "#paired" in block: + # Not part of the correct solution -- drop the whole block. + continue + for line in block.splitlines(): + line = re.sub(r"#settled\b.*$", "", line) + line = re.sub(r"#tag:[^;]*;[^;]*;\s*$", "", line) + line = line.rstrip() + if line.strip() and line.strip() != "=====": + clean_lines.append(line) + return "\n".join(clean_lines) + + def _build_static_parsons_response( parsonsexample_code, parsons_attrs, personalization_level ): @@ -144,7 +173,7 @@ def _build_static_parsons_response( """ return ( - parsonsexample_code + extract_parsons_solution(parsonsexample_code) + "||split||" + parsons_html + "||split||"