You are browsing as a guest. Sign up (or log in) to start making projects!

Ship #1 Changes requested

Are you smarter than AI, your friends, or alone?

Learnova is a study tool that only counts it as learned once you can say it back with no help. Multiple choice, then fill in the blank, then rebuild the explanation from pieces, then explain it cold, in your own words, with nothing to lean on. Most study tools stop testing you the moment you can recognize the right answer. This one starts there.

The new part is who you’re up against. Debate Mode puts you against an AI opponent that argues the other side of whatever you just explained, four speeches, then a real judge call scores who actually held their ground. Or skip the AI and go against a friend instead, live, same format, one shared link, no accounts. The room exists the moment two people are in it and nothing else.

Built this week: live 1v1 debate, real time, no database, a room is just two people and a shared connection. Rewrote how the grading talks to you, since a screen that quietly claims you contradicted a source that was never there does real damage to someone trying to learn. Killed the rating system twice over, first for hiding how different a study run and a debate round actually are, then for existing at all when neither needed to accumulate into a score. Rebuilt the whole color palette off the actual logo instead of picking colors that looked fine.

None of it is graded on vibes. The AI opponent isn’t told to be nice or mean, it’s told to actually argue, and the judge is a second, separate model call that never sees what the first one said about itself. The score is out of ten, not a made up elo number a language model invented on the spot, because a model asked to do arithmetic will hand you a plausible number whether or not the arithmetic happened.

Live at learnova.software. Open source. Built with Claude Code, over about five weeks of actual work, dates and all in the README because a repo that predates its own hackathon submission should say so plainly.

Try it against yourself first. Then a friend. Then, if you’re feeling something, the AI.

  • 4 devlogs
  • 19h
Try project → See source code →
Open comments for this post

5h 25m 30s logged

Learnova pre-ship devlog: live rooms, and the rating that stopped existing

Live 1v1 Debate shipped this session: two people, a four-character code, no accounts. The real decision was what a room actually is. Serverless functions don’t share memory between instances, so a Map tracking “does this room exist” lies half the time: the host creates it on one instance, the guest’s join lands on another, and the guest gets told the room doesn’t exist while the host sits inside it. The fix was to stop tracking existence at all. Presence on an Ably channel is the room.
Two bugs came out of testing that seriously. Only the host’s browser calls the judge, so the ballot was written entirely in the host’s frame, meaning both players read the host’s strengths and weaknesses in the second person. The judge now writes both ballots and mirrors them for the guest. Separately, a room was ending the moment the first person left instead of the last, kicking out the person who’d stayed.
This reverses the last devlog, twice. First, both elo modules got deleted: a single number going up and down implies a Round Mode run and a Debate round measure the same thing, and they don’t. That became a plain record instead. Then the record got deleted too. A debate ends on its verdict. A run ends on its own merit.
Results were also showing “+1,380 of 2,280,” a mental division at the exact moment someone wants one answer. It’s a score out of ten now. Anything above ten from an older build gets dropped, not converted, since converting needs a denominator that was never stored.
Cut this session: suggestion chips that turned “name a topic” into “pick one of these,” three motions that typewrote themselves onto the debate note for no functional reason, a /proof page that was a third way into a landing page whose whole point is two ways in, and thirteen dead exported symbols nothing was calling. The “get the ballot” button is gone entirely. The eighth speech lands and the round goes to the judge automatically.
The palette got rebuilt from the logo itself. The dark background is sampled directly off the asset, with every neutral swapped for the grey of equal relative luminance so the tuned contrast ratios held. Type warmed to the logo’s cream, the accent became the star’s gold.
Two prompt bugs got caught: the voice instructions were teaching third person inside their own example, and the ballot voice used bare letters “A” and “B” that occasionally read as “a flaw in A’s hanging out point.” Worst near miss: a case insensitive fix for the letter “A” also matched the article “a,” and shipped “with they cheaper alternative” through a live judge call before it was caught. A separate bug turned numbered lists into one run on line, fixed and property tested against 200 random chunkings.
Copy got more honest too. The unavailable state message used to claim nothing typed had been lost, which the app has no way to know. Bigger one: every AI call was failing with a raw “HTTP 402” shown to a student, telling them to retry something that couldn’t succeed, because the shared API key ran out of balance. That’s now a plain message saying it can’t be fixed by retrying.
The README’s status section had gone stale before, so it got corrected twice this session. First pass: a full eight speech live debate across two real browsers, timed, sub second speech sync, 1.6s to join, and a 15.2 second delay before a disconnect is detected, recorded honestly instead of rounded down. The judge call was stubbed that pass since the key had no balance. Second pass, after the key refilled: two real judge calls, two genuinely different ballots, no leaked letters. Still open: a judge call made from inside a live room and mirrored to the guest has only been checked against a stub, not the real thing.
Built with Claude Code. AI assisted commits carry Co-Authored-By trailers.

1
0
9
Open comments for this post

5h 46m logged

Debate Mode, and the source that did not exist

The biggest change this session was a second mode. Round Mode’s ladder tops out at saying a thing in your own words. Debate asks something else: hold a position for four speeches against an opponent trying to take it off you. Its own route, not a sixth round.
The rating is arithmetic and never the model’s. The judge returns a winner, a margin and ten dimension scores, and the elo change is computed in integer maths the model never sees. A model asked to update an elo returns a number that looks like arithmetic, and the failure is invisible because every value it gives is plausible.
Two tabs that never mix. Open debate is judged on whether the argument holds up. Tournament prep is judged against a tournament bar, where a dropped argument is conceded and 60 means mediocre, not good for a beginner. Format is required, never defaulted: a Public Forum ballot given to someone practising Lincoln-Douglas is worse than no ballot, because they will act on it.
A real user ran a topic-only session on Tritoflex, a spray-on rubber roofing compound, and was repeatedly told it was torch-applied. He caught it because he installs the stuff. A student studying something new would not have. Asked cold about an obscure term, the model returns a protein, an alpha helix, signal transduction, implicated in cancer. Not one word true, and none of it looks invented. That is the failure worth naming: not that it does not know, but that it does not say so.
The worst of it was not a missing disclaimer. In a topic-only session the Round 4 marking screen printed “Contradicts the source” under a heading reading “The notes”, when the student had pasted nothing and the server blanks every citation on the way out. Someone who wrote the true sentence was told they contradicted evidence that was never there. The app exists to close the gap between feeling like you know something and knowing it, and that screen widened it. It now says “the model disagrees”.
The header badge went from “AI-generated” to “AI, unchecked”. Authorship is the fact a student already has, since they typed the topic in. Nothing checked this is the one that costs them. “AI” stayed because the label outlives the moment: screenshots get shared.
Runs used to die with the tab: honest about the plumbing, wrong about the pedagogy, since retrieval practice works through spacing. The session already computed the thing worth keeping, a map of which ideas you can recognise but cannot say, showed it once, then threw it away. One standing per concept persists now, and the front door leads with what is still unsaid.
The method note, and it is the best yet: my first two boundary assertions both passed against the exact bugs they were written for. One compared trimmed pieces, which cannot distinguish a mid-word cut from a clean one, because trimming erases the evidence. The other tested a sample that had collapsed to one chunk, so there was no join in it to be wrong. Both were testing nothing, confidently, and only revealed themselves when I reverted the fix to watch them fail and they stayed green. A test you have not seen fail is not a test.
Closing an item from the last log: the green “strong” band is no longer unverified. It has been seen in a browser by someone who knew the answers, not by a driver answering option 1 every time.
The README’s privacy section had gone from cautious to false, because “nothing is stored beyond the current session” stopped being true the moment runs started leaving a record. Second time a claim here has drifted as the product changed, so anything that stores something now triggers a README check in the same commit. Still no accounts: standings are per device, and clearing site data erases them.
Built with Claude Code. AI-assisted commits carry Co-Authored-By trailers.

0
0
4
Open comments for this post

4h 58m 32s logged

Round Mode: deleting the score

The biggest change this session was subtraction. Round Mode had points, a running total, and a combo multiplier. All three are gone.
The argument against them was simple once I said it out loud. The headline number was the run time, and the fastest route through a round is to answer everything wrong immediately. So the number leading the results screen rewarded exactly the behaviour the app exists to catch. Points and combos were no better: computed from how fast and how often you were right, then displayed next to how fast and how often you were right.
What replaced them is a rating that weights every answer by how little help it had. Warm up counts once, Round 1 three times, Round 2 five times, Round 3 six times, and explaining a concept in your own words in Round 4 is worth 150. Harder questions weigh more. Speed is not in the formula at all, and two tests pin that down: identical answers at wildly different speeds produce an identical rating, and fast-and-wrong scores below slow-and-right. It shows as “+615 of 2,120”, because a bare number says nothing when a short session and a long one have different ceilings.
That is the thesis of the app as one number. The rating only goes up when the hint comes off.
Round 3 lost its distractors too. The chip tray used to include wrong words, making it a hunt for what to avoid rather than the assembly of a right sentence. Now the tray holds the sentence’s own pieces. Grading there stopped being pedantic about list order, with seven tests, four of which are cases that must still fail: “sugar traps light” for “light traps sugar”, a scrambled non-list, a missing chip, a changed verb.
Round 4 can no longer be skipped. There was a “stop here and see the results” button sitting immediately before the only unscaffolded moment in the session, which is exactly where bailing is most tempting and least useful.
The interface stopped hiding itself. The header used to empty during a round, on the theory that peripheral chrome competes with retrieval. True for one person at a desk, wrong for a room watching one screen. Locking the page to the viewport exposed two bugs, including a verdict that clipped off the bottom on short windows. The one line telling you the right answer was the line you could not read.
The run clock was a good bug. It measured wall-clock time since the run started, so it counted loading and between-round screens and disagreed with the final reported time. The header once read 3:20 against a reported 1:08. It is now built from the same splits the results use.
Trimmed the cold-open prompt after finding it asked the model to write explanation lines nobody reads. A/B against the real proxy across 10 topics cut median completion tokens from 397 to 266, a 33 percent reduction, on the one call every student waits on.
One method note worth keeping: I overrode setTimeout in the test harness to freeze auto-advance so I could screenshot a verdict, and it swallowed the harness’s own sleeps. The script hung and I captured the wrong state. Capture the real function before replacing it.
Closing an item from the last log: the assembled round is no longer unverified. The full ladder has been played end to end, in dev and against a production build.
Still unverified: the green “strong” band exists in tests but has never been seen in a browser, because my test driver answers option 1 every time and always lands in the middle band. That one needs a human who actually knows the answers.
Built with Claude Code. AI-assisted commits carry Co-Authored-By trailers.

0
0
28
Open comments for this post

3h 6m 33s logged

Round Mode: proving the scores were inflated, then making it sound like a game

Round Mode is Learnova’s core loop. You type a topic and play an escalating ladder of retrieval rounds, where the hint gets stripped away one rung at a time: multiple choice, then fill in the blank, then assemble the sentence from chips, then say it in your own words with nothing on screen to lean on. The research is clear that retrieval practice beats rereading, and that producing an answer beats recognising one. The ladder is that finding turned into a game.
The real work today was less fun. I noticed correct answers seemed to land in the first slot too often, so I wrote a script that generates questions through the actual route and counts where the answer ends up. Warm up was 5 out of 5 at index 0. Round 1 was 7/5/3/0. Every score the app had ever produced was inflated, because you could do well without reading the question.
The cause was the prompt itself: every JSON example I gave the model used “answerIndex”: 0, so it learned the pattern. Fixed by shuffling server side after generation, for every format with ordered options, including the chip tray, which had been shuffling in the browser where nothing could measure it. Re-ran across 197 questions and four topics. Chi-squared on every format now sits well under the 5 percent critical value.
Also killed repeated questions. Deduplication compares what a question asks for rather than its wording, so rephrasings do not slip through. When a topic runs dry, the generator gets told to ask about distinctions and consequences instead of drawing from the same pool again.
Then I drove headless Chrome over all 21 visual presentations and found three things no code review would have caught. Constellation stars stacked in a column because Tailwind emits .relative after .absolute. Doors swung 72 degrees, turning the correct answer edge on and unreadable at the exact moment it mattered. And a burst balloon faded to 0.25 opacity, which meant a wrong answer got softened by becoming illegible. That last one is a product failure, not a visual one, since the whole point is that feedback stays honest.
Then audio. The generated background music sounded like fans whirring, so it is gone, replaced with “8bit Dungeon Level” by Kevin MacLeod, credited in-app and in the repo under CC BY 4.0. Music and sounds are on by default, because the feel is the point, with a mute for anyone in a library.
Autoplay turned out to be its own small saga. No browser lets a page make sound before you touch it, so the track requests playback on mount, the rejection arms a listener, and the first click or keystroke starts it. Testing that surfaced a real bug I had previously reasoned away: React batches state, so if the very first thing you touched was the mute button, the listener fired while the state still said “on”, started the music, and paused it a frame later. A blip of music at precisely the person switching it off. Now set synchronously too.
Two lessons from the test rig that mattered more than the result. Calling element.click() does not grant user activation in Chrome, so my first run looked like a total product failure and was actually a test artifact. And running with a permissive autoplay flag meant the gesture fallback was never exercised at all. Testing under the real policy is the only version that tells you anything.
Still unverified: the assembled round in a live browser. The presentations are proven to render, but nobody has played the full ladder yet. That is tomorrow. Built with Claude Code. AI-assisted commits carry Co-Authored-By trailers.

0
0
27

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…