Commit graph

116 commits

Author SHA1 Message Date
Daniel
025290d64a feat: retire Learning Hub
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 45s
Forgejo Android APK / Build signed APK (push) Successful in 2m1s
Forgejo Docker Build / Build Docker image (push) Successful in 9s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
My Resources generates better slides than Learning Hub ever did — a typed deck
the model fills in, rendered by python-pptx with fit-to-slide text, figures, a
vision review and themes, against Learning Hub's markdown-through-pandoc — and
the articles and quizzes now live in the quiz app. Keeping a second, weaker
generator and a whole CMS beside it was not earning its maintenance.

Removed: three routers, the Learning Hub and Content Manager tabs, their
components and frontend modules, the five database tables, the WebDAV browser,
the content embedding column and its vector index.

Content was exported first — every article as markdown plus a full SQL dump of
all five tables — to ops-backups/learning-hub-export-*. That export is the
restore path; the migration's down() can recreate the shape but never the rows,
and says so.

Two things this simplifies rather than merely deletes:

generated_image_links existed only to record which published content an image
appeared in, and it was the sole reason a generated image could be read by
someone who did not make it. Images are now owner-only — the visibility rule is
one WHERE clause instead of a join across two tables and a published flag.

embeddings.js keeps the model discovery the admin panel uses and loses
searchSimilar and generateContentEmbedding, which queried a table that no longer
exists.

Kept deliberately: Nextcloud connect, disconnect and export, which are how a
generated note reaches a real filesystem and have nothing to do with Learning
Hub; learningRetrieval, which despite its name is the clinical corpus search My
Resources depends on; and the pandoc reference deck, still the fallback when the
python renderer fails, moved from assets/learning to assets/deck now that the
old name misleads.

Tests: four Learning-Hub-only files removed, and the individual cases inside
shared files that asserted its behaviour. Where a test used a Learning endpoint
only as a convenient example — the account-boundary token test, the policy
matrix — it now uses one that still exists, so the property it proves is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 20:14:20 +02:00
Daniel
22683f3584 feat: sign in with a code emailed to you, offered beside the password
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 46s
Forgejo Docker Build / Root app tests (push) Successful in 56s
Forgejo Android APK / Build signed APK (push) Successful in 2m6s
Forgejo Docker Build / Build Docker image (push) Successful in 15s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
The sign-in screen asks for an email first, then offers both ways in together:
a six-digit code sent to that address, or the password. Beside rather than
instead — a code depends on mail being delivered and a password does not, so
neither may be the only route. "Use a different email" goes back a step, and
creating an account stays where it was.

What keeps it from being a second, weaker front door:

- Only a bcrypt hash is stored, so a code read out of the database is not a
  working credential.
- Ten minutes, single use, marked used before the session is issued so a replay
  cannot race it, and requesting a new one deletes the old.
- Five wrong guesses burn it. Six digits is a million possibilities, which is
  plenty against a person and nothing against a script with unlimited tries.
- Requesting a code answers identically whether or not the address exists, and
  every verify failure returns one message. A sign-in screen that says "no such
  account" is a way of finding out who has one.
- Two-factor still applies: a code proves you can read the mailbox, which is one
  factor, and an account that asked for a second still wants it.
- Its own rate limits, tighter for requesting than for attempting, because
  requesting sends mail to someone else's address. These had to be separate
  limiters: Express matches app.use paths on segment boundaries, so
  /api/auth/login does not cover /api/auth/login-code — checked against a real
  router rather than assumed.

Two bugs found while building it, both mine:

authFetch keeps an allowlist of endpoints callable with no verified owner and
rejects everything else before it is sent. The new endpoints were not on it, so
the request never left the browser and surfaced as "Connection error".

reveal() hid elements by appending 'hidden' to className and showed them with a
non-global replace, so hiding twice left two copies and showing stripped one.
The "use a different email" link never reappeared. It uses classList now, which
is idempotent.

Verified against the running server: correct code signs in, the same code again
is refused, a superseded code is refused, five wrong guesses burn it, an expired
one is refused, and the stored value is a hash. In the browser: requesting a
code advances the screen, a wrong code is refused without losing the screen, and
the password route still signs in.

Not yet demonstrated: a correct code typed into the browser. The harness keeps
racing the one-live-code rule — the page's own request supersedes whatever code
the test holds, and with SMTP off the delivered one cannot be read. The same
request reaches the server on the wrong-code path, and the endpoint itself is
verified, but that last step is untested end to end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 20:12:03 +02:00
Daniel
5577ec301c refactor: cut the explanatory copy from the sign-in screen and My Resources
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 55s
Forgejo Docker Build / Root app tests (push) Successful in 55s
Forgejo Android APK / Build signed APK (push) Successful in 2m3s
Forgejo Docker Build / Build Docker image (push) Successful in 19s
Forgejo Docker Build / Deploy to the host (push) Failing after 2s
The HIPAA notice is gone from the auth screen, along with the CSS rule that had
no other user. The My Resources intro paragraph and the helper paragraphs under
each checkbox are gone too; where a paragraph carried something the reader
actually needed, it moved onto the control itself — the illustration checkbox
now says what it does and where to steer it.

Two sentences were kept rather than deleted, both about data leaving the
building: that a web search query leaves this network and must not carry
anything identifying, and that a PubMed query goes to NCBI. Trimmed to one line
each. Say the word and they go too, but a clinical tool should not quietly send
a query off-network with nothing on screen about it.

The three tests that pinned the removed sentences now assert the behaviour
instead: that the header still claims privacy, and that the illustration
checkbox is genuinely unchecked in the markup rather than merely described as
off by default.

Verified as rendered: neither the sign-in nor the sign-up screen mentions HIPAA,
the invite-code field is visible, and the generator card is down to its controls
plus the NCBI line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 17:57:07 +02:00
Daniel
fac8757ce8 feat: My Resources has a menu, a library and three downloads
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 56s
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Android APK / Build signed APK (push) Successful in 2m13s
Forgejo Docker Build / Build Docker image (push) Successful in 14s
Forgejo Docker Build / Deploy to the host (push) Failing after 1s
The pathway existed but was reachable only by API. It now has a tab of its own
next to the Learning Hub — related, not the same thing, and sitting together is
how someone discovers the difference — visible to every signed-in user with no
role gate in the markup.

Generate a deck or an article, see everything you have made, download each as
PowerPoint, Word or PDF, delete what you no longer want. The screen says
"Private to you" and "Nobody else sees these", because the distinction from
published Learning content is the thing a person needs to understand before
typing a patient's condition into it.

Downloads are fetched rather than linked: an <a href> cannot carry the
Authorization header. The blob is saved under the filename the server chose and
the object URL is revoked afterwards. Resource titles come from a model, so rows
are built as elements and a title is only ever assigned to textContent.

The e2e stack now joins danvics_convert too. It could previously reach only
Postgres and Redis, so a PDF download failed there in a way production would
not — which did at least prove the degradation path works: with Gotenberg
unreachable the response is "PDF conversion is unavailable right now. PowerPoint
and Word still work", and the other two formats download unaffected.

Verified in a browser as an ordinary user: the tab appears and opens, the form
swaps slide count for word count when the format changes, the library lists
their own work, and pptx, docx and pdf all download with sensible filenames
(36360, 13285 and 68310 bytes).

Also documents retrieval sizing in docs/retrieval-tuning.md — the per-feature
budgets, and RERANKER_TOP_K, which caps all of them and had until now appeared
in no configuration file at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 15:00:19 +02:00
Daniel
b3d66caaca feat: a retried transcript goes back to the tab the audio came from
Retrying a kept recording transcribed it and put the text on the clipboard,
leaving you to find the right tab and paste. The app already had the answer: the
module was recorded with the audio. Retry now opens that tab and puts the text
in its transcript box.

Two things had to be true first.

The module was not actually being recorded. transcribeAudio never sent one, so
the server stored its default for every upload — all 28 rows in audio_backups
said "recording", and a retry had nowhere to send anything back to. Each
module's call now tags its own upload.

And the names disagreed. The recorders tagged 'encounter', 'soap', 'dictation'
while the recording-started events said 'enc', 'sick', 'dict'. One table now
holds the mapping and resolves the aliases, so the recorder that tags the
upload, the backup row that labels it and the retry that delivers it cannot
drift apart again.

Existing text is appended to, never replaced: a retry usually recovers
something on top of a live transcript, and overwriting would lose the words the
browser did hear. The box only exists once its tab's markup has been fetched, so
delivery polls briefly rather than guessing a delay, and falls back to the
clipboard if the tab never opens. An empty result says so rather than claiming
success. Backup rows now name their source and the button reads "Retry into
SOAP Note" instead of "Retry".

Verified in a browser: enc resolves to encounter, delivery switched tabs and
produced 'existing live transcript\n\nRECOVERED TEXT'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 12:39:50 +02:00
Daniel
ca0be0a98e fix: the sign-in line describes the app, and ped-ai drops a network it never used
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 49s
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Android APK / Build signed APK (push) Successful in 2m5s
Forgejo Docker Build / Build Docker image (push) Successful in 11s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
"AI-Powered Clinical Documentation" named about a third of what is here.
Documentation is one pillar; the assistant and the learning hub are the others,
and none of it said pediatric, which is both true and the differentiator.

danvics_speech is gone from ped-ai. Everything ped-ai dials was checked against
every network it joins: assets, libretranslate, litellm, mcp and monitoring-loki
are the only internal hosts, and litellm answers to that alias on danvics_mcp
already. supertonic/kokoro/sherpa appear here only as LiteLLM model ids and
voice names, never as hostnames — speech goes through the gateway, exactly as
intended. Joining danvics_speech therefore bought no reachability and cost a
cold-boot dependency on local-speech-gateway, which had to have been up once
before ped-ai could start at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 01:37:39 +02:00
Daniel
39c1663334 feat: invite-only registration
registration_enabled was a single switch: open to anyone, or closed to
everyone. This adds the setting an operator actually wants in between — open
to people you invited.

A code is single-use, expires (7 days by default, 90 maximum), and can be
revoked or deleted. It is stored hashed with only its last four characters
kept, because an invite grants account creation and a database dump should
not hand someone a working one. The code is readable exactly once, in the
response that creates it.

The claim is a single conditional UPDATE carrying every condition, so two
registrations racing the same code cannot both succeed. It happens after the
account exists, so a code is never spent on a failed registration — and if
the race is lost, the just-created account is removed rather than left behind
as a free registration. The rejection never says which of the four reasons
applied; distinguishing them would tell someone probing codes which guesses
were closer.

Codes avoid I, L, O and U so they survive being read aloud or copied off a
screen, and matching ignores case and separators.

The sign-up field appears only when the server says a code is required. The
admin card creates, lists, revokes and deletes, and carries the toggle.

Verified against the live database: create, claim, second claim refused,
unknown code refused, revoking a used code refused, delete. 684 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-10 23:12:07 +02:00
Daniel
adcea2a0ca fix: image models can be added and offered; solid phone top bar on iOS
Image models
- The Clinical Assistant "Image models" list waited on an old
  #assistant-image-model dropdown that no longer exists, so discovery never
  reached it and only four hard-coded fallbacks appeared — with no way to
  add any of the gateway's 50 image models.
- Image Generation search rows now have + Add / Added. Added models are
  saved as clinical_assistant.image_model_roster (validated server-side:
  up to 100 ids) and appear in the Clinical Assistant list at once; ticking
  one there offers it to users. Anything already allowed or configured
  stays listed. Unsaved ticks survive an add.
- The roster notification is guarded, so it can never fail the settings load.

Phone top bar
- The page is drawn under the status bar (viewport-fit=cover) and its
  theme colour was the removed header's blue, so on an iPhone content showed
  scrolling at the top of the screen. The row is now a real fixed element
  that extends behind the status bar (env(safe-area-inset-top), 0 in a
  normal tab), the menu button, sources pill and drawers clear it, and the
  theme colour is white.

Verified in Chromium: + Add -> saved roster -> listed unticked; tick kept;
remove works. Top bar is the only thing in the top 48px on all 22 pages;
phone menu positions unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4rptBNvn6RYieQw54GXNS
2026-09-10 13:46:19 +02:00
Daniel
531996de1e feat: search finds what is inside a tab, and the menu head reads toggle, mark, search
Search now reaches sub-navigation, because people look for "bili" rather than
"Calculators". It reads whatever a loaded component exposes — data-calc,
data-subtab, data-section — so it covers every tab with sub-navigation instead of
one hard-coded list, and the relevant components are warmed when the palette
opens. Opening a result survives the component still loading, and reaching one
from the assistant navigates first and opens it after.

Menu head order is now show/hide menu, then the mark, then search. In the
collapsed rail every item is the same centred 52px box, so the icons finally
share one axis — the brand was a flex row with a gap and sat off-centre from the
buttons beneath it.

Settings, FAQ and Admin are no longer listed in the menu: they already have a
place in the account card, and listing them twice only made the tab list longer.
Their sections remain, and activateTab already tolerates a tab whose section
exists without a sidebar button.

The retry loops are named functions rather than IIFEs, which the module
entrypoint rules forbid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmpYHPSLGmXGZMyLpn2Lbe
2026-09-10 05:16:05 +02:00
Daniel
2d6eaccd96 fix: collapse to an icon rail, keep the menu scrollable, and brand as PedAI
Collapsing hid the sidebar entirely, and the toggle had moved into .menu-head
while the hide rule still targeted direct children of .sidebar-nav — so the head
went, taking the toggle with it, and there was no way to reopen the menu.

Collapsed is now a 52px icon rail rather than a disappearance: the mark, the
toggle, search and the account avatar stay reachable in place, so nothing has to
be floated over the content and there is always a way back. The assistant rail
collapses to the same strip.

Scrolling. The workspace list in the assistant rail had no overflow rule, so its
list of every app tab ran off the bottom and scrolling only appeared once you
left for the app. In the app sidebar the opposite: the whole sidebar scrolled, so
the account card scrolled away with the list and Settings and Log out became
something to hunt for. Both now scroll only the list, with the head and the
account card pinned.

The toggle was also being styled twice — as a head-row icon button and by its own
older rule with a different size and margin. It is now only the behavioural hook
that flips the icon.

Brand is PedAI, which is short enough to sit in the head row without squeezing
the controls; the collapsed rail shows the mark alone.

The reachability test now walks the toggle's real ancestor chain and fails if any
collapse rule hides a branch it sits on, rather than asserting one rule's text —
this class of bug has broken twice by moving the toggle deeper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmpYHPSLGmXGZMyLpn2Lbe
2026-09-10 05:06:23 +02:00
Daniel
c7820c5ae6 feat: one shell for every view, no header bar, and a search palette
The header bar is gone. Settings and Log out moved into the account card, so a
whole band of chrome had nothing left to hold. The brand now heads the menu
alongside the search and collapse controls, on one row rather than three stacked
full-width ones. btn-settings and btn-logout are retained hidden as the canonical
handlers other code already calls, rather than duplicating that logic.

One shell for every view. Each tab was a plain white page while the assistant and
the workspace launcher sat on a tiled card, so opening any menu item changed the
whole face of the app. The tiled ground and the card edge now belong to
.main-content, and only the contents differ. The assistant replaces the shell
card rather than nesting a second one inside it.

Search, in both views, from the same palette: the workspace searches the app
menu, the assistant searches saved chats. Both sources are already in memory, so
this needs no new endpoint. Cmd/Ctrl+K opens it, arrows and Enter work, and the
assistant publishes its chats rather than the palette reaching into its
internals.

The collapse icon is now directional angles that rotate when collapsed, so it
always states what the click will do; fa-table-columns said nothing.

Mobile: the sidebar slides in whole, so the in-sidebar collapse control is hidden
there, the account card sticks to the foot of a scrolling menu, and the palette
is full-bleed instead of a centred dialog on a small screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmpYHPSLGmXGZMyLpn2Lbe
2026-09-10 04:56:37 +02:00
Daniel
91699e45cb feat: one menu for both views, a slim top bar, and an account card
The view now decides only what the menu LISTS. Everything structural is shared,
so switching redraws the content and nothing else:

- One width. The app sidebar is 210px; the assistant rail was 260px, so every
  switch shifted the content sideways.
- One surface. The tiled ground belongs to the shared panel rather than to one
  view's empty state, so the transcript and the workspace launcher sit on the
  same background.
- One menu ending. An account card closes both menus with Settings, FAQ, Log out
  and — for admins only — Admin. These were unlabelled icon buttons in the old
  header; log out reuses the existing flow rather than a second implementation.

The blue gradient header is now a slim white bar. Being the loudest element on
screen, it made any view without it look like a different product. Everything
measured against it follows from 66px to 52px.

The composer has room around it and centres on an empty transcript instead of
sitting at the very top, and Create image joins take home, export and attach in
the + menu — it acts on the conversation, so it belongs with them rather than as
its own button on the rail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmpYHPSLGmXGZMyLpn2Lbe
2026-09-10 04:45:51 +02:00
Daniel
b55a748361 fix: remove the assistant's second top bar and give the toggle a menu icon
The assistant carried its own bar above the layout, on top of the blue app
header that both pages now share. On desktop it held a visually-hidden title, a
mobile-only drawer button and a status pill — so it was mostly dead space, and it
made the assistant taller than the app page it is supposed to match.

The bar is gone. The ready/busy indicator moved into the composer footer, next to
the work it describes, and the drawer button floats on phones rather than
occupying a strip the desktop layout does not have. The rail now sticks to the
top of the layout instead of 52px below a bar that no longer exists.

The menu toggle used fa-table-columns, a split-pane glyph that does not read as
"menu". Both copies now use fa-bars and rotate when collapsed to show which way
they will act.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmpYHPSLGmXGZMyLpn2Lbe
2026-09-10 04:25:10 +02:00
Daniel
dd25e0edb7 feat: one Assistant/Workspace switch and one menu toggle, shared by both screens
The app IS workspace mode, so the switch belongs in both places and the only
difference between the two screens is which pill is highlighted:

- The app sidebar's "AI Assistant" nav entry is replaced by the switch, with
  Workspace highlighted. The assistant rail carries the same control with
  Assistant highlighted. Clicking Assistant from the app navigates to
  /assistant; inside the assistant, Workspace opens the launcher in place.
- Accent corrected: I had it backwards last round and turned the assistant
  purple. The app's accent is blue (.tab-btn.active in styles.css); the
  assistant now uses it throughout, so neither screen looks like a different
  product. Citation chips keep purple so evidence stays distinct from
  interactive blue.
- One menu toggle labelled "Hide menu", shared by the app sidebar and the
  assistant rail. It replaces three separate controls: a pin button, a floating
  expand button, and the assistant's own rail toggle. The toggle stays reachable
  when the rail it collapses is hidden, or there would be no way back.
- Workspace cards list clinical work only. Settings, FAQ, Admin, Docs and
  Content Manager stay in the app sidebar under Account, where they belong.
- Model availability is discovery-only again: a hand-typed id cannot be verified
  at that point, so the manual entry field is gone.

The in-page mode state machine is removed along with the whole class of
hidden-versus-display bugs it carried.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjVc5oaAaYFNbZGLeJp6TX
2026-09-10 02:39:42 +02:00
Daniel
750efc4106 feat: Assistant / Workspace mode switch in the rail, the way Home and Code swap the page
The rail's Workspace section was a collapsible extra sitting under the chats.
It is now the second of two modes, selected by a segmented switch at the top of
the rail:

- Assistant keeps the chat column, the saved-chat list, New chat and Create image.
- Workspace REPLACES the chat column and the Sources panel with the app's own
  menu — a card grid in the main column and the same list in the rail — so
  nothing from the assistant is left half-visible beside it.

Both the rail list and the cards are built from the app's real .tab-btn
elements, so a tab added, renamed or hidden in index.html follows automatically.

Learning Hub moved down from second place to sit immediately before Content
Manager, with the other content tools rather than above the clinical ones.

Also removed dead CSS for .assistant-view-switch, .assistant-learning-view and
#assistant-learning-root: that markup no longer exists anywhere in the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfrkQwA4YGrGw9LZSpeAq
2026-09-09 23:47:41 +02:00
Daniel
1267d1b7fc fix: login screen — remove Download Android app (APK) link and About Pediatric Clinical Tools block
All checks were successful
Forgejo Android APK / Root app tests (push) Successful in 55s
Forgejo Android APK / Build signed APK (push) Successful in 2m22s
2026-09-09 16:37:47 +02:00
Daniel
73955dd5f9 feat: model selection in chat (composer pill + popup image model), deterministic image dispatch when the model writes text instead of calling the tool, markdown-rendered email, OWUI stacked composer, left mobile drawer, Go back in topbar, DeepL removed, image history inside the Create image popup 2026-09-09 02:55:28 +02:00
Daniel
982a089fcb fix: authHeaders must include Content-Type — Express never parsed JSON bodies, causing empty "Question is required" on every chat send. Also: Learning Hub back in the main menu, sources-only right column, image gallery as the user library with poll+preview, queued job reference handling 2026-09-09 02:13:17 +02:00
Daniel
c8a9f934be feat: AI Assistant at the top of the main menu; warn-only conversation budget
All checks were successful
Forgejo Android APK / Root app tests (push) Successful in 57s
Forgejo Android APK / Build signed APK (push) Successful in 2m3s
2026-09-09 01:25:46 +02:00
Daniel
68385c42f1 fix: iPhone keyboard handling — resizes-content viewport, in-flow pill composer on 100dvh, 16px input to stop auto-zoom 2026-09-08 23:30:35 +02:00
Daniel
8b072496e2 feat: Open WebUI-style assistant workspace — 3-column layout, markdown/math/code/tables, autosave with images, translation (LibreTranslate+DeepL), citation modal, Learning Hub moved in, handoff removed 2026-09-08 18:52:35 +02:00
Daniel
cfaf8e957b feat: ship reviewed prompt history, conversation limits and account protections 2026-09-07 04:01:01 +02:00
Daniel
3fb4c10f2b Point the APK download link at Forgejo
All checks were successful
Forgejo Android APK / Build signed APK (push) Successful in 2m48s
The login page linked to
github.com/ifedan-ed/pediatric-ai-scribe-v3/releases/latest, which returns
404 — releases are published to git.danvics.com by the Forgejo APK
workflow. Verified: the Forgejo URL returns 200, the GitHub one 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 01:17:29 +02:00
Daniel
82ed46d01b Fix Turnstile in the mobile app; drop it from login
The Turnstile challenge failed reliably inside the Capacitor WebView,
which blocked login and registration from the Android app.

Three separate causes:

1. Android WebView blocks third-party cookies by default. Turnstile runs
   in a cross-origin iframe from challenges.cloudflare.com and needs its
   own storage, so the widget never emitted a token. MainActivity now
   calls setAcceptThirdPartyCookies on the app's own WebView.

2. The register handler read the Turnstile response with an unscoped
   document.querySelector, which matched the *login* widget's input (it
   comes first in the DOM). Registration therefore submitted the login
   widget's token — single-use with a 5 minute expiry, so any prior login
   attempt or slow signup made it fail server-side.

3. The register and forgot-password widgets auto-rendered inside forms
   that start at display:none, where Turnstile does not reliably complete
   a challenge, and nothing re-rendered them when the form was shown.

Widgets are now rendered explicitly when their form first becomes
visible, and tokens are captured from the render callback instead of
being read back out of the injected input — which makes the unscoped
lookup in (2) structurally impossible. Added error/expired/timeout
callbacks so a widget failure surfaces the Cloudflare error code instead
of failing silently behind a generic toast.

Login is no longer gated at all. It is the path mobile users hit
constantly, and it is already covered by a 10-per-15-min per-IP rate
limit, a constant-time credential check, and TOTP 2FA. Registration and
password reset — the endpoints that actually attract bots — stay gated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:22:03 +02:00
Daniel
2fec33dc4d convert learning hub to module 2026-05-08 09:01:34 +02:00
Daniel
9167532a14 use indexed assistant prompt examples 2026-05-08 08:44:34 +02:00
Daniel
db2ecca45d remove top bar model selector 2026-05-08 08:28:54 +02:00
Daniel
04e736eb2e convert speech helpers to modules 2026-05-08 08:09:38 +02:00
Daniel
9ca5365daf convert auth helpers to modules 2026-05-08 07:56:41 +02:00
Daniel
bc0b43151d convert settings helpers to modules 2026-05-08 07:54:08 +02:00
Daniel
b6ebca0e6f convert admin docs and drug loader to modules 2026-05-08 07:50:26 +02:00
Daniel
03ee07f92f clarify template-only AI memory context 2026-05-08 07:38:27 +02:00
Daniel
20fc6798a9 remove browser whisper transcription 2026-05-08 07:33:12 +02:00
Daniel
59fa229b59 convert settings loaders to modules 2026-05-08 07:17:29 +02:00
Daniel
38667608b1 convert encounter note entrypoints to modules 2026-05-08 07:15:11 +02:00
Daniel
02f348ddb3 convert clinical note entrypoints to modules 2026-05-08 07:13:08 +02:00
Daniel
c458c4b4ff convert chart review entrypoint to module 2026-05-08 06:49:46 +02:00
Daniel
e05720083a test clinical note entrypoints 2026-05-08 06:33:34 +02:00
Daniel
90938f8ec1 fix diagram delete confirmation 2026-05-08 06:28:12 +02:00
Daniel
0102c9cbc6 convert admin entrypoint to module 2026-05-08 06:19:51 +02:00
Daniel
34f198edc0 extract well visit schedule data 2026-05-08 05:11:29 +02:00
Daniel
e4ea553bee extract calculator shared modules 2026-05-08 02:58:58 +02:00
Daniel
1ed1a37161 convert notes scripts to modules 2026-05-08 01:37:41 +02:00
Daniel
116bd941e1 extract notes api client 2026-05-08 01:31:06 +02:00
Daniel
c8436d5e4c refactor clinical assistant prompt handling 2026-05-07 22:52:46 +02:00
Daniel
21fb631fb5 feat: improve clinical assistant export and citations 2026-05-07 17:15:26 +02:00
Daniel
e4a95ad72e refactor: move assistant examples to module 2026-05-06 23:39:41 +02:00
Daniel
6c25e8ff05 fix: polish clinical assistant citations 2026-05-06 23:04:40 +02:00
Daniel
2310c43aea fix: improve clinical assistant rendering 2026-05-06 08:03:28 +02:00
Daniel
96c4565b9c feat: add clinical assistant MCP search
Some checks failed
Build & release Android APK / Build signed APK (push) Has been cancelled
Build TWA APK / build-apk (push) Has been cancelled
Build & Push Docker Image / Build linux/amd64 (push) Has been cancelled
Build & Push Docker Image / Build linux/arm64 (push) Has been cancelled
Build & Push Docker Image / Merge manifests (push) Has been cancelled
2026-05-06 07:31:32 +02:00