8000 Bring locus up to date: accounts and lists, email sign-in, processing for three data types, MaxQuant kit by jurilw · Pull Request #3 · hridaya423/locus · GitHub
[go: up one dir, main page]

Skip to content

Bring locus up to date: accounts and lists, email sign-in, processing for three data types, MaxQuant kit - #3

Open
jurilw wants to merge 315 commits into
hridaya423:mainfrom
jurilw:sync-2026-11-04
Open

jurilw wants to merge 315 commits into
hridaya423:mainfrom
jurilw:sync-2026-11-04

Conversation

@jurilw
@jurilw jurilw commented Nov 4, 2026
Copy link
Copy Markdown
Contributor

Brings this repository up to date with UoSDDNB/omics-agent, where locus has carried on since 18 Oct. Nothing here is lost: every file in this repo's main exists in that history, and this branch adds what came after.

The two repositories have separate histories, so this is one merge commit that joins them. The resulting files are identical to UoSDDNB/omics-agent all-features (ae43140, PR #4 there). Later updates can be ordinary merges.

What it adds to main

  • Accounts. Sign in with a passkey or a six-digit email code; recovery codes; Settings with export and delete.
  • Saved lists. Lists with notes and tags, CSV and accession export, and read-only public links. Saves made before signing in move into the account.
  • Watching. Watched searches with "new since your last run", and following researchers by ORCID (/following).
  • Interface. Header navigation, a command palette (/ or Ctrl+K), and toasts with undo.
  • Hardening. Client address behind proxies, origin check, __Host- cookies, write quotas and storage caps, no-store on account responses.
  • Processing. Three rows for every data type (here, laptop, cluster) for bulk RNA-seq, single-cell RNA-seq and Ribo-seq, with cluster kits.
  • MaxQuant kit. A search kit for PRIDE projects with an SDRF.
  • Metadata audit. Which domains, years and data types lack sample metadata, plus the three-minute pitch deck.

How it was checked

  • Full gate on ae43140: 224 tests, tsc, eslint, secrets check, next build, and 38 headless-Chrome page checks at 1280 and 390 px. All pass.
  • Two merge conflicts were resolved by keeping both sides:
    • agent.py: the hosted triage/screening service from deploy-public, and the batch and trace options from modalities.
    • api.py: the imports.
  • Two lint errors that modalities brought in were fixed.

Notes

  • The data release this README points to (data-2026-10-18) stays here; nothing in it changes.
  • accounts keeps user data in its own SQLite file (OMICS_USERS_DB), never in omics.db. Details are in docs/accounts.md.

🤖 Generated with Claude Code

hridaya423 and others added 30 commits October 18, 2026 00:17
8000
…thors' QC in their words

omics_agent/qc.py sums reads and bases per sample from the ENA run table
(GEO samples matched by GSM in the experiment title), groups them by the
library type the methods show, and flags samples below 0.25x or above 4x
their group's median. It quotes the authors' QC criteria: sentences with a
number that state a filter or threshold, from the record's processing text
and the paper's open-access methods (analysis sections only). Dataset page
section Quality; GET /qc/{accession}; MCP sample_qc. GSE132703: three
Ribo-seq libraries at 0.13-0.20x the median; GSE36892 quotes the paper's
'rRNA reads represented 40-80% of the footprint libraries'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es from SDRF

explain.py asks the model for a short paragraph from the contrast, the DESeq2 summary and the top genes, then checks every number in it against the input; a paragraph with a number the pipeline did not compute is dropped with a note. The CLI and the results panel show it under a 'written by the model' label.

sdrf.py reads the curated SDRF tables of bigbio/sdrf-annotated-datasets (data/sdrf/, Apache-2.0) and counts individuals, samples and runs separately at sample grain, leaving pooled, QC and reference channels out; a table without an individual column reports unknown. The search labels PRIDE hits through the same label prompt as GEO. Three real tables are vendored as fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nothing

PRIDE's keyword search returns an empty list for phrases of three or more words, so a planned phrase such as 'Alzheimer cerebrospinal fluid proteomics' found no project; each search term is now tried as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ces about the rules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… as evidence

A cell line column means cell_line, a biofluid material or organism part means patient_biofluid, other human material patient_tissue and non-human material a model; the model's label stands only when the table does not say.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l library types, real QC only

From critic F: libraries titled re-seq/top-up are listed as top-ups of the
sample they extend (GSE132703: GSM4146393-96 of GSM3889093-96) and never
flagged; flags apply only within a stated library type of 3-150 samples;
per-sample values are visible as bars (min/median/max and outliers beyond
40 samples); read length is per read for paired runs; proteomics datasets
show no depth line; a failed request says so with a retry. Quoted QC needs
a QC check name or a QC word with a threshold-like number (no tool versions
or reagent steps), repeats across fields merge, and every quote is reachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hase 2 log

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…MVP log

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sets that share it

The door audit's most common doors were values that open nothing (organism
on 34 of 38 pages, data type, instrument, keywords, disease). omics_agent/
browse.py finds every locus dataset with the same value: an exact phrase in
the indexed field (organism, or data type/instrument text) plus the
repositories' enrichment metadata (instruments, tissues, keywords), newest
first with counts per repository. /browse page with repository filters and
paging; the record aside links its organism, instrument, platform, data
type, disease, tissue, software and keyword values there. GET /browse; MCP
browse_datasets. Q Exactive HF-X: 2,309 datasets in iProX, PRIDE,
MetaboLights, MassIVE and others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bounded rows, and return unstated from filter_samples

While a filter loads, the line keeps the previous count dimmed and the table keeps its rows, so nothing jumps. A failed
filter shows its message and Try again in the line. Chips scroll horizontally in their own row, so the line is at most
two rows at 390 px. The commands action appears only for a selection (Files already offers them unselected) and moves
focus to the commands block. filter_samples returns unstated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ple, by title

assay.pairs matches each footprint library with the RNA-seq library of the
same biological sample (the title without library words and lab prefix):
one elongating, else initiating, footprint library and one RNA-seq library
per key, a TIS library of the same sample alongside; re-sequencing top-ups
and ambiguous keys stay out. classify returns it as 'matched' (also via MCP
classify_assays); the dataset page shows 'Matched libraries' under the
split sentence. GSE132703: 12 pairs and 4 top-ups; GSE36892: 4 pairs and
4 replicate-2 footprints without RNA-seq; GSE157490: 24 RPF/mRNA pairs
with their QTI library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ferent tissues, and the smallest detectable change

omics_agent/design_check.py checks one case/control comparison: a batch-like
characteristic (plate, date, institution, cohort) that goes with it
(Cramér's V; 'partly' from 0.25), characteristics unbalanced by 30 points or
more among samples with a stated value, cases and controls from different
tissues, groups under 3; batch-like characteristics found balanced are
listed; and, from the expression table, the fold change detectable at
p < 0.05 with 80% power (median within-group SD). Re-encodings of the
comparison and unknown levels are not traps. First look shows it under the
pickers; GET /design-check/{accession}; MCP check_design. Pooling warnings
are limited to experimental factors (cell line, strain, treatment, time).
GSE150910: diagnosis partly goes with institution (V 0.37), plate balanced,
1.13x detectable; GSE213001: age group unbalanced (39% vs 73% under 60).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dataset's first look

oantico's check for an organelle IP: omics_agent/lists.py reads MitoCarta
3.0 (1,136 human genes, symbols, synonyms and UniProt accessions; Broad's
public table, cached under ~/omics-data) and reports how many pasted items
are mitochondrial, by sub-compartment, with MitoCarta's evidence and
pathways. Given a GEO accession, each item's log2FC, q and rank in the
first look and the overlap with its significant genes against chance
(hypergeometric). /list page; first look links to it; POST /list; MCP
check_list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e final build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n the volcano, heatmap and counts panels

deseq2.R gets theme_locus(), the validated blue/orange condition pair (the web panel uses the same hex), ggrepel gene labels, drawn and annotated thresholds, symmetric volcano axes, a sample-to-sample distance heatmap (pheatmap) and a counts panel for the top six genes. Every figure is written to plots/ as a 300 dpi PNG and a vector PDF at a stated size; the three legacy PNG names stay for the panel. The API serves plots/ files and the panel lists them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ition and says why when it cannot

Idle, listening (pulsing ring, stop icon, Escape stops) and unavailable (disabled with the reason: no API or no secure context). Interim results land in the search box after the typed text; nothing auto-submits. Verified over the machine's IP (insecure, disabled) and on localhost (listening).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… search box, nothing auto-submits

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…, and an honest limits list; a clear message when no model is configured

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jurilw and others added 30 commits October 22, 2026 23:21
…red lessons

Merges main (the kit branch was 197 commits behind) and moves the kit download to the PRIDE study page's Files section.

From PRIDE Map PRs #11, #12, #14 and #15 (neomatrix369), re-implemented:
- Licence gate: preflight.sh and run_maxquant.sh refuse to run MaxQuant until MAXQUANT_ACCEPT_LICENSE=1; the README states
  its terms and that a built image must not be pushed publicly.
- Sizing from measured telemetry (PXD046787, 2 DDA runs: 13.4 GB peak RAM, 15.8 GB peak disk for 4 GB raw): disk is now
  4x the raw data, at least 20 GB; the cluster-only caveat is relaxed to workstation-or-cluster.
- submit_slurm.sh searches on node-local disk ($TMPDIR) when it has room (feature detection ran about 6x faster there).
- preflight and run_maxquant block symlinked raw files, which Thermo's reader rejects with an unhelpful error.

Fixed here (also wrong in PRIDE Map): msInstrument codes are 0 Orbitrap, 1 Bruker Q-TOF, 2 Sciex Q-TOF, 3 Agilent Q-TOF,
4 Bruker TIMS. timsTOF was written as 1. Each group now carries MaxQuant's default peak and tolerance settings, and
timsTOF runs use lcmsRunType TIMS-DDA. Bruker .d runs stored as one zip per run are fetched and unpacked; runs in
shared archives get a clear hand-unpack note, and disk is sized from the archives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checkouts moved from ~/omics-agent* to ~/fast/locus/omics-agent(.wt/<branch>) and the index from ~/locus-data to ~/fast/locus/data on 25 Oct 2026; the old-path symlinks are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…locus

The checkouts moved from ~/omics-agent* to ~/fast/locus/omics-agent(.wt/<branch>) and the index from ~/locus-data to ~/fast/locus/data on 25 Oct 2026; the old-path symlinks are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs at ~/fast/locus

The checkouts moved from ~/omics-agent* to ~/fast/locus/omics-agent(.wt/<branch>) and the index from ~/locus-data to ~/fast/locus/data on 25 Oct 2026; the old-path symlinks are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checkouts moved from ~/omics-agent* to ~/fast/locus/omics-agent(.wt/<branch>) and the index from ~/locus-data to ~/fast/locus/data on 25 Oct 2026; the old-path symlinks are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checkouts moved from ~/omics-agent* to ~/fast/locus/omics-agent(.wt/<branch>) and the index from ~/locus-data to ~/fast/locus/data on 25 Oct 2026; the old-path symlinks are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- omics_agent/users.py: users DB (OMICS_USERS_DB, numbered migrations in
  migrations/users), hashed session tokens with a 30-day sliding expiry,
  HttpOnly/SameSite=Lax/Path=/ cookie, require_intent CSRF guard
  (X-Locus-Intent: 1 + JSON), auth rate limit (10/min per address, 100/min
  overall), shared {"error": {code, message, transient}} shape, and the
  optional_user / current_user dependencies for later routers.
- omics_agent/account.py: /account/me, sign-out, passkey register and
  sign-in (webauthn), ten one-time recovery codes, ORCID OAuth behind
  ORCID_CLIENT_ID/SECRET (404 when unset), dev-login only with
  LOCUS_DEV_LOGIN=1 from loopback.
- web: account corner in the layout (Sign in / name menu), /account page,
  AuthPrompt with return-to (idea from papertok's auth-return), lib/account.ts.
- tests/test_accounts.py (25 tests, verifier and ORCID token endpoint mocked),
  docs/accounts.md, THIRD_PARTY_NOTICES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Users DB migration 002_lists.sql: lists (built-in "Saved" per user, named lists) and list_items
  (added order, note, tags, snapshot of what the page showed).
- omics_agent/saved.py: /lists routes (list CRUD, add/remove item, note and tags, export as
  accessions or CSV with formula-safe cells), limits 100 lists, 500 items, 120/600/10k chars,
  12 tags of 40; another account's list answers 404. Wired in api.py via saved.install(app).
- Web: SaveControl on result rows and the dataset page (one click into Saved, toast with
  "Add to a list" and Undo, list picker with new-list field); guests save to localStorage
  locus:saved. /lists and /lists/[id] pages: counts, inline note and tags with one Save,
  export links, rename, delete with confirm.
- tests/test_lists.py; test_accounts migration check now follows the migration files rather
  than assuming only 001 exists. docs/accounts.md describes lists and their limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

Fix round for reviews/F1-r1.json.
- F1-r1-1: /account/orcid/start sets a 5-minute HttpOnly SameSite=Lax cookie locus_orcid holding a
  random nonce; the challenge stores only its SHA-256. The callback refuses (orcid=failed) a browser
  without the matching cookie before redeeming the code, clears the cookie on every outcome, and a
  link attempt must be finished by the same signed-in account.
- F1-r1-2: user verification REQUIRED in registration and authentication options, and
  require_user_verification=True in both verify calls.
- F1-r1-6: signing in ends the session the browser came with; a refused passkey gets one fixed
  message and the library's reason is logged server-side.
- F1-r1-5: require_intent docstring says which origins get the CORS preflight.
- F1-r1-3/4: docs/accounts.md says a public deployment needs a proxy that appends X-Forwarded-For,
  and that dev-login's loopback check reads forgeable hops.
- Tests: a software ES256 authenticator drives the real py_webauthn verifier with and without UV;
  login CSRF and link-hijack attempts across browsers; a link finished by another account; session
  rotation on sign-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- POST /lists/saved/import (omics_agent/saved.py): one request adds a browser's guest saves to the
  built-in Saved list in their saved order, in one transaction. Idempotent (accessions Saved already
  holds, or repeated in the batch, are skipped and keep the account's note, tags and snapshot); each
  item validated as a single save (accession, bounded snapshot, note, tags); at most 500 items; when
  Saved would pass 500 the rest are reported in left_out. Answers added, already, left_out, held.
- Web: GuestImport panel (components/guest-import.tsx, in the root layout) appears after sign-in when
  localStorage locus:saved is non-empty, counting saved datasets and watched searches. Add calls the
  batch endpoint and drops local copies only for accessions the server says it holds; Not now is
  remembered per browser and account (locus:import-declined, account ids).
- Watched searches are counted but stay in this browser until F4; F3 adds no table or migration.
- tests/test_guest_import.py; docs/accounts.md section; page flow F3.mjs in the run dir.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Users-DB migration 003 (watches: user_id, question, since, seen JSON, created_at; unique per user and question)
  and omics_agent/watches.py under /follows/questions: list, watch or record a re-run's results (seen merged,
  newest 500 kept), unwatch, and an idempotent import batch. Limits: 200 watches, question 500 chars, seen 500.
  CSRF guard, session and error shape from users.py; tests in tests/test_watches.py.
- web/lib/watches.ts: one store for watches, the account when signed in and localStorage locus:watched for guests.
  The results' Watch control, the "new" marks and the home page's "Watching since" list use it. A re-run of a
  watched search marks results it had not shown, keeps those marks with the result, and records them as seen.
- Import panel (F3) now imports watched searches too and appears when the browser holds only those.
- F3-r1 fixes: unreadable saved entries are skipped client-side and reported in plain words; a 422 never shows
  validator text; the panel is a hairline-ruled strip docked to the bottom edge (no box, no page shift, compact on
  phones); it names up to three entries and offers "Clear from this browser"; after an Add that left items behind
  it is not offered again to that account; focus moves to the panel heading or the page heading and outcomes are
  announced; imports, single saves and watches run in BEGIN IMMEDIATE transactions.
- docs/accounts.md: watched searches section, import section updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pacing

- Person pages resolve the name to an ORCID from Europe PMC's author ORCID tags (newest 100 papers) and offer Follow
  only when one ORCID accounts for most tagged papers; a name several ORCIDs share, or one with no tags, gets a plain
  explanation and no Follow. Coverage is stated ("links 2 of 42 papers under this name to this ORCID").
- Migration 004 (follows), /follows/people (list, follow, unfollow, mark seen, updates) with current_user and
  require_intent; /person/identity for the page. Updates use AUTHORID results only: at most 40 follows, 4 at a time,
  45 s overall, 6 h cache per ORCID; datasets via person.datasets_of, newest paper first, new until seen.
- pace.py: app-wide slots for Europe PMC (5/s) and ORCID (2/s), idea from papertok's upstream-pace; person.py and
  paper.py go through it. LOCUS_EPMC_BASE / LOCUS_ORCID_BASE make the bases configurable for fixtures.
- /following page (followed people and watched questions, no searches run; signed-out status screen), Follow on the
  person page with the AuthPrompt for guests, docs/accounts.md section.
- tests/test_follows.py: two-people guard, coverage, authorization across accounts, bounds, deadline, cache, pacing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…strip

- F4-r1-1: one key for a watch (the trimmed question) in watches.ts lookups and writes; search.tsx trims before
  searching and when restoring, so a trailing space no longer breaks Watch or reruns a kept search.
- F4-r1-2: the import strip writes its measured height to --dock; main's bottom padding and the results'
  selection bar (Compare, copy) sit above it.
- F4-r1-3: visible "N new since your last run"; Watch tooltip and new badge say the same.
- F4-r1-4: a failed /account/me falls back to this browser's watches and retries on the next mount; "failed" only
  for a signed-in read that fails.
- F4-r1-5: questions over 500 characters show a plain reason instead of Watch; setWatch refuses them too.
- F4-r1-6: Clear / Keep them move focus to the matching control; offer and result are announced in the
  persistent live region, which no longer remounts.
Checked by RUN/tools/flows/F6.mjs (and F4.mjs wording).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST/DELETE /lists/{id}/publish (owner only, intent guard) draws a random 32-hex share id from secrets and freezes
a copy (name, description, first 50 items with snapshots; notes and owner name only when ticked, tags never) in
lists.share_snapshot (migration 005). GET /public/lists/{id} serves it without a session, no-store and noindex;
withdrawn, unknown and malformed ids all answer 404, and no route lists ids. 60 publishes/updates a day per account,
counted from the audit trail. List page: "Share a public link" with both choices unticked, then
Public: link · Copy · Stop sharing, and a stale note with "Update public page" when updated_at > published_at.
/s/[shareId] is rendered on the server with title, description, robots noindex and no referrer; a not-shared page
for 404. Tests in tests/test_public_lists.py; docs/accounts.md updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… waits

- Identity guard (F5-r1-1): Follow only when the name is not common (<= 300 papers, the page's own threshold), the
  ORCID is tagged on >= 3 papers and on more than half of the tagged papers (counted as papers, not pairs), and the
  ORCID record's own name agrees with the page's (same surname, same spelled first name, initials that agree; "C"
  never matches "CC"). Unreadable ORCID record: "unconfirmed / no_record", kept 5 minutes. New statuses and panels.
- Pacing (F5-r1-2): Pace.wait refuses any slot more than 2 s away (PaceTimeout, now an httpx.HTTPError) and routes
  answer 503 with Retry-After; a Gate caps uncached person/identity reads at 8 at once; /person and /person/identity
  are rate-limited per IP and overall; the ORCID lookup has a 5 s deadline; person() has a 6 h TTL cache that keeps
  no failures.
- Coverage (F5-r1-3): "Europe PMC ties N of the newest M papers under this name to this ORCID. Following uses all K
  papers Europe PMC links to the ORCID, so only papers tied to it are used."
- /updates (F5-r1-4): one shared 4-worker pool, a single fetch per ORCID, one upstream read per account every 10 s
  (otherwise cached answers, "wait"), failures kept 5 minutes.
- Lows: follow keeps the ORCID record's name and ignores the client's (F5-r1-7); seen keeps 5000 (F5-r1-8).
- F5-r1-5 settled by DECISIONS D12 (no count for watched searches); docs say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- F6-r1-1: staleness no longer compares whole-second timestamps. lists.rev counts every change (bumped in _touch,
  and a PATCH that changes nothing no longer counts); publish stores published_rev; stale = rev != published_rev.
  Migration 006 adds both columns (and sessions.strong_at for Settings). Test edits in the same second as publish.
- F6-r1-2: focus moves into the share form when it opens, to the share link after Publish, Update or Cancel, and to
  "Share a public link" after Stop sharing.
- F6-r1-3: "Show my display name" and "Include my notes" are <label>s for their checkboxes.
- F6-r1-4: the share row is spaced by gaps instead of "·" text, so no wrapped line starts with a separator.
- F6-r1-5: the F7 flow screenshots the real /s/<id> signed out at 1280 and 390, the owner's public and stale
  controls at both widths, and the not-shared page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eletion

/settings has Account (display name; passkeys listed with name, added and last used, rename, add another with the
F1 registration ceremony, remove but never the last; recovery codes left and regenerate, shown once; ORCID unlink,
refused when ORCID is the only way in), Following (people and watched searches with Unfollow / Stop watching),
Privacy, Your data (GET /account/export: every row of the account as JSON, without token hashes, code hashes,
passkey public keys or the WebAuthn user handle) and Delete account.

Deleting the account and regenerating codes need a passkey or recovery-code check within five minutes, kept on the
session (sessions.strong_at). Passkey and recovery sign-ins set it, and /account/confirm/* records a fresh one using
only the signed-in account's own passkeys and codes. Dev and ORCID sign-ins never count; there is no other setter.
Delete removes every row in one transaction (public links 404 at once), keeps one audit row "account deleted" with
no user id, and clears the cookie.

New: omics_agent/settings.py, tests/test_settings.py, web/app/settings, web/components/settings-page.tsx,
web/lib/settings.ts; docs/accounts.md gains a Settings section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- F5-r2-1: a page in Europe PMC's form ("Thoreen CC") agrees with an ORCID record that spells the
  first name when the record's initials are the page's or their start ("Carson Thoreen" yes,
  "Carson J Thoreen" and "Kevin Thoreen" no); initials-only records stay strict. Rule and examples
  in docs/accounts.md. New gate page /a/Thoreen%20CC (flows/F5-epmc.mjs).
- F5-r2-2: a follow keeps the person page's name (migration 007, follows.page_name) and /following
  links to it, so the linked page shows Following and Unfollow.
- F5-r2-3: POST /follows/people checks duplicates and the 200 limit before any ORCID read, is limited
  to 30 a minute per account, and answers 503 BUSY when locus's ORCID pace is full. A busy pace is
  never cached as "no_record"; an ORCID failure is kept 20 s, a missing record (404) 5 minutes.
- F5-r2-4: /person and /person/identity limits count only uncached reads; rate limits key IPv6
  callers by /64 and IPv4-mapped addresses by their IPv4 address.
- F5-r2-5: busy is not mistaken for failure: /cite answers 503, a paper page reports busy, and the
  supplementary-size cache never keeps a busy moment.
- F5-r2-7: plainer coverage sentence, "the newest 100 of K" past 100.
- F5-r2-8: at most 8 fresh /updates calls at once (503 at once past that); LAST_UPDATES is pruned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y Confirm

- F7-r1-1: adding and removing a passkey and unlinking ORCID need a confirmation within five
  minutes. A passkey added from Settings records its session (migration 008,
  credentials.added_in_session) and cannot confirm that session. Exception: an account with no
  passkey may add its first within five minutes of an ORCID sign-in on this session. Linking ORCID
  to a passkey account needs a fresh confirmation; an ORCID-only account never swaps its iD; its
  own iD again while signed in is a fresh ORCID sign-in. Tests replay the reviewer's chain.
- F7-r1-2: /privacy page built from web/lib/privacy.ts (one source of the user-facing facts);
  Settings links to it with Link.
- F7-r1-3: Confirm tells an account with no passkey or codes
10BC8
 to add a passkey first, with a
  button that starts it; "Add a passkey" when there is none.
- Lows: focus after Remove, ink delete button, copy slips, docs "Not built yet" fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on, status screens

- Search: a fast /api/search answer no longer gets wiped by the late view-transition reset; the search starts after
  updateCallbackDone (F4's finding). The F4 flow now answers /api/search at once.
- Header: Search, Following (signed in) and Lists (guests too), current page marked in signal, a menu button at
  phone width; the guest list picker and the save toast link to /lists (F2-r1-1). Account menu keeps Settings.
- Command palette on "/" and Ctrl/Cmd+K: accessions through /resolve on Enter only, followed people and researcher
  pages, your lists, recent searches (local storage, last eight), pages. Typing never runs a search.
- Shared toast (components/toast.tsx): success clears after six seconds unless hovered or focused, errors stay in an
  alert until closed, Undo where reversible; save-control's local toast moved there.
- EmptyState in page-kit for no lists, empty list, nothing saved, nothing followed, nothing new, signed-out list and
  settings.
- Reduced motion also stops CSS transitions; the palette and toasts animate only under motion-safe.
- /privacy: request log with IP addresses, the one-minute rate limiter (which now drops idle addresses within a
  minute, with a test), the account log kept until deletion, guest data in the browser. docs/accounts.md rewords
  the added_in_session rationale (F7-r2-1, -2, -3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hes the limiters

F8-r1-1 (high): hovering or focusing a toast held every later toast too, so after one mouse click on
Undo no success toast cleared itself. The hold is now keyed to the toast's id.
F8-r1-2/3 (medium): /privacy now says the limiters drop an address the next time they are checked after
a minute, that restarts forget all, and that researcher-page lookups (guests too) are limited by address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rite quota, storage cap, no-store, docs)

Co-Authored-By: Cursor <noreply@cursor.com>
…Settings, confirm with an email code

Codes are hashed, single-use, ten minutes and five guesses; sends limited per address and per day. Asking for a code
answers the same for known and unknown addresses. Passkey errors on non-https addresses now say so. Gate PASS
(logs/gate-20261102T031007Z.log): 163 tests, 37 page checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	omics_agent/agent.py
#	omics_agent/api.py
# Conflicts:
#	omics_agent/api.py
# Conflicts:
#	docs/plans/2026-10-18-maxquant-kit.md
… new row is intended; escape the apostrophe)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2026)

Takes every file from UoSDDNB/omics-agent branch all-features (ae43140). The two repositories have separate
histories; this merge joins them, so later updates can be ordinary merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0