Commit graph

907 commits

Author SHA1 Message Date
Daniel
f3e4e6df4f merge: a restarted modification is retried, not regenerated
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
2026-09-16 23:39:59 +02:00
Daniel
4448688102 fix: a restarted modification is retried, not regenerated
recoverResourceJobs marked every interrupted job failed with "The server
restarted while this was being written. Generate it again." It matches all
kinds, so someone whose modification was interrupted was told to generate —
which makes a new resource rather than retrying the edit they asked for, and
leaves the deck they were changing unmodified. The message is the only guidance
they get, so it now names the action that actually helps.
2026-09-16 23:39:59 +02:00
Daniel
39272f17c8 merge: modifying a resource is a job
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 6s
2026-09-16 23:28:42 +02:00
Daniel
d2a06b0fcf feat: modifying a resource is a job, the same as generating one
Modify held the request open for a library search, a PubMed search, a web
search and a restating model call. That is minutes, and a browser gives up
first — Firefox abandons a non-streaming fetch at five minutes, the same
failure generating was moved off the request to fix in ef574edd. The server
carried on and saved the result while the person watched an error, and closing
the tab killed the work outright.

POST /my-resources/:id/refine now records the request and answers 202 with the
job, exactly as /generate does. The writing moved into refineResource(), which
the job runner dispatches to by kind; the job list, the five-second polling,
the restart recovery and the three-in-flight cap are all the work they already
did, unchanged. Ownership is checked again inside refineResource because the
resource can be deleted while the job waits.

The page follows the job instead of the response. Reporting is unchanged — the
unchanged reply, what was seen and what was searched — it is only said from the
job list now, so it still reaches the person who asked for it after a reload.
2026-09-16 23:28:42 +02:00
Daniel
894ce251bb merge: the workflow image model is saved, not just accepted
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Docker Build / Build Docker image (push) Successful in 12s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
2026-09-16 23:08:44 +02:00
Daniel
0fd25deb50 fix: the workflow image model is saved, not just accepted
The PUT handler read budget and fallbacks and never looked at body.model,
so picking a different model, pressing Save and refreshing put the old one
back. The frontend had already been fixed to send it for both workflows; only
the write was missing, which is why the card reported "Saved" and the database
kept the previous value.

image_model is now written when the field is present. Blank is a real value for
my_resources ("use the Clinical Assistant's model"), so an empty string is
stored rather than skipped, and only an absent field leaves it alone.

The fallback de-duplication compared against the saved primary rather than the
one this request is setting, so a model promoted to primary and left in the
chain in the same save survived as its own fallback — a paid retry against the
same refusal.
2026-09-16 23:08:33 +02:00
Daniel
87c69ce151 revert: no new tests were asked for with the slide-cap change
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
The existing coverage moves with the constant: the review test asserts
against review.MAX_SLIDES + 1, and the browser suite already checks the slide
count the form sends. Removed so the change is exactly the four lines.
2026-09-16 17:43:30 +02:00
Daniel
36f2652648 test(e2e): the browser proves the slide count reaches sixty
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
The caps were raised to the schema's own limit, so the suite asserts it where a
person would: the form's max is 60, and 60 typed reaches the route as '60'
rather than being clamped back. 36 passed on the rebuilt stack.
2026-09-16 17:36:55 +02:00
Daniel
1a491d7367 feat: a deck up to sixty slides is generated, reviewed and modified
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 50s
Forgejo Docker Build / Build Docker image (push) Successful in 7s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 8s
Twenty was the review ceiling while the form allowed thirty, so a 21-30 slide
deck was written, stored and then silently never reviewed — and modifying one
above twenty was shown only its first twenty pages while the prompt claimed
one image per slide. The three numbers are the schema's own limit now, so a
deck that can be asked for can also be stored, reviewed and edited: the form's
max, both generation clamps and MAX_SLIDES are all 60.

Measured at that size before this change: 45 pages cost 28,796 prompt tokens
and 2.2s to review, a 46-page deck with a fault on page 24 was found and
re-laid correctly, and a 44-slide deck used ~5,900 output tokens of the
16,000 ceiling.
2026-09-16 17:21:03 +02:00
Daniel
b683356458 feat: DeepSeek is asked without thinking unless the caller asks for it
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 52s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
The switch was opt-in, and the callers that said nothing were the ones that
paid for it: a 16,000-token deck reasoned its whole budget away and wrote
nothing (reasoning_chars=51573), the four 2,000-token reviews of that deck did
the same, and a 16,000-token revision reasoned for a minute and a half before
writing a word — which is past the point a browser waits for a request. Every
clinical route (encounters, notes, chart review, the visits, take-home, and
the rest) passes no reasoning option at all, so all of them were in that
position.

Silence now means off for DeepSeek, in one place rather than in fifteen, and
an explicit effort still means what it says: a task that wants reasoning asks
for it. The retry after a reasoning-starved reply no longer asks for 'low'
either — on DeepSeek that means thinking on, which is how a starved call came
back starved with four times the budget. It adds room instead.

Other providers are untouched, the Groq Qwen profile included.
2026-09-16 04:11:35 +02:00
Daniel
643e7f4204 test(e2e): the landing specs tie the job to the click, not to a poll count
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 6s
The page polls the job list as soon as it opens, so a stub that finished the
job on the second poll had already finished it before Generate was pressed —
and there was no in-flight to landed transition left to announce, which is the
behaviour these two specs exist to pin. The job now stays running until the
click is recorded, and the run is green: 34 passed.
2026-09-16 04:05:12 +02:00
Daniel
f0cc537df1 test(e2e): the browser suite follows generation into its job, and can log in again
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 51s
Forgejo Docker Build / Build Docker image (push) Successful in 8s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 18s
Two faults kept every spec in this file from running, both in the same code
path and both from the SSO-only change. The fixtures minted a session by
shelling out to `docker compose exec`, which cannot work from inside the
Playwright container — it has no docker CLI and no socket — so all seventeen
specs (both viewports) died at the auth fixture before touching the page. The
harness now mints both sessions on the host and passes them in, and the seed's
connection banner is no longer mistaken for the token: it prints before it,
so the token is the last line. The host-side docker path stays as the fallback
for `npx playwright test` run directly on the host.

The specs then move to what generation is now: the click is answered with a
job, so the assertions follow the job list. A generation in flight is listed
and survives a reload, a job that lands reloads the library and says what it
was written from, a job that fails says why, and what was searched for is
reported when the job lands rather than when the button is pressed.
2026-09-16 04:02:10 +02:00
Daniel
ef574eddcb feat: a resource is generated as a job, and DeepSeek writes it without thinking
Some checks failed
Forgejo Docker Build / Build Docker image (push) Blocked by required conditions
Forgejo Docker Build / End-to-end (browser) (push) Blocked by required conditions
Forgejo Docker Build / Root app tests (push) Has been cancelled
Writing a resource held the request open for as long as it took: a library
search, one or two long model calls, a review, then figures. Measured end to
end that was six minutes on 2026-09-16 (00:40 to 00:46:16, resource 53), and
Firefox abandons a request that has said nothing for five — the browser
reported "NetworkError when attempting to fetch resource" while the server
carried on and saved the deck anyway, so a generation that worked looked
like a failure and left no status line.

The request now records what was asked and answers 202; the work runs on the
server as a job; the page lists what is being written, what landed and what
failed, polls while anything is in flight, and reloads the library when one
lands. Several can run at once, a reload loses nothing, and a boot pass marks
jobs stranded by a restart as failed rather than spinning for ever.

The same generation also ran with DeepSeek's thinking on, which is what made
it take minutes rather than seconds: the 16,000-token write spent the whole
budget reasoning and returned an empty reply (completion_tokens=16000,
reasoning_chars=51573), which fired the automatic retry at four times the
budget, and the 2,000-token reviews of that deck starved the same way four
times over. Thinking is now off for the writing, the review of it and a
revision — DeepSeek's own field, sent by the model wrapper. Other clinical
routes are deliberately untouched and keep the provider default.

The review inherits the writer's rule rather than hard-coding it, so a task
that wants reasoning can still ask. Migration 1781500000000_resource-jobs.js
adds user_resource_jobs; the container entrypoint applies it before the app
starts.
2026-09-16 03:53:57 +02:00
Daniel
102249cc10 chore: the app reaches the gateway by container name, not the public host
LITELLM_API_BASE came from OpenBao as https://llm.danvics.com, so every AI
call in the app also depended on Caddy, public DNS and edge TLS being up.
Pinned to the compose network while the public hostname is being taken off
the internet, at the user's request.
2026-09-16 03:53:53 +02:00
Daniel
ac3e6bf3f0 fix: the assistant page finishes loading for non-admin accounts
The download-transcript button is removed for anyone who is not an admin,
but the event binder still called addEventListener on it unguarded. The
throw stopped the binder before the saved-chat list loaded, before the
click handler for opening old chats was registered, and before the send
box was fully wired, so a non-admin saw no chats until New chat, could not
open old ones, and could not chat. The button is now bound only if present.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 19:59:29 +02:00
Daniel
3c04d662f6 feat: the password door is gone; One Sign In is the only way in
POST /api/auth/login answers 410 for everyone, administrators included. The
sign-in screen never draws an email or a password: it is the provider's
button, or a sentence saying sign-in is not configured. The admin CLI no
longer resets passwords. The e2e harness mints its sessions inside the
container instead of signing in with a password.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 04:14:04 +02:00
Daniel
29ff7e435c feat: One Sign In is the only door; the SSO-only switch is gone
With OIDC on, every password route is shut except /api/auth/login for
administrators — the way back in if the provider is down, reached from an
"Administrator sign-in" link. The disable-local-auth setting, the
registration CLI command and the docs that described them are removed;
accounts, roles and invitations live in authentik.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 03:33:53 +02:00
Daniel
4c699b86ef feat: local registration is closed for good, and its on/off switch is gone
Every account comes through One Sign In; an administrator sends an
invitation link from there. The register route answers 410, the
registration-status route and the admin toggle are removed, and the
setting no longer exists in defaults, lockdown lists or seeds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 03:18:57 +02:00
Daniel
f94c2b9f90 revert: the starter chips are three short labels again
The user, seeing four full questions stacked on the empty page: "the former
way… was better". Labels, three of them, as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 01:42:09 +02:00
Daniel
67336343cf feat: a resource's details are the brief — pasted questions, topics and cases are used as given
Tested with a pasted board question, a topic list and a SOAP case and no other
instruction: all of it was used. The prompt now says so explicitly, so it stays
that way whatever model writes the deck.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 00:18:04 +02:00
Daniel
6b9587d09f fix: the assistant's empty state keeps its own heading when the starters arrive
The re-render swapped "Pediatric Clinical Assistant" for a different heading
and line the moment the starter questions loaded. Same words as the page now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 23:55:33 +02:00
Daniel
91a35a4b27 fix: the assistant page keeps its layout whatever the initialiser does, and its starters are whole questions
The page class that hides the app sidebar and gives the chat its full height
was added only after the initialiser returned, so any error there left the
app menu and the chat rail side by side with the panels at half height. The
class goes on first now. The starter chips show the question itself rather
than a two-word label, four of them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 23:42:32 +02:00
Daniel
15ec830038 fix: each pool batch names what the category already holds, so no batch repeats the last
Batches three onwards reuse the first batches' snippets, and an identical
request came back with the identical list: every category stopped at exactly
40 of its quota. The list of what is covered makes each request, and each
answer, new.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 18:30:28 +02:00
Daniel
6e2ad26547 feat: starter questions are things to learn, not scenarios, and DeepSeek writes them without thinking
The pool was cases — "A 6-week-old with 3 days of projectile vomiting… which
fluid do you start?" — and the user, reading them on the assistant's front
page: "Vague nonsense clinical scenarios. I want concrete things people learn
from and pathophysiology if possible, all related to peds. No more clinical
scenarios. Use deepseek with its reasoning off."

So the brief is inverted. A question asks one definite thing with a definite
answer — a mechanism, a distinction between two look-alikes, the number that
decides, the reason behind a rule of practice — mechanism first, naming its
disease, drug or finding, and staying paediatric. The filter refuses a
vignette opener and a bare definition instead of demanding a number; the
prompt version moves to 3 so every deployment rebuilds the pool once; the
model defaults to DeepSeek flash with thinking disabled, which the assistant
already knows how to ask for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 18:25:05 +02:00
Daniel
57ba2a268c fix: the format picker shows the format's name only — the slide count is the one length control
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 1m2s
Forgejo Docker Build / Build Docker image (push) Successful in 7s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
The picker labels carried each format's typical length ('15–25 cases, three
or four slides each, 60–90 minutes'), which sat under a slide-count field and
read as a second, contradicting number. The label is the name, the hint says
who the format is for, and the brief tells the model that the slide count
asked for is the length and the parts fit into it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 16:07:05 +02:00
Daniel
6ca278c0e6 feat: deck cards fit their content, callouts use the theme's ink, questions can carry a film, formats end with references
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 52s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
Compare cards were as tall as the body whatever they held, so two columns of
four short bullets sat in cards 60% empty; a one-sentence callout sat in a
card two thirds of the slide. Both now size to the text. The callout card's
fixed brown ink was the worst contrast in the catalogue on Board Review's
mint card; it takes the theme's ink. A question slide may carry image_prompt
— a film beside the stem, drawn or left as a labelled frame like a figure.
The evidence-based formats (board review, journal club, QI, abstract, debrief)
gain a References part and the brief says the deck ends with it: DeepSeek
skipped it when it was only suggested.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 15:35:10 +02:00
Daniel
50f5036118 feat: the assistant reopens the chat you were in, and DeepSeek answers without thinking when told to
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 50s
Forgejo Docker Build / Build Docker image (push) Successful in 11s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
A refresh landed on a new empty chat with the conversation a click away in
the list. The open chat's id is remembered per account and reopened on
load; New chat forgets it; a chat deleted elsewhere is quietly gone.

DeepSeek models think by default: measured on ds-deepseek-v4.1-flash, a
three-sentence clinical answer spent 301 reasoning tokens and 2.9 s before
writing and gave the same answer in 0.9 s with thinking off. The assistant's
reasoning effort now comes from CLINICAL_ASSISTANT_REASONING_EFFORT ('low'
as before; 'none' switches thinking off where a model allows it, sent as
DeepSeek's own thinking field through LiteLLM).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 15:24:48 +02:00
Daniel
381483dcf3 fix: a question slide's options are lettered once, and a bare-letter answer names its option
DeepSeek flash wrote 'A. Start IVIG' into option text the renderer already
letters, so the slide read 'A  A. Start IVIG'; and an answer of just 'A' sat
alone on the answer card, sending the room back a slide to see what A was.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 15:14:32 +02:00
Daniel
aac02ec0df feat: a figure the deck wanted and did not get keeps its place as an empty labelled frame
With illustration off, or for a picture no model should draw — a real
radiograph, a run chart, a photograph — the figure or image slide used to
become a slide of text, and the deck forgot it had wanted a picture. It now
keeps a dashed, labelled frame in the picture's place, on figure slides,
full-slide images and the image shapes of custom slides, so the author can
drop their own in after downloading; the Word export says what belongs
there. Figures already drawn are never turned into frames. The one format
guidance that implied a slide authored live now says 'fill in by hand'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 14:46:29 +02:00
Daniel
7ea6c41941 fix: tables scroll sideways on the phone again instead of breaking words to fit the screen
The morning's change forced every table to the screen width below 640px and
let cells break mid-word, so a four-column table read as columns of broken
syllables. A swipe is the better trade; the hint under the table says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 14:41:40 +02:00
Daniel
cd00fae460 feat: a deck has a format as well as a theme — the shape of the session, whose parts the model may reuse
A theme is a look and the slide vocabulary is what one slide can be; neither
said what a journal club is (PICO, methods, appraisal, bottom line) or that a
board review runs in case-question-answer cycles, so every deck came out as
the same lecture. assets/deck-formats.json holds ten formats distilled from
how departments run each session — grand rounds, journal club, morning
report, M&M, board review, noon conference, simulation debrief, parent
education, QI project, research abstract — each a sequence of parts naming
the layouts it is usually drawn with and whether it may repeat. The brief is
prose after the layout vocabulary; a part is a unit the model reuses as many
times as the material needs, and every slide stays one of the twelve types.
Chosen beside the theme, 'Free' by default, stored on the deck and carried
through modifications.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 14:13:22 +02:00
Daniel
f762e91af7 feat: the assistant searches every collection the library declares, fused by rank — one collection stays one call
The search service can hold several Milvus collections, each with its own
embedder, but a search names one or gets the default, so a second collection
was invisible to the assistant. The client now learns the list off the query
path (at warm-up and on the session timer), and only when the service lists
more than the default does a search fan out — one call per collection in
parallel, fused by reciprocal rank so scores from different embedders are
never compared. With one collection, today's case, the request is byte-for-
byte what it was and no listing call is made while anyone waits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 13:57:56 +02:00
Daniel
055c8d121b fix: the last browser confirm() in the resources library uses the app's confirm modal
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 1m4s
Forgejo Docker Build / Build Docker image (push) Successful in 19s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 9s
Deleting a generated image asked with the browser's own confirm(), a bare
dialog that looks nothing like the rest of the app. It now goes through
showConfirm like every other confirmation, with the danger styling and a
Delete button.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015j4L7Eu5Jp8gVMPsXdPaZ2
2026-09-14 10:44:31 +02:00
Daniel
5d28ebad9c feat: the assistant degrades politely under load — bounded retrieval queue, per-user limit, short search cache
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Docker Build / Build Docker image (push) Successful in 17s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Three things the load path lacked. The retrieval slots had an unbounded
line behind them, so a burst meant silent waiting; past a bounded line, or
after eight seconds in it, a caller now gets 'the library is busy' and a
503 with a retry hint. The paid routes had no per-account ceiling; they
now get one, counted in Redis so every replica sees the same count and
nothing is refused when Redis is absent. And the same library search
asked twice within a minute (a retry, a refresh) went to the library
twice; it is now answered from Redis, with 0 turning that off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 07:56:26 +02:00
Daniel
fbc6e3fa6c fix: the Nextcloud login flow waits in Redis, so any replica can answer the poll
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 1m3s
Forgejo Docker Build / Build Docker image (push) Successful in 20s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
The flow lived in a Map inside the web process; with more than one
replica the poll could land where the flow was never started and every
sign-in would look expired. It now sits in Redis with the same 20-minute
life and one-per-account rule, and falls back to memory when there is
no Redis, which is what tests and a single-process box always had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 07:31:52 +02:00
Daniel
9695e3c7f9 fix: an empty reply whose budget went to reasoning is retried with room to write
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Docker Build / Build Docker image (push) Successful in 19s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
Any call with a small max_tokens could come back blank from a reasoning
model: finish_reason=length, content empty, the budget spent thinking.
The shared LiteLLM call now retries that one signature once, with at least
6000 tokens and low reasoning effort, so no caller has to guess a budget.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 05:49:16 +02:00
Daniel
394c543a0a fix: answer and take-home tables wrap to the phone screen instead of scrolling sideways
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 9s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 17s
The sideways swipe was not reachable on the phone, so wide tables were cut
off. Below 640px the cells break their words and the table takes the full
width; wide tables get taller, never clipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 05:40:42 +02:00
Daniel
1d58e5b509 fix: the take-home sheet gives a reasoning model room to think and still write
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Docker Build / Build Docker image (push) Successful in 9s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
The 1,800-token ceiling was spent entirely on hidden reasoning, so the
sheet came back empty. Same budget as the answer path now, with one retry
at a larger budget if the reply is still blank.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 05:23:13 +02:00
Daniel
012a06c107 fix: lazily loaded components stay invisible until their stylesheet and init are ready
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Docker Build / Build Docker image (push) Successful in 12s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
The fragment's markup was inserted before its own stylesheet resolved, so a
first visit to the assistant tab flashed unstyled content. The pending
marker app.js already sets now also hides the markup; the Loading status
line is all that shows until the tab reports ready.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 05:11:26 +02:00
Daniel
8142dd4ab1 fix: the assistant no longer drags the view to the end while an answer streams
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 52s
Forgejo Docker Build / Build Docker image (push) Successful in 18s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
The reader can start at the top of a long answer and scroll at their own
pace; the per-token and on-completion jumps to the bottom are gone, which
also stops a finger on the screen fighting the auto-scroll.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-14 04:26:38 +02:00
Daniel
684a8920e9 fix: a deck's illustration stays out of the page (status line only, tile under Images); a streaming table head is held back, never shown as pipes
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Docker Build / Build Docker image (push) Successful in 8s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 21:11:36 +02:00
Daniel
4de76dbfd9 feat: a share link pasted into an open tab is handled like a fresh arrival
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 50s
Forgejo Docker Build / Build Docker image (push) Successful in 19s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 19:04:48 +02:00
Daniel
fa5ed6c2b4 feat: sharing is by link only — the share-with-everyone switch and route are gone
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 57s
Forgejo Docker Build / Build Docker image (push) Successful in 18s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 19:00:53 +02:00
Daniel
1beb2d33b0 fix: room between the starter questions and the box; a half-arrived bold run streams as bold, not asterisks
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 53s
Forgejo Docker Build / Build Docker image (push) Successful in 9s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 18:56:41 +02:00
Daniel
bbcc824b65 fix: the share panel stays open (no library redraw behind it); the Nextcloud upload button says what it is
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 54s
Forgejo Docker Build / Build Docker image (push) Successful in 14s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 18:54:30 +02:00
Daniel
4127e2d101 feat: the starter-prompt pool is built once and regenerated from the admin button only, on gpt-4.1-mini by default; the question box sits two-thirds down before a chat and at the bottom during one
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 10s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 18:38:11 +02:00
Daniel
4119788815 feat: Settings links to the PedsHub account page in a new tab
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Docker Build / Build Docker image (push) Successful in 20s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 18:10:18 +02:00
Daniel
de13c8306b feat: templates, not just palettes — colour title and section pages, heading bands, striped callouts, question slides with answers, key-point strips, page footers; Grand Rounds and Board Review join the catalogue
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Docker Build / Build Docker image (push) Successful in 9s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 6s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 18:05:41 +02:00
Daniel
9c86c56968 fix: a failed recording keeps its tab and the retry adds the words after the last word; Settings drops the session list; FAQ says what sign out does
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 50s
Forgejo Docker Build / Build Docker image (push) Successful in 8s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 17:56:32 +02:00
Daniel
cba2a312e1 feat: the preview is a viewer — one page at a time, arrows, keys, swipe, a PDF tab and downloads; the theme leaves the list row
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 51s
Forgejo Docker Build / Build Docker image (push) Successful in 11s
Forgejo Docker Build / End-to-end (browser) (push) Successful in 5s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 17:49:22 +02:00