U of I × MindRouter App Gallery

Degree Pathways

Education Built with OpenCode v1.18.21 Critic status: shipped

Degree Pathways

Plan a four-year path through prerequisites

See a degree's whole prerequisite web at a glance, then drag courses into semesters and get instant warnings about missing prerequisites, overloaded terms and credits left to graduate.

Degree Pathways running on a desktop screen, plan a four-year path through prerequisites Desktop · click to enlarge
Degree Pathways running on a phone, plan a four-year path through prerequisites
Phone · click to enlarge

What it is ✎ blurb written by the AI critic

Explore the prerequisite web for the University of Idaho B.S. in Computer Science (2026-2027 catalog). Hover a course to see what it needs and what it unlocks, then build a four-year plan by dragging courses into semesters. The plan re-checks prerequisites, credit loads, term availability and degree requirements as you go, and you can try what-ifs like a failed or transferred course, share the plan by link, or print an advising sheet.

Try this

  1. Click 'Load the catalog's sample plan', then hover CS 3383 on the Prerequisite map to see its required courses light up in red and what it unlocks in teal.
  2. In the Semester planner, hover CS 1121 and press F to mark it failed, watch the errors cascade, then click 'Add retake in Fall 2' and see them clear.
  3. Open Critical path and press 'Show on map' on the CS 4810 chain to trace the six-semester sequence to the senior capstone, including the Fall-only CS 4800.

Highlights ✎ written by the AI critic

  • A layered prerequisite map of 52 courses built from the real U of I catalog. Hovering a course highlights everything it requires and everything it unlocks.
  • An eight-semester board with optional summer terms and a transfer-credit row. Problem cards are flagged with a short reason, such as 'needs CS 2240 first'.
  • What-if planning: mark a course failed, add a retake, or mark it transferred in, and credits, requirements and errors update immediately.
  • A critical-path view listing the longest chains, the term each course falls in on your plan, and Fall-only courses. One click highlights a chain on the map.
  • Plans are saved in a shareable link, and a printable advising sheet has signature lines.

Who it's for

Undergraduates, academic advisors, department curriculum committees

Features

  • Interactive prerequisite graph (layered DAG layout) with hover to highlight everything a course unlocks and everything it requires
  • Eight-semester planner board with drag-and-drop course cards, credit totals per term, and summer terms
  • Real-time validation: prerequisite order, co-requisites, credit caps, missing requirements, with friendly explanations
  • 'Critical path' view showing the longest prerequisite chains (the courses that delay graduation if missed)
  • What-if: mark a course failed or transferred-in and watch the plan re-validate
  • Save/share plans via URL hash, and a printable advising sheet

Data & sources

Full data provenance ✎ written by the AI reviewer

Raw data sources — Degree Pathways

All files are unmodified HTML pages from the University of Idaho 2026-2027 General Catalog (https://catalog.uidaho.edu/), published by the University of Idaho Office of the Registrar (CourseLeaf catalog system). Retrieved 2026-09-13 with curl (HTTP 200). Each page footer reads "© 2026-2027 University of Idaho All Rights Reserved." These are public catalog pages; they are used here for non-commercial educational reference with attribution and links back to the catalog. Students must confirm requirements with their academic advisor — the catalog is authoritative.

Catalog year to cite in the app: 2026-2027 University of Idaho Catalog.

1. Degree program

FileURLContents
computer-science-bscs.htmlhttps://catalog.uidaho.edu/colleges-related-units/engineering/computer-science/computer-science-bscs/Computer Science (B.S.C.S.) — requirements, sample four-year plan, learning outcomes
  • Format notes for computer-science-bscs.html: Requirements are in the first <table class="sc_courselist"> (inside the requirementstexttab div). Rows are <tr class="odd|even ...">.

    • Area header rows: <span class="courselistcomment areaheader">, e.g. "Communications (two courses required)", "Computer Science First Year (three courses required)", "Upper-division Technical Elective Courses (four courses required …)", "Mathematics (four courses required …)", "Statistics (one course required …)", "Natural Science with Lab for Science and Engineering Majors (two courses plus their respective labs required … from two different disciplines …)".

    • Sub-header rows (areasubheader) group the science list: Biology, Botany, Chemistry, Environmental Science, Geography, Geology, Physics, Soils.

    • Course rows: <td class="codecol"><a ... onclick="return showCourse(this, 'CS 1120');">CS 1120</a></td><td>Title</td><td class="hourscol">4</td>.

    • Alternatives: <tr class="orclass ..."> whose code cell starts with "or " — an OR alternative to the row above (e.g. ENGL 3170 or ENGL 2020).

    • 11 science rows join a lecture and its lab with "&amp;" in the code cell, e.g. "BIOL 1150 &amp; 1150L" (lab number written without the subject), "BIOL 2500 &amp; BIOL 2550", "CHEM 1111 &amp; 1111L".

    • Ends with a "Total Hours 89" row; the text after says courses must total 120 credits for the degree.

  • Sample four-year plan is in <table class="sc_plangrid"> (degreemaptexttab): rows plangridterm give "Fall Term 1", "Spring Term 1" … "Spring Term 4"; course rows follow; plangridsum rows give "Hours 17" per term; final "Total Hours 120". It includes placeholder rows without a course code, such as "Humanistic and Artistic Ways of Knowing Course 3", "Science with Lab Course from approved list 4", "Major Technical Elective Course (UPDV Computer Science or Cybersecurity) 3", "Elective Course(s) 1", "American Experience Course 3", "International Course 3".

  • Course codes in link text usually use a non-breaking space (U+00A0), e.g. "COMM 1101"; inside showCourse(this, 'CS 1120') they have a normal space. Normalise U+00A0 to a space before matching.

  • U of I uses four-digit course numbers (e.g. CS 1120, MATH 1170, CS 4800).

2. Course descriptions (one page per subject)

Each is the catalog's subject course listing. Retrieved 2026-09-13.

FileURLSubjectCourse blocks
courses-cs.htmlhttps://catalog.uidaho.edu/courses/cs/Computer Science (CS)107
courses-cyb.htmlhttps://catalog.uidaho.edu/courses/cyb/Cybersecurity (CYB)30
courses-math.htmlhttps://catalog.uidaho.edu/courses/math/Mathematics (MATH)83
courses-stat.htmlhttps://catalog.uidaho.edu/courses/stat/Statistics (STAT)42
courses-engl.htmlhttps://catalog.uidaho.edu/courses/engl/English (ENGL)97
courses-comm.htmlhttps://catalog.uidaho.edu/courses/comm/Communication (COMM)32
courses-biol.htmlhttps://catalog.uidaho.edu/courses/biol/Biology (BIOL)90
courses-chem.htmlhttps://catalog.uidaho.edu/courses/chem/Chemistry (CHEM)68
courses-phys.htmlhttps://catalog.uidaho.edu/courses/phys/Physics (PHYS)66
courses-geol.htmlhttps://catalog.uidaho.edu/courses/geol/Geology (GEOL)84
courses-geog.htmlhttps://catalog.uidaho.edu/courses/geog/Geography (GEOG)72
courses-envs.htmlhttps://catalog.uidaho.edu/courses/envs/Environmental Science (ENVS)60
courses-plsc.htmlhttps://catalog.uidaho.edu/courses/plsc/Plant Science (PLSC)54
courses-rem.htmlhttps://catalog.uidaho.edu/courses/rem/Rangeland Ecology & Management (REM)48
courses-soil.htmlhttps://catalog.uidaho.edu/courses/soil/Soils (SOIL)50

Together these cover every subject referenced by the B.S.C.S. requirements page.

  • Format notes for the courses-*.html pages: Each course is a <div class="courseblock">.

  • Title: <p class="courseblocktitle noindent"><strong>CS&#160;2240 Computer Operating Systems (3 credits)</strong></p> — the space between subject and number is a non-breaking space (U+00A0 / &#160;); normalise it. Credit formats seen: "(3 credits)", "(1 credit)", "(1-16 credits, max 99)", "(3 credits, max 6)", "(1-3 credits)". For variable credits use the lower bound (or both bounds).

  • Description: <p class="courseblockdesc noindent">…</p>; often contains "Typically Offered: Fall and Spring." / "Typically Offered: Fall." / "Typically Offered: Spring." — useful for term-availability warnings.

  • Extras: <p class="courseblockextra noindent"><strong>Prereqs:</strong> …</p> (447 courses), <strong>Coreqs:</strong> (63), <strong>Prereqs or Coreqs:</strong> (1). Another courseblockextra may say "General Education: …". Course references inside are <a class="bubblelink code" onclick="return showCourse(this, 'CS 1121');">.

  • Prerequisite text is free English mixing AND / OR, e.g.

    • CS 2240: "CS 1121 and CS 1550"

    • CS 3383: "A grade of C or better in CS 2240 and CS 2230, and a grade of C or better in either CS 2100 or CYB 2200."

    • CS 4800: "CS 3383; ENGL 2020 or ENGL 3170, and Senior standing"

    • CS 1112: "MATH 1080 with a grade of C or better; or sufficiently high ACT, SAT, or Math Placement Test score …" A practical parse: split on ";" and on top-level " and " into AND-groups; within a group, courses joined by "or" / "either … or" are alternatives. Keep the original text too, so the UI can show it verbatim, and note non-course conditions such as "Senior standing", "Permission", placement scores, "grade of C or better".

  • Graduate-level (5000+) courses are listed too; the undergraduate degree only needs the courses it references plus their prerequisite chains.

License / terms

University of Idaho catalog content, © 2026-2027 University of Idaho, all rights reserved; publicly available on catalog.uidaho.edu. Credit it in the app's "Data & sources" section with links to the program page and the subject course pages, name the catalog year (2026-2027), and remind students to confirm with an advisor.

How it was built

Written by OpenCode 1.18.21 (opencode.ai) on qwen/qwen3.8-27b, served by MindRouter, the University of Idaho's on-premises AI platform.

16harness rounds
150.8minutes of harness work (2.5 h)
3,109lines of code
222KB, single-file app

Rounds by kind

build1
continue4
feedback8
critic3

Lines of code per file

body html145
app css348
app js2,163
scripts453

Critic scores ✎ scored by the independent AI critic, 1–10

visual 8 out of 10
usefulness 8 out of 10
correctness 8 out of 10
interactivity 8 out of 10

Independent audits ✎ by the AI critic

Data honesty: checked against raw data Authorship audit: every app-code commit is a harness round

Automated checks

Console no console errors No network 0 external requests Phone layout 0 px overflow Canvases no empty canvases Brand header header present No key leak no key in source

Checked by critic-verify.

Data spot checks ✎ written by the AI critic

  • CS 3383 details panel: 4 credits, 'Offered: Fall and Spring', catalog prereq 'A grade of C or better in CS 2240 and CS 2230, and a grade of C or better in either CS 2100 or CYB 2200.' -- data/raw/courses-cs.html courseblock has 4 credits, identical text, 'Typically Offered: Fall and Spring'; parsed as CS 2240 AND CS 2230 AND (CS 2100 OR CYB 2200): correct
  • Critical path 'CS 4810 minimum 6 semesters' (CS 1120>CS 1121>CS 2240>CS 3383>CS 4800>CS 4810) with a 'Fall only' badge on CS 4800 -- independent parse of the raw pages confirms each course is a hard prereq of the next (CS 4810<-CS 4800<-CS 3383<-CS 2240<-CS 1121<-CS 1120) and CS 4800 'Typically Offered: Fall': match
  • Sample plan term totals on the board 17/16/12/16/16/16/12/15 -- raw sc_plangrid 'Hours' rows 17,16,12,16,16,16,12,15, 'Total Hours 120': match
  • '33 upper-division credits' for the sample plan -- independent sum of raw plan-grid courses numbered >=3000 (STAT 3010 3, CS 3383 4, CS 3185 3, MATH 3300 3, CS 3600 4, CS 3195 3, ENGL 3170 3, CS 4145 4, CS 4800 3, CS 4810 3) = 33: match (the 4 upper-division elective slots are not counted)
  • Computed what-if: marking CS 1121 (3 cr) failed shows '117 / 120' planned and '3 credits left'; 120 - 3 = 117: correct. Adding the retake returns 120 / 120 and CS First Year back to Complete
Show all 6 spot checks
  • Titles and credits of all 117 embedded courses match an independent parse of the 15 raw catalog pages. Prereq text differed only by stray spaces before punctuation, now tidied in the UI. BIOL 2500 prereq groups are mis-parsed (optional science course). Sources and the 2026-2027 catalog year are cited in 'Data & sources' with links, plus an advisor disclaimer; no synthetic figures
Build notes ✎ written by the AI reviewer

DATA: 11 of 12 existing raw catalog pages were reused after validation (titles, 2026-2027 catalog year, course-block counts). courses-geog.html was truncated (no </html>) and was re-downloaded. The pages for ENVS, PLSC, REM and SOIL, which the B.S.C.S. page references, were added. data/raw/SOURCES.md was written with exact URLs, formats (including the non-breaking space in course codes, lab pairs and the plan grid) and terms, then committed as 'data: raw downloads'. I never edited src/, scripts/, data/embed/ or index.html. All app code came from OpenCode rounds via run_round.sh, and no TAMPER GUARD message ever appeared.

  • HARNESS BEHAVIOUR: OpenCode 1.18.21 with qwen3.8-27b repeatedly got stuck in a loop, running the same read-only inspection command (python3 -c or grep over the data) over and over and never editing a file. Round 1 (build): 166 identical runs in 34 min; killed.

  • Round 2 (continue, same prompt): exited after 93 s with no changes.

  • Round 3 (continue): the prompt said the exploration was already documented in SOURCES.md and set a file-writing order. It built the first full app in 9 min, but none of the three views was ever visible.

  • Round 4 (feedback, 14 items): looped 164 times; killed.

  • Round 5 (continue, same prompt): looped; killed.

  • Round 6 (continue, prompt with anti-loop guidance): looped 23 times, then OpenCode exited.

  • Round 7 onward: splitting FEEDBACK.md into small front-end-only batches worked (round 7's log ended in garbage text, but its edits were coherent).

  • Round 9 (data-model batch): looped 51 times; killed.

  • Rounds 10-13: succeeded (5-10 min each) once the prompt banned all data inspection, allowed only 'python3 scripts/build_data.py' and 'python3 build.py --check', and FEEDBACK.md quoted the current JSON structure and the exact catalog sentences with the intended AND/OR reading.

So the harness did all implementation, but only with heavily specified reviewer feedback. It never got the prerequisite and requirement semantics right on its own. Kills were my judgment on visible loops (identical commands, no file changes), not the strict no-log-growth rule. Round kinds: 1 build, 4 continue, 8 feedback (the maximum). Each round's feedback-item count and verify summary are in buildlog.json.

TESTING: after each app-producing round I ran verify_app.py and my own Playwright probes on port 18901 (probe*.py in the runs dir). They used real mouse hover and touch taps on SVG nodes, HTML5 drag-and-drop, the F/T/remove/Move buttons, the retake button, share-link reload in a new tab, print media and PDF, and 390 px phone viewports. I checked the screenshots each time.

The final app is polished and genuinely useful: truthful OR/placement prerequisites and a correct critical path on real catalog data. Status is needs-work rather than shipped because the what-if 'Add retake' button still fails silently and the details-panel transfer button is mislabelled; there was no feedback round left to fix them. FEEDBACK.md was removed and the deletion committed as 'driver: remove FEEDBACK.md'. Final screenshots: final-desktop.png (map hover) and final-phone.png (map tap).

Round-by-round timeline

Every harness round, in order. The prompts and FEEDBACK.md were written by the AI reviewer; the app code was written by the harness. Expand a round to see the exact prompt and the FEEDBACK.md the harness received.

    1 build 34.2 min · 1 file changed, 107 insertions(+) Show prompt

    Prompt given to the harness:

    Read AGENTS.md, SPEC.md and BRAND.md in this directory. Then build the complete app they describe, step by step: first write scripts in scripts/ that turn the raw data in data/raw/ (see data/raw/SOURCES.md — the U of I 2026-2027 catalog page for the Computer Science B.S.C.S. plus the subject course-listing pages) into compact JSON in data/embed/ and run them; then write src/body.html, src/app.css and src/app.js; run python3 build.py --check and fix any error it reports. Implement every required feature in SPEC.md with a polished, modern, responsive University of Idaho design. Do not stop until python3 build.py --check succeeds and every feature is implemented.
    

    No feedback file was sent in this round.

    2 continue 1.6 min · — Show prompt

    Prompt given to the harness:

    Read AGENTS.md, SPEC.md and BRAND.md in this directory. Then build the complete app they describe, step by step: first write scripts in scripts/ that turn the raw data in data/raw/ (see data/raw/SOURCES.md — the U of I 2026-2027 catalog page for the Computer Science B.S.C.S. plus the subject course-listing pages) into compact JSON in data/embed/ and run them; then write src/body.html, src/app.css and src/app.js; run python3 build.py --check and fix any error it reports. Implement every required feature in SPEC.md with a polished, modern, responsive University of Idaho design. Do not stop until python3 build.py --check succeeds and every feature is implemented.
    

    No feedback file was sent in this round.

    3 continue 8.8 min · 7 files changed, 5358 insertions(+), 7 deletions(-) Show prompt

    Prompt given to the harness:

    Read AGENTS.md, SPEC.md and BRAND.md in this directory. Then build the complete app they describe.
    
    Nothing has been built yet: scripts/ does not exist, and data/embed/ and src/ are still empty stubs. Two previous attempts spent all their time re-inspecting the raw HTML and never wrote a single file. Do NOT explore the raw pages at length — data/raw/SOURCES.md already documents their exact structure (course blocks, the non-breaking space in course codes, credit formats, Prereqs/Coreqs text, the B.S.C.S. requirements table and the plan-of-study grid). Use at most three quick commands to confirm what you need, then work in this order and write each file before moving on:
    1. scripts/ — a Python 3 (standard library) script that turns the B.S.C.S. requirements, its sample four-year plan and the referenced courses (code, title, credits, prerequisites, co-requisites, typical terms, original prerequisite text) from data/raw/ into compact JSON in data/embed/. Run it. If a prerequisite sentence is hard to parse exactly, keep its original text and extract the course codes it mentions instead of getting stuck.
    2. src/body.html
    3. src/app.css
    4. src/app.js
    5. python3 build.py --check — fix any error it reports.
    
    Implement every required feature in SPEC.md with a polished, modern, responsive University of Idaho design. Do not stop until python3 build.py --check succeeds and every feature is implemented.
    

    No feedback file was sent in this round.

    5 continue 17.9 min · — Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    

    No feedback file was sent in this round.

    6 continue 2.8 min · — Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    
    Important — two previous attempts at this feedback got stuck re-running the same read-only inspection command (grep / python3 -c over data/embed/courses.json) dozens of times and never edited a file. You already have everything you need: FEEDBACK.md, data/raw/SOURCES.md, scripts/build_data.py, data/embed/*.json and src/*. So:
    - Use at most five inspection commands in total, and never run a command you have already run.
    - Start editing immediately and fix the items in order, beginning with item 1 (the views never become visible) — that is a small fix in src/app.js.
    - After finishing each item, save the file(s) and run python3 build.py --check before moving to the next item, so progress is never lost.
    - For items 2–4, change scripts/build_data.py so the JSON carries prerequisite AND-groups of OR-alternatives, requirement-area rules and plan placeholder slots, re-run it once, then update src/app.js to use them.
    Do not stop until every item in FEEDBACK.md is fixed and python3 build.py --check succeeds.
    

    No feedback file was sent in this round.

    14 critic 7.0 min · 4 files changed, 612 insertions(+), 186 deletions(-) Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    
    Strict working rules for this round (earlier attempts got stuck running the same inspection command dozens of times and never edited a file; the rounds that followed these rules succeeded):
    - These items only need changes in src/app.js, src/app.css and src/body.html. Read those files and edit; do not read or inspect scripts/, data/raw or data/embed.
    - The only shell command you may run is `python3 build.py --check` — run it after each item.
    - Work through the items in order (1 to 6) and save after each one.
    

    FEEDBACK.md the harness received ✎ written by the AI reviewer:

    Reviewer feedback — critic batch 1 (tested in headless Chromium, desktop 1440×900 and phone 390×844)

    The app is in good shape: the hover highlighting on the map, the sample plan, the move menu, transfer row, share link and the printed advising sheet all work, with no console errors. Fix exactly these six items, in order, and keep everything else as it is. All of them are changes in src/app.js (and a little src/app.css). None needs scripts/ or data/.

    Current state shape (for reference, do not inspect data files): state.plan[termId] = [ {code, status, retake} | {slot:true, name, hours} ], state.status[code] = "failed" | "transferred" | null. Term ids in order: f1 s1 su1 f2 s2 su2 f3 s3 su3 f4 s4, plus the special list state.plan.transfer.

    1. The details-panel button and the "Unlocks" chips do nothing (mouse and touch)
    • Map → click the CS 3383 node → in the Course details panel click the transfer button: nothing happens (the Transfer credit row stays empty). Same for the "Unlocks" chips: click CS 1121, then click the CS 2240 chip → the panel still shows CS 1121. Cause: clicking a node gives it keyboard focus; pressing the mouse on the panel button blurs the node, and the node's blur listener in renderGraph() calls renderDetail(), which replaces the panel HTML between mousedown and mouseup, so the click is lost. Expected: In the node blur listener, do not re-render the detail panel when focus is moving into #dp-detail (check e.relatedTarget and document.getElementById("dp-detail").contains(e.relatedTarget)), and never re-render it if it already shows the focused course.

    • The transfer button in renderDetail() reads "⇄ Mark transferred in" when the course is not transferred and "Undo transfer" when it is. The hover version in renderDetailFor() must use the same label logic (today it always says "Mark transferred in", and renderDetail() wrongly says "Transferred in").

    • Clicking a chip focuses that course (panel shows it, map highlights it).

    • In the course catalog tray, the small card button should read "⇄ Mark transferred" (not "⇄ Transferred in").

    2. Overloaded terms are not reported when slots are involved

    Sample plan → Move CS 3383 from Fall 3 to Spring 3. The Spring 3 header turns red "20/18", but the validation list has no overload warning and the summary says "0 / 0" errors/warnings. Section 3 "Credit caps" in validate() sums only course codes. Expected: use termCredits(t.id) (courses and slots) so the warning "Spring 3 is overloaded: 20 credits (cap 18)." appears whenever a header is red. Same for summer terms (cap 9).

    3. Catalog quotes have stray spaces before punctuation

    The panel shows Catalog: "A grade of C or better in CS 2240 and CS 2230 , and a grade of C or better in either CS 2100 or CYB 2200 ." and MATH 1170 shows MATH 1170 );. When displaying c.prereq (and c.coreq) in renderDetail() / renderDetailFor(), tidy the text first: replace /\s+([,.;:)])/g with "$1", /\(\s+/g with "(", /\s*\/\s*/g with "/", and collapse multiple spaces. Expected: …CS 2230, and a grade of C or better in either CS 2100 or CYB 2200."

    4. "Add retake" does not work: the retake copy is also marked failed
    • Sample plan → Semester planner → hover CS 1121 (Spring 1) → click F. The list shows "CS 1121 is marked failed in Spring 1 … [Add retake in Fall 2]". Click it: a second CS 1121 card appears in Fall 2, but it is also styled failed, all six "needs CS 1121" errors and four "at risk" warnings stay, the button disappears, and the planned credits jump to 123 / 120. The only way out is un-clicking F on the original. Cause: failure is stored per course code in state.status[code], and every check does e.status || state.status[e.code], so the retake (status null) falls back to "failed". findPlacement(code) also only ever finds the first copy. Expected model: Store failure on the plan entry: entry.failed = true. Use state.status only for "transferred". When loading an old share link where state.status[code] === "failed", set failed = true on the first placed entry of that code and delete the status.

    • Give every board card data-term and data-idx, and make its F, T, Move and × buttons act on that exact entry (not on findPlacement(code)), so the F on the retake card toggles only the retake.

    • Add helpers: attempts(code) = all placed entries of the code with their term index; passIndex(code) = the term index of the earliest attempt that is not failed (−1 if transferred in, null if none); isFailedCourse(code) = the code has attempts and none of them passes and it is not transferred.

    • Prerequisite checks in validate(), satisfiedSet(), isBlocked() and the graph's ✗ badge use these helpers: a prerequisite is met for a course in term T when passIndex(p) !== null && passIndex(p) < index of T (same-term rules stay as they are).

    • Section 4 (failed courses): report "CS 1121 is marked failed in Spring 1" with the "Add retake in …" button only while the course has no passing attempt. Once a retake exists in Fall 2: dependants placed after Fall 2 are fine (their errors and blocked outlines disappear immediately); dependants placed in or before Fall 2 get one error: "CS 2100 (Fall 2) needs CS 1121 — its retake is in Fall 2; move CS 2100 to a later term."

    • The failed card keeps its pink ✗ style; the retake card has a small "retake" tag and normal styling.

    Check: sample plan → F on CS 1121 → Add retake in Fall 2 → errors remain only for CS 2100 (Fall 2); CS 2230, CS 2240, CYB 2200 (Spring 2) and later courses are no longer errors or at-risk.

    5. A failed course still counts as credit and as a completed requirement

    Sample plan → F on CS 1121: the summary still reads "120 / 120 planned credits", "0 credits left", and the checklist still says "CS First Year — Complete". Expected: a failed attempt earns nothing. Planned credits, credits left, upper-division credits, the progress bar, the requirements checklist and the print sheet count only entries that are not failed (use one helper, e.g. earnedSet()). After F on CS 1121: "117 / 120", "3 credits left", and "CS First Year — Missing — still need CS 1121 (failed — add a retake)". After adding the retake in Fall 2: back to "120 / 120" and "CS First Year — Complete".

    6. Warnings are shown about 2,000 px away from the course that caused them
    • Sample plan → drop CS 3383 into Fall 1. The CS 3383 card in Fall 1 looks completely normal; the three "CS 3383 needs … in an earlier term" errors only appear in the list below the board, about 2,170 px further down the page. Expected: Every board card that has an error gets a raspberry outline and a one-line reason under its title in small text, e.g. "needs CS 2240, CS 2230 first" (warnings: gold outline and the short reason, e.g. "typically Fall only"). Build a map issuesByCode in validate() (store code on each issue) and use it in renderBoard().

    • Add a compact status bar directly above the board (inside .dp-plan-grid's board column, before the Transfer credit row): "✓ No problems found" in teal, or "⚠ 3 errors · 1 warning — show list" in raspberry, where "show list" smoothly scrolls to #dp-validation. Keep it updated on every change.

    15 critic 6.4 min · 5 files changed, 432 insertions(+), 262 deletions(-) Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    
    Strict working rules for this round (earlier attempts got stuck running the same inspection command dozens of times and never edited a file; the rounds that followed these rules succeeded):
    - These items only need changes in src/app.js, src/app.css and src/body.html. Read those files and edit; do not read or inspect scripts/, data/raw or data/embed.
    - The only shell command you may run is `python3 build.py --check` — run it after each item.
    - Work through the items in order (1 to 9) and save after each one.
    

    FEEDBACK.md the harness received ✎ written by the AI reviewer:

    Reviewer feedback — critic batch 2 (tested in headless Chromium after round 14)

    Round 14 worked well. The details-panel button and chips now work, and overloads count slots. Catalog quotes are tidy. A failed course no longer earns credit, and a retake clears the downstream errors. The status bar above the board is useful too. Fix exactly these nine items, in order, and keep everything else as it is. All are changes in src/app.js, src/app.css and src/body.html. None needs scripts/ or data/.

    1. Flagged board cards show no reason text

    Sample plan → drop CS 3383 into Fall 1. The card gets the raspberry outline, but no reason line appears under its title. Cause: inside renderBoard() there is an inner function shortReason(code) that shadows the top-level shortReason(msg). The card code calls shortReason(<message>), which runs the inner function, finds nothing and returns null. Delete the inner function so the top-level shortReason(msg) is used. Expected: under "Software Engineering" the card reads "needs CS 2240 first" (or all its missing prerequisites), in small raspberry text on one line (use white-space: nowrap; overflow: hidden; text-overflow: ellipsis).

    2. Move and drag on a retake card move the failed original instead
    • Sample plan → F on CS 1121 (Spring 1) → "Add retake in Fall 2" → on the retake card in Fall 2 click Move → Summer 1. Result: the failed Spring 1 card moves to Summer 1 and loses its failed mark, while the retake stays in Fall 2. Dragging the retake card does the same. Cause: openMoveMenu(btn, code, slot) and the board card dragstart only pass the course code. The drop and menu handlers then call placeCourse(code, …), which uses findPlacement(code) (the first copy) and pushes a fresh {code} without the failed/retake flags. Expected: Board cards: dragstart sets "entry:" + cardTerm + ":" + cardIdx; tray cards keep sending the plain code.

    • Add moveEntry(fromTerm, fromIdx, toTerm). It splices that exact entry object out of state.plan[fromTerm] and pushes the same object into state.plan[toTerm], keeping failed and retake. Use it for drops of entry: data and for the Move menu. Pass cardTerm and cardIdx to openMoveMenu, whose "Remove" uses removeEntry(cardTerm, cardIdx).

    • Check: after the steps above, Spring 1 still shows the pink failed CS 1121, Summer 1 shows the retake card, and the "CS 2100 needs CS 1121 in an earlier term" error disappears, because the retake is now before Fall 2.

    3. "Undo transfer" puts the course in the wrong term

    Sample plan → Map → click CS 3383 → "⇄ Mark transferred in" → "Undo transfer". CS 3383 lands in Summer 2, which also causes a "typically offered Fall and Spring, not Summer" warning. It came from Fall 3. Expected: every "mark transferred" path records where the card came from: {code, status:"transferred", from:"f3"}. The paths are the details-panel button, the T button, the Move menu's "Transfer credit" and dropping on the Transfer row. Every undo path (details-panel "Undo transfer", T on the transfer card) puts the course back in from when present, otherwise in suggestTerm(code), skipping summer terms. Use one pair of helpers, transferEntry(termId, idx) / untransfer(code), instead of the five copies of this logic.

    4. The "requirement areas" summary card wraps onto three lines

    With the sample plan the card reads "7 complete · 3 planned of 10" in large type, wrapping to 2 lines on desktop and 3 on a phone. Expected: the big number is "7 / 10" and the label stays "requirement areas complete". When planned > 0, add a small muted line under the number: "+3 planned". Also color the errors/warnings number raspberry when there are errors, and gold when there are only warnings.

    5. The requirements checklist makes the planner page very long

    The checklist takes about 500 px before the board starts, and the planner page is about 4,000 px tall on desktop. Expected: at widths ≥ 900 px, lay the checklist rows out in two columns (display:grid; grid-template-columns: 1fr 1fr; column-gap: 32px on a wrapper around the rows, heading stays full width). Keep one column on phones.

    6. Printed advising sheet: empty first column

    Each year table on the print sheet starts with an empty column (a blank <th></th> and <td></td> in every row, including the totals row). Remove that column in renderPrintSheet() and label the totals row cells like "17 cr".

    7. The map opens un-fitted and hides the capstone courses
    • On first load the map is at 100 % zoom and shows only columns "No prerequisites" to "4 steps". CS 4800 and CS 4810 are off-screen, and a visitor sees a partial web. On a phone, "Fit" leaves an ~800 px empty white box under a tiny graph. Expected: The first time the map is shown, call fitGraph() (inside requestAnimationFrame so clientWidth is known).

    • Desktop: .dp-map-scroll height max(520px, calc(100vh - 160px)).

    • Phones (window width < 520 px): the first zoom is 0.6, not a full fit (text would be unreadable). Show a one-line muted hint above the map: "Swipe sideways to explore — Fit shows the whole map".

    • After any zoom, if the scaled SVG is shorter than the box, shrink the box height to the SVG height + 2 px (no empty area).

    8. Critical path view is a static list
    • It shows the chains, but nothing connects them to the plan or the map. Expected, in renderCritical(): Under each course chip show a tiny line with the term where that course is planned ("Fall 3"), "transfer", "failed" (in raspberry) or "not planned" (muted). Use the earliest attempt that is not failed.

    • If a chain course's offered is exactly "Fall" or exactly "Spring" (e.g. CS 4800 is Fall), add a small gold badge "Fall only" on its chip, and one sentence under the chain heading: "CS 4800 is offered in Fall only — if it slips, CS 4810 waits a full year." (name the next course in the chain).

    • Add a "Show on map" button in each chain header. It switches to the map tab, stores the chain in state.critChain, and gives the chain's nodes and the edges between consecutive chain courses the class is-crit (CSS for .node.is-crit and .edge.is-crit already exists). All other nodes get is-dim. It also scrolls the chain's first node into view and shows the chain's last course in the details panel. renderGraph() re-applies state.critChain after re-rendering. Esc or clicking empty map space clears it. Hovering a node still works.

    9. Map columns are sorted alphabetically, so edges cross a lot
    1. In layoutGraph() each column is in alphabetical order. The first column lists 17 mostly-optional science courses (BIOL, CHEM, GEOG, GEOL …) above the CS and MATH courses, and edges criss-cross the whole map. Expected (crossing reduction, keep everything else about the layout): Column 0: required courses (REQUIRED_SET) first, then the others; within each group order by area (cs, math, comm, nat, other — see areaClass()), then by code.

    2. Forward sweep, columns 1 … MAX_LAYER: sort each column by the average row index of the course's prerequisites in earlier columns. A course with none keeps its current relative order. Break ties by the same area/code rule.

    3. Backward sweep, columns MAX_LAYER−1 … 0: sort by the average row index of the course's dependants in later columns. Courses with no dependants keep their current row index as the key.

    4. Run one more forward sweep, then compute graphGeom x/y exactly as today (columns vertically centered).

    Check: in the fitted map the CS core chain CS 1120 → CS 1121 → CS 2240 → CS 3383 → CS 4800 → CS 4810 reads roughly as a horizontal band, and the optional science courses sit together at the top or bottom.

    16 critic 2.0 min · 5 files changed, 407 insertions(+), 156 deletions(-) Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    
    Strict working rules for this round (earlier attempts got stuck running the same inspection command dozens of times and never edited a file; the rounds that followed these rules succeeded):
    - These items only need changes in src/app.js, src/app.css and src/body.html. Read those files and edit; do not read or inspect scripts/, data/raw or data/embed.
    - The only shell command you may run is `python3 build.py --check` — run it after each item.
    - Work through the items in order (1 to 4) and save after each one. Do not stop before item 4 is done: the last round stopped early and the final items were never started.
    

    FEEDBACK.md the harness received ✎ written by the AI reviewer:

    Reviewer feedback — critic batch 3 (tested in headless Chromium after round 15)

    Round 15 fixed a lot, and I verified all of it in the browser. Flagged cards now say "needs CS 2240 first", and Move/drag on a retake moves the right card. Undo transfer returns a course to its original term, the "7 / 10 +3 planned" card fits, the checklist uses two columns, and the print tables have no empty column. This is the last batch: fix exactly these four items, in order, and keep everything else as it is. They are changes in src/app.js, src/app.css and src/body.html only. The last two items from batch 2 (critical path, column ordering) were not started, so they are repeated here with more detail.

    1. transferEntry and untransfer are defined twice

    src/app.js contains two identical copies of function transferEntry(termId, idx) and function untransfer(code) (right after moveEntry). Delete the second copy of each.

    2. The map still opens un-fitted
    • fitGraph() and adjustMapBoxHeight() exist, but nothing calls fitGraph() on first show. On load the map is at 100 % and only the columns "No prerequisites" to "4 steps" are visible. Expected: In setView(v), for v === "map": after renderGraph(); renderDetail();, if !state.mapFitted, set state.mapFitted = true and call requestAnimationFrame(fitGraph).

    • src/app.css: on screens ≥ 900 px, .dp-map-scroll { height: max(520px, calc(100vh - 160px)); }.

    • src/body.html: directly above .dp-map-wrap, add <p class="dp-map-hint">Swipe sideways to explore — Fit shows the whole map.</p>. Style it small and muted, and show it only below 520 px (display:none otherwise).

    Check: reload at 1440×900 → all eight columns are visible without pressing Fit, from "No prerequisites" through CS 4810 in "7 steps".

    3. Make the Critical path view interactive
    • renderCritical() currently prints a static list. Change it as follows (keep "minimum N semesters"): Each chain course chip becomes <span class="dp-chain__node"><span class="dp-chain__code">CS 4800</span><span class="dp-chain__term">Fall 4</span></span>. The term line comes from passIndex(code): a term index gives TERMS[i].name, −1 gives "transfer", and null gives "failed" (raspberry) when attempts(code).length > 0, otherwise "not planned" (muted). Style .dp-chain__term as a tiny second line (0.68rem).

    • If course(code).offered is exactly "Fall" or exactly "Spring", add <span class="dp-chain__season">Fall only</span> inside the chip (small gold pill). For the first such course that is not the last in the chain, add one sentence under the chain heading, e.g. <p class="dp-chain__note">CS 4800 is offered in Fall only — if it slips, CS 4810 waits a full year.</p> (name the next course in the chain).

    • Add a button <button class="btn btn--ghost btn--sm dp-chain__show" data-chain="…">Show on map</button> in each chain header (store the chain as the codes joined by "|"). On click: state.critChain = codes; state.focus = null; setView("map"); then call applyCritChain().

    • New applyCritChain(): if state.critChain is set, add is-crit to every .node whose data-code is in the chain, add is-dim to all other nodes, and add is-crit to every .edge whose data-from/data-to are consecutive courses in the chain. Scroll the chain's first node into view (scrollIntoView({block:"nearest", inline:"start"})) and show the chain's last course with renderDetailFor(lastCode). The CSS for .node.is-crit and .edge.is-crit already exists.

    • Call applyCritChain() at the end of renderGraph() and at the end of applyHighlight(code) when code is null, so hovering and leaving a node keeps the chain highlight.

    • The existing Esc handler and the click on empty map space also set state.critChain = null and remove is-crit/is-dim.

    Check: Critical path → "Show on map" on the CS 4810 chain → the map tab opens with CS 1120, CS 1121, CS 2240, CS 3383, CS 4800 and CS 4810 outlined in leather-brown with dashed connecting edges, everything else dimmed. Esc clears it.

    4. Order each map column to reduce edge crossings
    1. layoutGraph() builds byLayer from GRAPH_CODES, which is alphabetical. Column 0 lists 17 mostly optional science courses (BIOL, CHEM, GEOG, GEOL …) above CS/MATH, and edges criss-cross the map. Between building byLayer and the for (var l = 0; l <= MAX_LAYER; l++) geometry loop, reorder each byLayer[l] array: A helper areaRank(c) returns 0 for REQUIRED_SET[c] courses whose areaClass(c) is "cs", 1 for other required courses, 2 for "math", 3 for "comm", 4 for "nat" and 5 for "other". Sort column 0 by areaRank, then by code.

    2. Keep a map rowOf[code] = the course's index in its column, updated after every sort.

    3. Forward sweep, l = 1 … MAX_LAYER: sort byLayer[l] by the average rowOf of the course's prerequisites (prereqsOf[c]) that are in earlier columns. With no such prerequisites, use its current rowOf. Break ties by areaRank, then code.

    4. Backward sweep, l = MAX_LAYER − 1 … 0: sort by the average rowOf of the course's dependants (dependentsOf[c]) that are in later columns. With none, use its current rowOf.

    5. One more forward sweep (step 3), then the existing geometry loop runs unchanged.

    Sort a copy with a stable comparator and store the result back into byLayer[l]. layoutGraph() is called on every render, so keep it cheap (52 nodes, three sweeps). Check: in the fitted map the CS core courses sit together in a band, and the optional science courses are grouped at the bottom of each column.

What the agent was asked

The full specification the harness received, rendered from the original Markdown.

Read the specification

Degree Pathways — Plan a four-year path through prerequisites

Category: Education Who it is for: Undergraduates, academic advisors, department curriculum committees

What it should do

See a degree's whole prerequisite web at a glance, then drag courses into semesters and get instant warnings about missing prerequisites, overloaded terms and credits left to graduate.

Data

Course numbers, titles, credits and prerequisites for one real U of I program — e.g. the B.S. in Computer Science — taken at build time from the public U of I catalog (catalog.uidaho.edu), with a clear note naming the catalog year and reminding students to confirm with their advisor. Keep the data in an editable JSON block so a department could swap in its own program.

Raw downloads (if any) are in data/raw/ with data/raw/SOURCES.md describing where each file came from. Transform what the app needs into compact JSON in data/embed/ (write a small script in scripts/ to do it, so the transformation is reproducible). Every dataset the app shows must be credited in a visible "Data & sources" section of the app, with links. If a dataset is illustrative/synthetic, label it clearly as such in the UI — never present made-up numbers as real University of Idaho figures.

Required features

  • Interactive prerequisite graph (layered DAG layout) with hover to highlight everything a course unlocks and everything it requires

  • Eight-semester planner board with drag-and-drop course cards, credit totals per term, and summer terms

  • Real-time validation: prerequisite order, co-requisites, credit caps, missing requirements, with friendly explanations

  • 'Critical path' view showing the longest prerequisite chains (the courses that delay graduation if missed)

  • What-if: mark a course failed or transferred-in and watch the plan re-validate

  • Save/share plans via URL hash, and a printable advising sheet

Quality bar

This app is going into a University of Idaho gallery that shows faculty, staff and students what they can build with AI coding agents. It must look polished and modern (think a well-designed data product, not a homework demo), feel alive (smooth transitions, responsive hover/selection states, animated where it helps understanding), and do real, useful work — every control should change something meaningful. It must work well on a laptop and on a phone. Include a short "How to use" hint for first-time visitors and a helpful empty state wherever the user must supply data.

Known limitations

Reported by the independent critic. ✎ written by the AI critic

  • BIOL 2500's prerequisite ('BIOL 1150, BIOL 1150L and either CHEM 1101 and CHEM 1101L or CHEM 1111 and CHEM 1111L') is still parsed into the wrong AND/OR groups by scripts/build_data.py. It is an optional science course, so the effect is minor.
  • The upper-division credits card excludes the four 'Major Technical Elective (UPDV)' slots: the sample plan shows 33 where the planned total including those slots is 45.
  • When a course sits in the same term as its retake, the error is the generic 'CS 2100 needs CS 1121 in an earlier term.' It does not name the retake term. The error is accurate, just less specific than it could be.
  • In column 0 the crossing-reduction sweep interleaves optional science courses with required courses instead of grouping them at the bottom. Same-span edge crossings still fell from 42 to 15.
  • When the map is fitted to a 1440 px desktop, node labels are about 7 px (readable, but small). On phones the map opens at 60% zoom and needs horizontal swiping. Touch has no drag-and-drop; Move and tap-to-place replace it.
  • Minor: adding the 'Elective Course(s)' slot from the tray always gives it 2 credits, taken from the last matching placeholder. The two-column checklist rows misalign when one side has a sub-line. renderGraph re-adds a document keydown listener on every render, with no visible effect in tests.