mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
36cb64c8402a1356625cd465671c7809ffeb7715
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3aff2c16db | fix(insights): scope generation guard to prompt fields, clear cache on restore | ||
|
|
bc1633db40 |
fix(export): make the standard report a click, not a preselection
The scope picker arrived with the standard doctor's report already ticked, so someone who opened the panel and pressed Generate sent material they never chose. Nothing is selected now until a person selects it. The template keeps its definition and loses its automatic application: it sits on a button at the top of the picker, named, on both surfaces that mount the picker, and it stays there so the standard set can be restored after experimenting. One click still gets a complete doctor's report. Generate and the share-link create refuse an empty scope, with the scope line beside them saying what to do rather than reporting a failure after the press; the line also describes the control, so the disabled state is not silent for a screen reader. |
||
|
|
f12290532f |
fix(sharing): let the owner replace a link the upgrade closed, and count only what is shown
Migration 0273 revokes every live record share link minted before the selection model, because their frozen scope was "everything except mood and cycle" — a scope nobody chose. The owner has to see why the link stopped and be able to replace it without retyping what they already decided, or a deliberate closure reads as a fault. The list now names that state, says why in one sentence, and offers a replacement that carries the label, the window and the lifetime across, leaving exactly the decision that link never carried. Writing the test for it found a defect in the picker. The group count and the group checkbox read the catalogue's group rather than the leaves the surface actually renders, so on the share-link form — where the insurance leaf is hidden because the server refuses it — the identity row read "1/2" and drew itself half-selected over a leaf the owner could neither see nor reach, and its checkbox would have tried to add it. The three helpers take the rendered leaves now, so a control cannot describe more than it offers. |
||
|
|
9e68ff525f |
feat(share): serve the record a link already implies, and say when there is none
Two downloads under the token — the FHIR document Bundle and the clinical PDF —
behind the same unlock cookie the document route enforces, serving exactly the
link's frozen selection. That is what the removed FHIR toggle promised, in the
form a practice actually uses: open the link, unlock it, save the file. No new
credential type, no new scope, no mint path that does not exist.
The gate is the document route's, step for step: the same live check, the same
token-scoped cookie, the same blunt 404 for every miss class so a probe cannot
tell "no such link" from "revoked" from "documents-only". It differs in one
number, because generating a bundle is far more expensive than serving a stored
blob and must not share a budget with it. Each download writes its own audit
row. The scope cannot exceed the page's: both resolve the same frozen leaf list
through the same resolver, and the insurance leaf is refused at create, so the
insurance number cannot reach this path at all.
The MCP doctor-visit summary stops claiming presence it cannot support. An
account whose owner never saved a report selection resolves to the empty one on
that surface, and an empty scope produces an empty payload — which now reads as
{ present: false, reason }, the same sentinel the other reads use, instead of a
record with every section quietly missing.
Mutation-checked: removing the unlock check, the rate limit, the documents-only
guard or the audit row reddens its own case; pointing the bucket at the document
route's key reddens the bucket assertion; making presence unconditional reddens
the absence case.
The saved profile's shape moves out of the module that reads the row. The panel
is a client component and parses the blob from the /me payload; importing the
reader dragged the database client into the browser bundle, which typecheck and
the unit suite both pass and only `next build` catches.
|
||
|
|
f42934487d |
test(export): prove the scope holds at the artefact, not just at the switch
Five layers, because the defect had five places to hide. The catalogue guard checks what the compiler cannot state: every leaf resolves a label a human can read, every group resolves one, the fenced tier stays last, and the shipped template intersects the fence at nothing. The selection guard pins the three minting functions against each other. A stored blob this build cannot read resolves to the empty selection, never to a default set — that is what makes the revoked legacy links revocable rather than silently re-scoped. A request naming an id the server does not know is refused by name rather than dropped, because dropping it would narrow a scope the caller did express. The egress guard freezes the set of aggregator callers with the source of each one's selection written down. The required brand-typed parameter already stops a new path compiling without an answer; this stops one being added without a human writing the answer down, which is the moment a reviewer asks the question. The behavioural sweep aggregates every leaf in and out against a fixture that carries a row for each, and asserts the payload differs. It is parametrised over the catalogue, so a leaf added later is covered the day it is added. Its positive control asserts the all-on payload is genuinely populated first, so a "no difference" result means the gate is inert and not that the account is empty. Beside it: an unmapped measurement type is excluded rather than passed through, no table behind an unchosen leaf is queried at all, and an empty selection produces an empty record. The artefact layer runs the real route and reads what comes out — the FHIR bundle, the PDF text and the zip's contents — with a positive control beside every exclusion. Mutation-checked: making the gate admit everything reddens the sweep for every leaf; passing an unmapped type through reddens the fail-closed case; dropping the mood query's condition reddens the zero-read case; returning the BMI unconditionally reddens the sweep naming BODY_MASS_INDEX; adding a fenced leaf to the template reddens the purity guard; removing the panel's seeding block reddens the first-run and repeat-run cases; dropping the insurance refusal reddens the share-link case; adding a caller outside the frozen list reddens the egress guard naming the file. The panel suite grew past the practice name it was written for and is renamed for what it now covers: the three states, the fenced tier having no group control, and the scope line naming its sensitive inclusions. Integration coverage moves to the leaf model, including the case this release exists for: an account with data in every domain and a caller that named nothing gets an empty record. The OpenAPI contract catches up with the routes — the required selection, the refused keys, the share summary's new fields, the moved `Patient/$everything`, the Coverage search and the five typed reads. |
||
|
|
582db8a538 |
feat(export): one grouped scope picker, on both surfaces that choose a scope
The export panel rendered twenty flat switches behind a collapsed disclosure, and the share-link form rendered none at all — it posted whatever the create schema defaulted to. Both now mount the same picker: twelve named groups, a count on each, the leaf grid one chevron away, and a fenced tier below a separator. The fence is mechanical, not a label. The sensitive tier has no group checkbox, so no single control anywhere in the picker can turn on more than one of mood, cycle, family history, PHQ-9, GAD-7, WHO-5 or SCI at a time. There is no select-all. The shipped template contains none of them. And the scope line under the button restates every sensitive inclusion by name rather than folding it into a count, because a number cannot be checked at a glance and "incl. Mood, PHQ-9" can. First run opens expanded with the named standard template applied and the fenced tier visibly empty, so the user presses Generate having seen twelve named groups. Every later run opens collapsed carrying their own last selection, which makes the waiting-room path two interactions. Only leaves are ever stored. A group's checkbox is derived from its leaves, so the two cannot disagree, and switching a group off and back on inside one session restores the pattern instead of flooding it. The cycle export card is deleted. It posted empty groups under a docblock claiming every other section was off, and empty groups resolved to the defaults — so a "cycle only" download carried weight, blood pressure, pulse, oxygen saturation, body fat, body composition, the medication list, adherence, labs, allergies and family history. Cycle is a fenced leaf in the one picker now, reachable for the PDF as well as for FHIR, which is more than the card offered. The panel gains a 30-day preset and a custom window, and its error line says what went wrong instead of interpolating a status code. The Checkbox primitive learns the indeterminate state it already had the Radix support for. A partly-selected group drawn as unchecked would say "none of this is included", which on this surface is a false statement about what leaves the instance. |
||
|
|
5e490f31ec |
feat(export): make the doctor report carry only what was chosen
The report resolved an absent key to "included". A caller that sent no sections object, or an empty group inside one, received the full record at server-chosen defaults: roughly fifty metrics plus the medication list, labs, allergies and family history. The web panel sent every key explicitly so it looked fine from the browser, but the share link, the FHIR REST face, the MCP doctor-visit resource and the cycle-only export card all went through paths where absence meant yes. No arrangement of booleans fixes a default that means yes, so the shape goes. A selection is now an inclusion list of leaf ids. A leaf is the smallest thing that can be released or withheld: every measurement type, and thirteen structured sections — identity, insurer, the glucose panel, labs, the drug list, the administration ledger, adherence, the GLP-1 block, allergies, illness episodes, family history, mood, cycle. Membership is inclusion, absence is exclusion, and there is no defaults constant anywhere to drift. Three locks keep a metric added later off a report chosen earlier. A stored selection is a list of what was chosen, so a leaf that did not exist then is not in it. The catalogue is keyed over the measurement enum, so adding a member without deciding its group fails typecheck in the same commit. And the aggregator's filter inverts: a type the catalogue does not know is excluded and annotated, where it used to be passed through. The aggregator's third parameter is now required and brand-typed, which turned the three surfaces that had no scope at all into compile errors. Each one had to name a source, and each now replays the owner's saved selection — the same object reviewed in Settings. An account that never saved one gets nothing there rather than a guess. Identity and insurance become selectable, and a share link refuses the insurance leaf outright. The share view never decrypted the number; that is structural now instead of incidental. The KVNR is not even read unless the leaf was chosen. Two surfaces gain a record they always implied. A share link serves its frozen selection as a FHIR bundle and as the PDF, behind the same unlock cookie the document route uses, with its own rate bucket, its own audit row and the same blunt 404 for every miss. That is what the removed FHIR toggle promised, in the form a practice actually uses. The AI summary leaves the report. Its text came from the cached briefing with no relation to the selection, so its scope could not be proven, and prose whose scope cannot be proven has no place on a document whose whole point is a proven scope. The PDF table stops being a nine-type whitelist. It prints every selected leaf that has data, sub-headed by group, in the same order the panel shows — so selecting resting heart rate no longer means seeing it in the bundle and not in the PDF. The clinician view is grouped the same way and reads real labels instead of "Phq9 score". Migration 0273 adds the saved-selection column, drops the orphaned prefs column and the two retired share-link scope columns, and revokes every live record share link minted before this model. Their frozen scope was "everything except mood and cycle", which is a scope nobody chose; narrowing it would still be a scope nobody chose. Documents-only shares are untouched. The sharing list shows each closed link in a pick-again state with its label, window and expiry kept. The retired allowFhirApi and resourceTypes stay on the create and list responses as the constant false and empty list. The request refuses them. They come off the response once the native client confirms it does not decode them. Six files are split at seams this change already touched: the aggregator into its gate, its dense-bucket query, its series, its glucose panel, its medication ledger and its clinical records; the export route's three emitters into their own module; the clinician view into its sections, its document list and its downloads. |