StudyFix · app design · 2026-08-10

Every generation state, success and failure

Left is what the app does today, read out of the code. Right is the proposal. Real tokens, real fonts, real strings — the failure text on the left is copied verbatim from app.js and server.py, not imagined.

delivered partial — the honest state, never green, never red failed

1 · Lecture upload

#drop · server.py:131–156

The app rejects on exactly three conditions: over 40 MB, not a PDF, no file. All three currently print the server's own words into a red box with one button that throws the upload away.

today
StudyFixupload
PDF only
← Start over
Two words from an HTTP handler. It doesn't say what you dropped, what it wanted, or that your file is still sitting there. "Start over" is the only exit.
proposed
StudyFixupload

Drop a lecture PDF

One lecture. About four minutes.

Slides are fine. Scans are fine.

That's a PowerPoint, not a PDF.

In PowerPoint: File → Export → PDF, then drop it here. Nothing else changes.

Choose another fileCardiology_L4.pptx · 8.2 MB
Names what it got, names the fix in one step, and the drop zone stays live — the failure doesn't clear the screen. Same three conditions, three specific messages.

2 · The generation run

#stages · service.py:52–63

Ten stages. Two are essential and kill the run if they throw — upload and graph extraction ("no graph = no lecture"). Everything else is wrapped in _try, so one artifact failing degrades that artifact and the rest still ships. The screen doesn't distinguish those two cases at all.

today · hard failure
StudyFixerror
ResourceExhausted: 429 Quota exceeded for quota metric 'Generate requests per model per day' and limit 'GenerateRequests per day per model per project'
← Start over
app.js:75 prints error straight through, and server.py:118 builds it as {ExceptionType}: {msg[:300]}. A medical student is being shown a Python exception class. The nine stages that already succeeded vanish with it.
proposed · hard failure
StudyFixstopped
Handing your lecture in
Reading it, slide by slide Gemini's daily limit is used up. Nothing was lost — your PDF is still here.
·Writing it up as one story
·Mapping how it all connects
Try againUse a different lecture
Technical detailResourceExhausted: 429 Quota exceeded…

Shipped without the Ref a4f21 code — that has to be generated where the exception is caught, server-side. Logged. The raw exception is kept behind the disclosure instead of thrown away.

The failure is marked on the stage that failed, in place. Prior stages stay visible so it's obvious how far it got. The reference code is what you paste to me instead of describing a screen.
today · one artifact degraded
StudyFixresult
Some parts couldn't be generated — the rest is here. SBAs.

…and the SBAs tab looks identical to a lecture that simply has none.

app.js:200 does w.split(':')[0], so the message the backend wrote is cut at the first colon and you get the word "SBAs." Red ground, too — but nothing failed for you; one of eleven outputs is missing.
proposed · one artifact degraded
StudyFixresult
Everything is ready except the exam questions.

The lecture itself came through — this is one output missing, not a failed run.

Not shipped: a scoped retry needs a per-artifact entry point on the server. Logged, not built — a button that silently re-ran the whole four-minute pipeline would be a worse lie than no button.

Amber, not red — this is the partial state. It counts what survived before naming what didn't, and the retry is scoped to the one artifact rather than the whole four-minute run.

3 · The Anki deck

#dl · index.html:404

There is no success state. #dl is a bare <a href="/download/{jobId}"> — the file downloads and the screen does nothing. This is the moment the product actually delivered, and it is the only moment with no design on it.

today
StudyFixresult
34 cards · 6 SBAs · 2 OSCE · 3 written·coverage 17/17 Download Anki deck

↓ click, and this screen is unchanged

No confirmation, no filename, no card count, no statement of what the deck excludes. You find out what you got by opening Anki.
proposed · delivered

Your deck is in Downloads

Aortic_stenosis.apkg · 412 KB
Cards34
Slide images included6
Facts carded17 of 17
Left out of the decknone
Start reviewing here Open in Anki
What was built, where it went, what it excludes, one next action. Reviewing here is primary — the deck is the export, not the product.
today · nothing to export
StudyFixresult
no valid cards to export

server.py:215 · shown only if you reach the URL; the button is simply hidden

apkg_ready false just sets display:none on the button. It disappears with no explanation — a dead control is better than a missing one, but a named one is better than both.
proposed · nothing to export

No deck yet

Every card this lecture produced failed cloze validation, so there was nothing to pack.

Cards attempted12
Rejected — malformed cloze12
Notes and flowchartready
Rebuild the cardsSee why each was rejected
The reasons already exist — coverage.card_warnings carries one string per dropped card. Today they're only visible if you find the Audit tab.

4 · Flowchart

app.js:855 · tab 3 of 11

A render error is caught and written into the panel as a raw exception message inside a <pre>. Same pattern on the Mindmap tab (app.js:252), which additionally dumps the whole mermaid source underneath it.

today
StudyFixresult
Master DocDisease IndexFlowchartMindmapCards
flowchart render error: Cannot read properties of undefined (reading 'nodes')
A JavaScript TypeError, verbatim, on the tab that is supposed to be the master notes. The other ten tabs are fine and nothing says so.
proposed
StudyFixresult
Master DocDisease IndexFlowchart !MindmapCards
The map didn't draw.
Every fact is still here — read them in Master Doc or Disease Index while we look at it.
Technical detailCannot read properties of undefined (reading 'nodes')
The tab carries a badge so the failure is visible from the other ten. The panel says what survived and where to go instead — the exception goes to the error tracker, not to the student.

5 · SBAs, OSCE, Written

service.py:151–161 · tabs 7–9

All three are _try-wrapped with [] as the default. When one fails you get an empty array, and an empty array renders exactly like a lecture that legitimately produced none. Absence and failure are the same picture.

today
StudyFixresult
CardsSBAsOSCEWrittenAudit
No SBAs were generated for this lecture.
True either way, and useless either way. You can't tell whether the lecture had nothing to ask about or the generator threw — and only one of those is worth retrying.
proposed
StudyFixresult
CardsSBAs !OSCEWrittenAudit
This did not come back.

It is not that the lecture had none — the step failed, and everything else is unaffected.

When a lecture genuinely has none, the same panel reads "Nothing here for this lecture." on the plain ground — different sentence, different colour, every time.

Shipped without a retry button, for the same reason as above.

One rule for all three tabs: empty-because-nothing and empty-because-failed never render the same. It needs the failed label kept per artifact, which generation_warnings already carries.