Repository navigation
Merged
Conversation
…sets Pure helpers for the rebuilt Annotate tab (#83): runHypeR() defaults held to the function's formals, hypeR-native gene list parsing and per-test checks, every background form including per-signature lists, and genesets from the MSigDB cache (list or table form), live msigdbr via getHypeRGenesets(), hyperdb rgsets and custom files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing R code Builds runHypeR()/prepareHypeRSignatures() arguments per signature source and test, runs them keeping warnings, reads hyp and multihyp results into tables, provenance, summaries and per-geneset enrichment details, sizes plotHypeRDots() output, and writes the R script that reproduces a run and its plots (#83). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tab now only turns inputs into runHypeR() arguments and reads the result back with SigRepo's hypeR functions (#83): - Signatures from repository picks, uploaded OmicSignature .rds, or hypeR-native gene lists, one source per run, up to ten. - Genesets from the MSigDB cache (live msigdbr when allowed), hyperdb rgsets or custom GMT/CSV/text. - Every hypergeometric, KS and fgsea argument with its default in the label, per-signature backgrounds, and a query preview through prepareHypeRSignatures(). - Results: plotHypeRDots(), plotHypeREnrichment() with ES/NES and leading edge, plotHypeRMap() enrichment and hierarchy maps, a full-precision results table, per-query provenance, hypeR's own rctbl, the reproducing R code, and .rds and hypeRToExcel() downloads. Removes the module's own query builders, dot plot, GSEA curve, GEM modes and report bundle, hypeR_module.R, and the tests of the removed builders. Symbol lookup now happens in the client, so the app no longer sources api/lib/symbols.R. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- The Enrichment tab rebuilds its controls from the Results-table pick, so a row picked before the tab has opened, or a new run whose query names match the last one, no longer leaves the geneset dropdown empty. - req() inside a drawing step reaches Shiny instead of showing a blank 'could not be drawn' message. - Integer browser inputs compare equal to runHypeR()'s defaults, so the R code no longer writes fgsea_args = list(sampleSize = 101L, minSize = 1L). - An empty dot plot says which cutoff to raise; cleared plot controls fall back to their defaults; wider query dropdowns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dropdowns hypeR draws enrichment maps with igraph's layout_nicely, which lines up every unlinked geneset along one edge (most of a Hallmark map). The Maps tab offers force-directed, circle and grid layouts as well, applied with visNetwork::visIgraphLayout(), and the R code writes the same call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(feedback) From Cameron's Notes/Feedback/shiny_annotate_feedbac: - hypeR rgsets dropped (too heavy for the droplet); MSigDB and custom genesets stay. - Hypergeometric keeps split and min query genes; KS and GSEA keep direction only. Ranked table, score column, power, absolute, seed, fgsea sizes and the whole significance block are gone and stay at runHypeR()'s defaults, named in the help text under Background. - Background is a primary option rather than hidden in a details panel. - Results drop the Maps and Provenance tabs, and their helpers go with them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
montilab.bu.edu runs dev and needs an image whose R packages match it. Until now a dev change that added a package could not be tested on staging until it was merged to master, which defeats the point of the staging gate. The dev trigger is gated in a new `decide` job rather than by a second `push:` key with its own paths filter. YAML keeps only the last duplicate key, so that spelling would have silently deleted the master build with nothing to say so. On dev: build only when Dockerfile, install_r_packages.R or DESCRIPTION change, tag :dev, bake SIGREPO_BRANCH=dev, and build only the sigrepo-api entry, since all three filtered paths belong to its context and neither sigrepo-mysql nor sigrepo-web is under test on the staging line. On master and main: unchanged. The extra-repo tag is computed from the same value, so :dev reaches montilab/sigrepo, which is the name docker-compose-local.yml resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…atest montilab.bu.edu is the staging instance and runs the dev image, while every other stack runs latest. Hardcoding :dev here was not an option: the Mac development stack uses this same file, so it would have changed every local stack on every machine. Declared once as an x- anchor, beside the volume anchors this file already uses, so the explanation lives in one place rather than three. sigrepo-mysql and sigrepo-web are deliberately left on :latest: neither is built from the branch under test, so staging runs the same ones as everyone else. Verified that `docker compose config` output is byte identical to before when SIGREPO_IMAGE_TAG is unset, and that setting it moves exactly sigrepo-api, sigrepo-shiny and sigrepo-mcp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checks the failures that are silent and act at a distance: gene sets no longer
resolving because the source bind mount masks the cache baked into the image,
and Compare breaking because the image predates ComplexHeatmap, circlize, cba
and fgsea. Neither shows as a failed container or a non-200 response.
Shell, curl and python3 only, because montilab has no browser installed and its
host R package set is not guaranteed. The Shiny sign-in check lives separately.
Two failure paths matter as much as the happy path and are covered by
tests/fixtures/fake_api.py, which serves a valid but completely empty
repository:
- a dead API fails in seconds rather than hanging, because a stalled deploy
looks exactly like a slow one
- an empty database is a clean FAIL, not a traceback and not a pass; a stack
whose database failed to attach still answers 200 on every route
Containment is opt-in rather than silently skipped: a check that quietly does
nothing is worse than one that is absent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
HTTP 200 from the Shiny port proves Shiny Server is running. It says nothing
about whether the app works, whether the database is reachable from it, or
whether anyone can get in.
Separate from scripts/smoke_test.sh because montilab has no browser installed,
so the API checks run on the staging host during a deploy while this runs from
a machine that has one, through the SSH tunnel.
Three workarounds are load bearing and each carries a comment saying why:
- a zero-width viewport suspends Shiny outputs, so the window is sized first
- #sign_in_btn carries an inline onclick Enter-key helper that throws on a
plain click, so the inputs are set with Shiny.setInputValue first
- the login elements stay in the DOM after signing in, merely hidden, so the
assertion is on the signed-in state rather than their absence
That last one is not theoretical: the tab bar renders identically signed in and
signed out, so a check asserting on tabs alone would pass with a bad password.
Verified that this one fails with a wrong password.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
camv is deliberately not in the docker group on montilab: the host is shared and
BU-managed, and the lab keeps no standing docker access on it. So a staging
deploy is one sudo line, and everything else lives in this script and its log.
What it refuses to do is the point:
- it checks BOTH checkouts can fast forward before moving EITHER, because
updating the server and then failing on the client leaves staging running a
mismatched pair, which is worse than not deploying
- it refuses to run without SIGREPO_IMAGE_TAG in .env, which would otherwise
silently deploy the production image and make every later result misleading
- it refuses an image tag that was never published, turning a confusing
"manifest unknown" halfway through into a clear message before anything moves
- it refuses to merge over uncommitted tracked changes
- it never names sigrepo-local-mysql, so the seeded replica is never at risk
- it fails the containment check loudly if any port leaves 127.0.0.1
tests/test_deploy_montilab_guards.sh exercises every one of those refusals and
asserts that none of them reaches a mutating step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Staging is writable on purpose: production forbids adding, deleting or updating signatures, so addSignature, updateSignature and the permission paths can only really be exercised on montilab against real data. The cost is that test data accumulates, and until now the reset was a hand-run mysqldump that existed only as a memory of what was typed. The guard matters more than the feature. This drops a database, and production MySQL runs as "sigrepo-mysql", one character away from the "sigrepo-local-mysql" this is for. The container name is checked against an allow-list first, before the file is read and before docker is touched, so it cannot be aimed at production by a typo or a stray environment variable. It also refuses a corrupt dump before dropping anything, keeps a pre-restore backup and names it in every later failure message, and verifies row counts afterwards instead of trusting an exit status. That last point is from experience: reading $? after a `grep -v` that filters out every line reports failure on a successful restore, which is exactly what happened on 2026-09-24. This reads PIPESTATUS for mysql itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CONTRIBUTING.md told people to branch with --base master. dev is now the integration branch and is what the montilab.bu.edu staging instance runs, so the documented workflow and the real one disagreed, and the documented one is the one people follow. States the rule the arrangement depends on: master gains commits only through a release pull request, and a hotfix straight onto master is merged back into dev the same day. That is what stops the two branches drifting apart, which is how this arrangement usually goes wrong. Also documents how to reach staging, which is through your own SSH tunnel with your own BU account. Every port on montilab is bound to 127.0.0.1 and stays that way: it is a shared, BU-managed host, the lab has been warned once about an exposed port already, and the staging database holds real signatures behind accounts that can edit them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…backup
Two defects, both on paths that only appear when things go RIGHT, which is why
neither showed up in the refusal tests.
A trailing `|| true` is a separate command in the AND/OR list. When the restore
succeeds, mysql exits 0 printing only its password warning, grep filters every
line and exits 1, so `true` runs -- and PIPESTATUS collapses to true's own
single element. Under `set -u` the next line, status=${PIPESTATUS[1]}, is then
a fatal unbound variable. The script died on restores that had just worked,
never printing the row counts or where the backup went. Moving `|| true` inside
a brace group keeps it out of the AND/OR list and leaves PIPESTATUS intact.
The pre-restore backup was written and never checked. gzip produces a valid
archive when its input produced nothing, so a failed mysqldump leaves a
plausible-looking two-byte file, and the script would drop the database anyway
and then point every later failure message at it. It is the only copy of
whatever staging held.
tests/test_reseed_staging.sh covers both, and drives the actual pipeline shape
so the trap is reproduced rather than described.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The compare check tested the response body for truthiness. The API answers
failures with json_error(), which serialises to [{"MESSAGES": "..."}] -- a
non-empty JSON list, and therefore truthy. So an image predating ComplexHeatmap,
circlize, cba and fgsea produced HTTP 500 "there is no package called
'ComplexHeatmap'" and the script printed PASS and exited 0. That is the precise
failure this check was written for, reported green. Now it requires a 200 and a
payload that is not an error envelope.
The containment check looked for the literal string 0.0.0.0, and only among
sigrepo-local containers. A bind to a routable address (128.197.229.35:8020->)
or to the IPv6 wildcard ([::]:8020->) contains no 0.0.0.0 and passed, and a
vm-compose stack published on 0.0.0.0 beside a local one was filtered out
entirely. It now asserts that every published binding IS on 127.0.0.1, across
every sigrepo container. On a shared BU host that distinction is the point.
Timeouts: the default 120s applied to enrichment and compare, on a 2-CPU host
with a single R worker, and deploy_montilab.sh passes no --timeout at all. A
slow-but-healthy staging instance produced two red checks and a deploy reporting
"not healthy". Enrichment and compare now get their own budget, and every
request gets --connect-timeout so a filtered host still fails in seconds rather
than burning the full budget.
tests/test_smoke_test.sh covers all of it, including the binding formats that
are awkward to produce on demand, via an overridable DOCKER_PS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Staged changes passed the preflight. `git diff --quiet` compares the working tree to the index, so `git add` with no commit sailed through it -- and then `git merge --ff-only` fails. Because the merge loop does the server first and the client second, that left staging running a NEW server against an OLD client: precisely the half-deployed state the two-phase check exists to prevent. Now checks tracked changes with --untracked-files=no. Untracked files break --ff-only too, but only when origin/dev is about to add that same path, so only those are refused. Refusing any stray file would have blocked every deploy on montilab, which legitimately carries an untracked run_host_shiny.sh, and a guard people have to route around is not a guard. Nothing checked which branch the checkouts were on. Every comparison is against origin/dev, but `git merge --ff-only origin/dev` fast forwards whatever HEAD points at, so on a checkout still on master it would have moved master to dev's tip. That is the direct-to-master change branch protection and the hotfix rule exist to prevent, and it would make "what is staging running" unanswerable from the branch name. A mistyped flag ran a real deploy. `--dryrun`, `--dry_run` and `-n` all fell through to fetch, fast forward, pull and recreate. Pasted as a sudo line on a shared host, silently upgrading "show me what would happen" into "do it" is the wrong way to be wrong. Also appends to the log instead of truncating it: the log of the deploy that just failed is the thing you want when the retry fails too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
workflow_dispatch set build=true in the decide job but changed neither the Docker Hub login step nor the build step's push: input, both gated on github.event_name == 'push'. A manual run therefore warmed the build cache and published nothing, which is the opposite of the reason it was added. That is not academic. Merging this pull request into dev touches only a workflow file, which is not in the paths filter, so no :dev image appears -- and deploy_montilab.sh refuses to run without one, telling the operator to use workflow_dispatch. Without this fix that advice was a dead end and the only way out was the empty commit workflow_dispatch exists to avoid. Also: after a force push to dev, github.event.before is unreachable and git diff fails. The old code swallowed that and fell through to build=false, so a force-pushed Dockerfile change would publish nothing and the next deploy would quietly run an image that did not match the branch. It now builds when it cannot tell, matching how the all-zero "before" of a first push is already handled. test.yml's comment said the image build fires on master only and nothing consumes a dev image. Both halves are now false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…arded matrix.build_args is a block scalar that already ends in a newline, and sigrepo-web's is empty. Written as a two-line block, the rendered input for that entry begins with a blank line, and whether docker/build-push-action discards it is a bet with no upside. Concatenating yields exactly the arguments for all three entries with no blank lines in any of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…when-dependencies-change-on-dev Publish montilab/sigrepo:dev when dependencies change on dev
…g-from-sigrepo_image_tag-defaulting-to-latest Select the sigrepo image tag from SIGREPO_IMAGE_TAG
…he-staging-instance Add an API smoke test for the staging instance
…n-check-for-staging Add a headless Shiny sign-in check for staging
…ript-for-the-montilab-staging-instance Add a one-command deploy script for the montilab staging instance
…-staging-database Add a reseed script for the staging database
…-release-pull-request-and-the-staging-tunnel Document the dev branch, the release pull request and the staging tunnel
GitHub auto-closes an issue only when the pull request merges into the repository's default branch. Pull requests now target dev, so Closes #NNN does nothing at merge time and fires when the release pull request carries dev into master. Observed on 2026-09-24: nine pull requests merged into dev across both repositories, every one carrying Closes #NNN, and all nine issues stayed open. The behaviour is defensible -- an issue open until its change is live means the board shows what is in production rather than what is merged -- but the file promised something different, and a rule people follow that does not do what it says is worse than one that is honest about its timing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The suite ran in montilab/sigrepo:latest on every branch, which defeated the point of building a dev image at all. A dev branch adding an R package would build :dev correctly and then fail its own tests inside :latest, which lacks that package, and the only way to go green would be to merge to master first -- exactly the loop the staging gate exists to break. Deliberately held back until now: flipping this before a :dev image existed would have made every run on dev fail to pull its container. montilab/sigrepo:dev was published on 2026-09-24, so the precondition is met. master and main are unaffected and keep using :latest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ose-at-merge-under-the-dev-model-they-close-at-release Say when Closes #NNN actually fires under the dev model
…dev-image-once-one-exists Run dev's test suite in the dev image
The first real staging deploy failed containment on sigrepo-activate-user (8080) and sigrepo-rstudio (7878). Every container of ours was correctly on 127.0.0.1. Those two are other lab services on the shared host that merely share the name prefix; they have been internet-open since long before SigRepo staging existed and are not ours to change. So every montilab deploy ended "staging is running the new code but is not healthy" and exited non-zero, for a condition nobody can fix. A check that cries wolf on every run is one people learn to ignore, and this is the check guarding the constraint the lab has already been warned about once. Matching only sigrepo-local-* is also wrong: it would hide a vm-compose or default-compose stack of ours published on 0.0.0.0 beside a local one, which is the case the prefix match was introduced for. Match our container names exactly instead. They are enumerable and all three compose files agree on them. Also fixes the test harness, which fed synthetic listings through an inline "printf %s\n$fake". That mangled the tabs and newlines and word-split on the ports, so the containment cases were passing for reasons unrelated to what they claimed to test. They now go through a real file. Verified by mutation: putting the over-broad match back fails exactly the two new cases and nothing else. The montilab listing in the tests is the real one from the 2026-09-24 deploy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e-bu-network-so-lab-members-need-no-ssh-tunnel
…b() step leaves no package The first uncached build after #141 published an image with no SigRepo in it. SigRepo's DESCRIPTION imports hypeR (>= 2.0.0) with no Remotes: entry, so with hypeR absent at install time remotes fetched hypeR 2.0.0 from the CRAN snapshot, which no longer builds against the current msigdbr. hypeR failed, SigRepo was removed as "dependency not available", and because install.packages() only warns, R -e exited 0 and the build went green. The GitHub hypeR (2.11.1) then installed cleanly one step later. hypeR and hypeR.GEM now install from GitHub before SigRepo, so its resolution finds them already present. Every install_github() step ends with stopifnot(requireNamespace(...)), matching the ComplexHeatmap line, so a missing package fails the build instead of the containers. Closes #144. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nterface-to-the-bu-network-so-lab-members-need-no-ssh-tunnel Publish the staging Shiny interface on SHINY_BIND so BU-network users need no tunnel
…o-before-hyper-so-sigrepo-pulls-cran-hyper-200-fails-and-the-image-ships-without-sigrepo Install hypeR before SigRepo and fail the build when an install step leaves no package
The hypeR client changed after the tab was rebuilt on it. runHypeR() now requires organism and test, gives neither a default, and no longer takes direction or absolute: kstest and fgsea always test both ends of the ranking, as two queries, up and down. The tab follows. Organism moves out of the MSigDB picker to the top of Step 2 and is sent with every run, since it is now the organism of the signatures and not only the species the genesets are loaded for. The Direction control and the absolute setting are removed. Changing the organism drops loaded MSigDB genesets and keeps custom ones, which do not depend on it. A signature of another organism now blocks a run with a message that names it, where before it was a note against the loaded genesets. runHypeR() stops on that mismatch, so the tab says so first. The tests skip unless the installed SigRepo has the final client, which they recognise by runHypeR() taking organism and not taking direction. This was written on 2026-09-16 and left uncommitted in the worktree. It is committed as it was found, after running the tab's tests against the SigRepo dev client: 265 expectations, none failing, none skipped. Refs #83. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GitHub honours closing keywords only on merges into the default branch. Pull requests target dev, so issues stayed open after their pull request merged. The workflow closes them at merge and CONTRIBUTING says so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e-into-dev Close linked issues when a pull request merges into dev
The branch was cut on 2026-09-14 and dev has moved 84 commits since: the type and platform rename, the React app's removal, the repository cleanup and the staging image fixes. Two conflicts. bootstrap.R sources both utils/annotate_utils.R, added here, and utils/signature_utils.R, added on dev. annotate_module.R keeps this branch's rebuild; dev's two changes to the old module applied the rename to code the rebuild replaces, and the rename is applied to the rebuild in the next commit. Refs #83. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The tab has shown an under-development page since #86 while its rebuild on SigRepo::runHypeR() was tested. This puts the rebuilt module back in the navigation and restores its server call. The signature table and the picked-signature summary read `type`, which is what signatures.direction_type became in #124; they were written before the rename and would have shown a blank. An uploaded .rds saved before the rename still carries direction_type, so the upload summary reads either and leaves it to runHypeR() to say why that file cannot run. tests/testthat/Rplots.pdf was a plot device's output committed by accident with the rebuild, and is removed. Checked in montilab/sigrepo:dev, the image staging runs (hypeR 2.11.1, fgsea 1.32.4, SigRepo from dev): the tab's tests pass with 265 expectations and none skipped, and the tab was driven in a browser against a copy of the repository for human, mouse, categorical and proteomics signatures under each of the three tests. Refs #83. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
test.yml checked out the SigRepo client with no ref, so every run took the client's master, and it chose the image from github.ref_name, which on a pull request is the merge ref and never "dev". A pull request into dev therefore ran in :latest against the client's master: a pairing that is never deployed. For the Annotate tab that meant no test ran. Its tests skip on a client without the rebuilt runHypeR(), which is every client but dev's, so 19 of 40 blocks, including every module test, would have skipped and the job would have passed. Both now read the line from github.base_ref on a pull request and github.ref_name on a push: dev gets the client's dev and :dev, everything else the client's master and :latest. Refs #140. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
C2, and the HPO and GO:BP subcollections of C5, are no longer offered in the picker. GO:CC and GO:MF stay. A run happens inside the one R process every visitor shares, and R does not hand freed memory back to the system, so a large run costs the app and not the session. It is the same cause as #88 for the Compare tab. Measured in montilab/sigrepo:dev with one signature, hypergeometric, each on a freshly started app at 1.4 GB: H 50 genesets 3 s 1.5 GB C5 GO:CC 1,080 21 s 2.3 GB C5 GO:MF 1,872 41 s 2.6 GB C2 CGP 3,555 75 s 3.8 GB C5 HPO 5,793 the process was killed for memory C5 GO:BP 7,538 the process was killed for memory The container had about 4.5 GB to itself. After the C2 run the process was still at 4.0 GB twenty seconds after the browser closed. The rows stay in the table and are filtered by ANNOTATE_MSIGDB_WITHHELD, which takes a collection or a collection/subcollection, so offering one again is removing an entry. Refs #83. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The picker listed every MSigDB collection it knew, 44 choices across the two organisms, of which the cache scripts/build_msigdb_cache.R produces held 6. The rest, every mouse-native collection and human C1, C3, C4, C6, C7 and C8, ended in "not in this server's MSigDB cache". It now offers what is cached for the organism chosen, and an organism with nothing cached is told so and pointed at custom genesets. C5 is withheld whole, joining C2. GO:CC and GO:MF ran, but took the shared Shiny process from 1.4 GB to 2.3 GB and 2.6 GB, and it does not give that back. On a server with the standard cache this leaves Hallmark, for human and mouse, and custom genesets. What is asked of the loader is checked against what is offered. The dropdown kept the last organism's collection after the organism changed, so choosing an organism with nothing cached and pressing Load asked for that organism's Hallmark file; found by driving the tab in a browser, not by the tests, which set the inputs directly. The check also means a withheld collection cannot be loaded by a stale or crafted input. A server that allows live MSigDB fetches no longer widens the picker. annotate_load_msigdb() can still fetch, but nothing in the tab asks it for a collection that is not cached. Refs #83. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ab-on-runhyper Rebuild the Shiny Annotate tab on SigRepo::runHypeR() and open it
The Home tab gets the React landing page's layout: a thin title bar with one "Browse signatures" button, four stat cards with colour-coded icons, and three chart cards (organism donut with legend, assay type, top five contributors) on the same washed-blue background and card styling. The Quick Actions card and the footer go, as they did in React. The totals and chart frames come from pure helpers in home_utils.R so they are tested without a server; the module is tested under testServer() against a canned signature frame. Closes #152 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…to-match-the-retired-react-dashboard Restyle the Shiny Home tab to match the retired React dashboard
…uture workers The HTML5 UP page template under legacy_app/www, its scripts and sass, a vendored Font Awesome 5 that duplicated the Font Awesome 6 Shiny serves through icon(), duplicate images and five unreferenced stylesheets were never loaded by the app: 62 of the 69 files under www, 3.3 MB of 3.4 MB. The test, resource and login modules, the view modal, upload_modal_server() and a commented-out delete modal had no caller, and app.js carried handlers for markup the app no longer renders. app.R used to source bootstrap.R, which sourced every module into the global environment, and then source every module again into its own environment. bootstrap.R is now the one loader and sources into the environment app.R gives it. It no longer attaches future and promises or calls plan(multisession), which nothing in legacy_app used and which started an idle R worker per CPU core (twelve, about 90 MB each, in a container on a 12-core Docker Desktop VM), nor rmarkdown (the report template went with #83) nor a sibling hypeR-GEM checkout (the rebuilt Annotate tab has no GEM modes). The hypeR-GEM install stays in the Dockerfile because the SigRepo client imports it. Verified with the ten Shiny test files (660 passes before and after) and a logged-in walk through every tab on a preview container: every asset returns 200, every icon renders from Font Awesome 6, and the container runs a single R process. Closes #155 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… title bars instead of hero banners The app carried four visual systems: the Home tab's React tokens in home_dashboard.css, five other tabs each with ~100 lines of inline tags$style under their own class prefix (a gradient hero banner, drop shadows, uppercase tracked labels, five different gradients), a plain Bootstrap 3 navbar in another navy, and an Arial sign-in card over a photo. Feedback was unstyled and nothing styled the DataTables chrome. www/assets/css/sigrepo.css now holds the whole look, with the Home tokens promoted to the root: wash #eaf1fb, surface #ffffff, ink #101828, accent #2563eb, border #d7e3f2, the system font. Every tab is a .sr-page with a thin title bar (title, one sentence, the tab's primary action) above cards; the modules share class names (.card, .sr-step, .sr-toolbar, .sr-summary, .sr-empty and so on) and carry no CSS. The navbar is ink with an accent underline on the active tab, and the Longevity Consortium logo lives in the navbar's title slot, which also removes the startup warning about a bare image child (it hides below 1100px, where it would sit on the last tab). The sign-in card, the register and profile modals, Bootstrap's inputs and buttons, selectize and DataTables (header band, hairline rows, quiet export buttons, accent-tint selected rows) take the same tokens. Uppercase labels are sentence case; the step pills stay on Annotate and Compare only. app_style.css, sign_in_style.css, home_dashboard.css and the sign-in photo are removed. tests/testthat/test-shiny-stylesheet.R checks that no module carries inline CSS, that every tab renders inside the shared skeleton, and that every class the app emits has a rule in sigrepo.css. Verified with the eleven Shiny test files (688 passes) and a signed-in walk through every tab on a preview container at 800, 1000 and 1440 wide. Closes #157 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…column dropdown Review feedback on #158: the column visibility dropdown was hard to read. Buttons 2.x marks a shown column with the dt-button-active class and a darker gradient, and the stylesheet's button rules outranked that cue, so every column in the list looked the same. The colvis button now opens Buttons' fixed, multi-column layout with a title, and the stylesheet draws a box beside each column that is filled with a tick when the column is shown. The two tests that looked for the bare colvis string now use colvis_offered(), which accepts the configured button as well. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Enter key, password hash in the URL, log timer Collections: grouped_collections() grouped collection_db() by its id columns, but a search that finds nothing returns a data.frame() with no columns and group_by() errored, so the tab showed "Error: [object Object]". It now returns the empty grouped shape, which DatatableFX() renders as "No data available." Uploads: both upload handlers called addSignature() / addCollection() without the dialog's Private/Public choice, so every upload took the client's default. They pass it now, through an injected add function (add_signature_fn, add_collection_fn) so tests can see what reaches it. Sign-in: the inline handlers called login_keypress(e), and e is not defined inside an inline handler, so every keypress threw and Enter did nothing; the button's onclick threw the same on every click. The handlers pass event and the button has no onclick. login_keypress() also sends both field values before it clicks: Shiny's text bindings debounce keystrokes, and a click straight after the last character reached the server before the password did. Sign-in wrote the user name and an unsalted MD5 of the password into the URL hash, where it landed in browser history and access logs; nothing read it back. Both hash writes are gone. The keepalive flush that stops an idle tab greying out behind a proxy fires every 20 seconds instead of every 2 and no longer prints a dot to the log each time. Tests: test-shiny-collection-module.R (empty frame, grouping, upload visibility), test-shiny-signature-upload.R (upload visibility both ways), test-shiny-shell.R (the handlers and the button). 705 Shiny tests pass. Verified on a preview container: Enter signs in with no console error and no URL hash, the empty Collections tab shows the message, and the log has no dots. Closes #159 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nto an auth module app_server.R was 993 lines, about 870 of them these flows written longhand: the same isolate-and-trim block twelve times, the email regex three times, three modals built inline, none of it tested. modules/auth_module.R now holds the sign-in card, the three modal builders and auth_server(), a plain server function (its ids stay bare because app.js and scripts/smoke_test_ui.R address them) that takes every database, API and mail-service call as an argument and returns the per-user handler and user row. The checks live in utils/auth_utils.R; validateUser() moved in from its own file. app_server.R keeps the session plumbing, the two repository frames and the tab wiring, 103 lines. Registration and password reset reuse the API's register_new_user() and request_password_reset() from api/lib/user.R, which bootstrap.R sources, so the form and the /users routes behave the same: a reset never reveals whether an account exists, and an account whose admin notification fails says so instead of implying nothing happened. user.R gets its own scalar_text() in place of common.R's json_scalar(), because common.R opens the API's database pool when sourced. The sign-in handler no longer falls back to the literal droplet address 142.93.67.157:8020 when API_HOST is unset; bootstrap logs a notice instead, and the installer and compose files always set it. One field_value() in utils.R replaces the identical helper in the Signature, Collection and Browsing tabs. Tests: test-shiny-auth-helpers.R and test-shiny-auth-module.R (sign-in refusals and success with the handler built from the environment, sign-out, register, reset by username and by email, email and password changes), written first. 797 Shiny tests pass. Verified on a preview container: sign-in by Enter, the register, reset and profile dialogs with their messages, sign-out, no console errors. Closes #161 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…template-files-unused-modules-and-idle-future-workers Remove the Shiny app's dead template files, unused modules and idle future workers
…y-app-the-home-tabs-tokens-on-every-tab-title-bars-instead-of-hero-banners One stylesheet for the Shiny app: the Home tab's tokens on every tab, title bars instead of hero banners
…-empty-state-ignored-upload-visibility-sign-in-enter-key-password-hash-in-the-url-log-timer Shiny bug fixes: Collections empty state, upload visibility, sign-in Enter key, password hash in the URL, log timer
…ster-forgot-password-and-profile-flows-into-an-auth-module-with-shared-validators Move the Shiny sign-in, register, forgot-password and profile flows into an auth module
17 of 20 tasks
… on purpose montilab publishes the Shiny port on SHINY_BIND=0.0.0.0 (#142), so the containment check failed on 0.0.0.0:8051 and every staging deploy ended red for an intended condition. --public-ports lists host ports allowed off 127.0.0.1; only the port is matched, so any other publish, and any other port, is still caught, and the pass line names what was allowed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…low-the-ports-deliberately-published-off-loopback smoke_test.sh: --public-ports allows the ports published off loopback on purpose
20 of 21 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release:
dev->master. Checklist copied from #154 (state as of opening; kept current there). Pairs with the montilab/SigRepo 1.1.0 release PR; both merge in the same window and production rebuilds once afterwards. #151 is left out of this release by decision.Release checklist for the next
dev->masterpull request. Paired with montilab/SigRepo release issue montilab/SigRepo#267 (the client one). The two release pull requests merge in the same window, and production rebuilds its image once, after both are in. See the client issue for why the coupling runs both ways; in one line: server dev calls seven client functions that only exist on client dev, and client dev'srunHypeR()breaks therun_enrichmentMCP tool that server master still ships.Staging is montilab.bu.edu running
montilab/sigrepo:dev; every box below is ticked there unless it says otherwise.Depends on the client release
SigRepo::runHypeR(). On staging, runLLFS_Aging_Gene_2023with hypergeometric, kstest and fgsea against MSigDB Hallmarks: results table, dot plot, enrichment plot, Excel export all work; the generated R script shown in the tab reproduces the call. Run a mouse signature: species is picked from the signature, not a dropdown default. The MSigDB cache is present in the image (startup does not warn "MSigDB cache file was not found").SELECT COUNT(*)on the staging database for signatures, collections and organisms; links from the tiles land on the right tabs.run_enrichmentwithdrawn. The MCP tool list has norun_enrichment;rummagene_enrich,search_signatures,compare_signaturesstill answer. This is the change that makes the client release safe for production.Staging and build tooling (server-only)
dev; theSigRepo Docker Buildworkflow runs withcheckout_ref = devand publishes:dev, not:latest.ARG SOURCE_REFRESHmakes the install step a cache miss:packageDescription("OmicSignature")$Versionin the new image is 1.4.1.library(SigRepo)loads in the image.SHINY_BINDfor BU-network users. Reachable at http://montilab.bu.edu:8051 without an SSH tunnel. 8051 is the only SigRepo port off loopback (the API, MCP proxy and database stay on 127.0.0.1); like the other lab apps on that host it is reachable from the public internet, which was accepted on 2026-10-14 with Shiny's own login as the gate. The smoke test's containment check allows it through--public-ports 8051and still catches everything else.:devimage published when dependencies change; image tag selected fromSIGREPO_IMAGE_TAG. Staging compose runs:dev, production compose runs:latest.sigrepoprefix.Removals
web/; the Shiny app is the only interface and the home page loads.R CMD check-style hygiene: the repo contains code, docs and the testthat suite; the local validation harness still runs fromlocal_validation/where it is gitignored.Process and CI
Closes #NNNtiming; the close-linked-issues workflow fires on merge into dev. Still accurate after the release.Release steps
dev->masterwith this checklist copied into the body. NoCloseslines.montilab/sigrepo:lateston the DigitalOcean host (pull only; pushes come from the Mac). ConfirmpackageDescription("SigRepo")$Versionis 1.1.0 andpackageDescription("OmicSignature")$Versionis 1.4.1 inside the production API container.masterback intodevthe same day.Out of this release
devis excluded. A failed box becomes a bug issue linked here; this issue carriesblockeduntil it closes.🤖 Generated with Claude Code