Rebuilding and Deploying the Developing Mouse Visual Cortex Gene Expression Viewer
This guide documents the complete, reproducible workflow used to convert the public Developing Mouse Visual Cortex files from ABC Atlas Access into the pseudobulk and gene-level files required by the web application, publish those files to public AWS storage, run the R Shiny application, deploy it to Posit Connect, and configure GitHub to update the deployed application automatically.
mouse-developing-vis-app repository and Developing Mouse Visual Cortex viewer. It does not document the SEA-AD Multiregion application.
Source repository: AllenInstitute/mouse-developing-vis-app
Live application: Developing Mouse Visual Cortex Gene Expression Viewer
Data documentation: ABC Atlas Access: Developing Mouse Visual Cortex
End-to-end workflow
- Create a GitHub repository and clone it into a linked Code Ocean Capsule.
- Configure a Python + R Code Ocean environment with all compiled and interpreted dependencies.
- Run
create_dev_mouse_vis_cortex_pseudobulk.pyto produce a pseudobulk H5AD. - Capture the H5AD as a Code Ocean Data Asset and mount it under
/data. - Run
create_mouse_aging_parquet.pyto create gene-level Parquet and supporting metadata, excluding the statistics table. - Run
create_statistics.pyto create the final exploratory gene-statistics table. - Run
create_value_sets.pyto create authoritative category order and color definitions. - Upload the app-ready directory from
/resultsto a public S3 prefix and verify parity. - Test
code/shiny/app.Ragainst the public S3 files. - Create and validate a reproducible
renv.lock. - Deploy the app to Posit Connect.
- Configure GitHub Actions to regenerate the deployment manifest from
renv.lockand update the existing Connect content after changes undercode/shiny/. - Publish
index.htmland this deployment guide with GitHub Pages.
Repository structure
mouse-developing-vis-app/
├── .github/
│ └── workflows/
│ └── deploy-connect.yml
├── code/
│ ├── create_dev_mouse_vis_cortex_pseudobulk.py
│ ├── create_mouse_aging_parquet.py
│ ├── create_statistics.py
│ ├── create_value_sets.py
│ ├── run
│ └── shiny/
│ ├── app.R
│ ├── abc_atlas_gene_url_function.R
│ ├── abc_atlas_schema_kiwi.txt
│ ├── table_column_definitions.csv
│ ├── renv.lock
│ ├── .Rprofile
│ ├── .rscignore
│ └── www/
│ ├── Developing_Mouse_Visual_Cortex_logo.png
│ ├── allen_institute_logo.svg
│ └── tokens.csv
├── data/
│ └── .gitkeep
├── index.html
├── deployment_documentation.html
├── README.md
├── requirements.txt
└── .gitignore
Downloaded expression matrices, caches, H5AD files, Parquet files, and intermediate results are not committed to GitHub.
1Set up the Code Ocean Capsule
Create the GitHub repository
- Create a repository named
mouse-developing-vis-appin the appropriate GitHub organization. - Add the repository structure shown above.
- Keep
data/.gitkeepempty so Git tracks the otherwise empty directory. - Use the Python
.gitignoretemplate and add exclusions for large scientific files.
# ABC Atlas cache and generated scientific data
data/*
!data/.gitkeep
results/
abc_atlas_cache/
*.h5ad
*.h5
*.parquet
# R project state
.Rhistory
.RData
.Ruserdata
.Rproj.user/
renv/library/
renv/staging/
# Python state
__pycache__/
*.py[cod]
.ipynb_checkpoints/
.venv/
venv/
Create .gitattributes to keep runtime scripts compatible with Linux:
code/run text eol=lf
*.sh text eol=lf
*.py text eol=lf
Clone the repository into Code Ocean
- Add GitHub credentials to the Code Ocean account. For a private organizational repository, ensure that the GitHub credential is authorized for the organization.
- In Code Ocean, choose New Capsule → Clone from Git.
- Enter the HTTPS GitHub repository URL.
- Name the Capsule Developing Mouse Visual Cortex Gene Expression Viewer.
- Use Clone from Git, not Copy from Git, so the Capsule retains its repository connection.
Code Ocean maps the repository's code/ and data/ directories into the Capsule's corresponding sections. After cloning, open a terminal and verify the remote and branch:
cd /root/capsule
git remote -v
git branch --show-current
git status
If the repository is renamed, update the remote explicitly rather than relying on a redirect:
git remote set-url origin https://github.com/AllenInstitute/mouse-developing-vis-app.git
git fetch origin
git push --dry-run origin main
Configure the Python + R environment
Select a current Python + R starter environment. The validated workflow used Python 3.12 and R 4.3.3. Add dependencies through the Environment Editor, rebuild, commit the environment, and launch a fresh workstation after changes.
Python packages
git+https://github.com/AllenInstitute/abc_atlas_access.git
anndata
h5py
numpy
pandas
scipy
pyarrow
R CRAN packages
shiny
bslib
dplyr
ggplot2==3.5.2
scales
shinycssloaders
cachem
DT
jsonlite
renv
rsconnect
curl
Conda/Mamba packages
r-arrow
libcurl
openssl
libstdcxx-ng>=13
libgcc-ng>=13
nodejs
awscli
Node package and environment variable
The gene-specific ABC Atlas link uses the Node package kiwi-schema. This is an npm package, not a CRAN, Bioconductor, or pip package. In the Code Ocean Environment Editor, add the following post-install command:
npm install --global kiwi-schema@0.5.0
Add this environment variable so Node can resolve globally installed modules:
NODE_PATH=/opt/conda/lib/node_modules
After rebuilding the environment, confirm all three commands succeed:
node --version
npm --version
node -e "console.log(require.resolve('kiwi-schema'))"
ggplot2 3.5.2 stack for R 4.3.3. Install the R curl package against the current Conda libcurl and OpenSSL environment. Do not create compatibility symlinks between incompatible OpenSSL or C++ runtime versions.
Environment variable
LD_LIBRARY_PATH=/opt/conda/lib:/usr/local/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu
After rebuilding, validate the environment before running project code:
python - <<'PY'
import anndata, h5py, numpy, pandas, scipy, pyarrow
from abc_atlas_access.abc_atlas_cache.abc_project_cache import AbcProjectCache
print("Python imports succeeded")
PY
Rscript -e 'library(arrow); library(ggplot2); library(curl); print(arrow::arrow_info()); print(curl::curl_version())'
Use /data and /results correctly
| Path | Purpose | Persistence pattern |
|---|---|---|
/code | Version-controlled scripts and Shiny application. | Commit and sync to GitHub. |
/data | ABC Atlas cache and mounted read-only Data Assets. | Cache may be deleted after capture; mounted Data Assets persist independently. |
/results | Generated outputs from each processing stage. | Capture important outputs as Data Assets. |
Use /results for generated run outputs. Before closing a workstation, capture important files as Code Ocean Data Assets and mount those assets under /data for later stages. A captured Data Asset persists independently of the workstation.
Preflight checklist
- The Git remote points to
AllenInstitute/mouse-developing-vis-app. code/runhas LF line endings and is executable.- Python can import
abc_atlas_access,anndata,scipy, andpyarrow. - R can load
arrow,shiny,ggplot2, andcurl. - Node can resolve
kiwi-schema. aws sts get-caller-identitysucceeds in the workstation used for upload.- The current app-only support files are present:
table_column_definitions.csv,abc_atlas_gene_url_function.R,abc_atlas_schema_kiwi.txt, andwww/tokens.csv.
2Convert and Format Back-end Files
Run create_dev_mouse_vis_cortex_pseudobulk.py
code/create_dev_mouse_vis_cortex_pseudobulk.py
Downloads the public Developing Mouse Visual Cortex data through AbcProjectCache, reads the raw count matrix in backed mode, and aggregates cells in chunks. The output has one row per library_label × cluster_alias, one column per gene, summed raw counts in X, sparse detection counts in layers["number_of_cells_expressing"], library/donor/taxonomy metadata in obs, and number_of_cells for each pseudobulk observation.
For every chunk, the aggregation computes both group_matrix @ count_chunk and group_matrix @ (count_chunk > 0). The second result is stored as unsigned integer counts and is the numerator for fraction expressing.
Use this run script:
#!/usr/bin/env bash
set -euo pipefail
mkdir -p /data/abc_atlas_cache
mkdir -p /results
python /code/create_dev_mouse_vis_cortex_pseudobulk.py \
--cache-dir /data/abc_atlas_cache \
--output /results/Developing_Mouse_Visual_Cortex_library_cluster_pseudobulk.h5ad
Run:
bash /code/run
Validate:
python - <<'PY'
import anndata as ad
path = "/results/Developing_Mouse_Visual_Cortex_library_cluster_pseudobulk.h5ad"
adata = ad.read_h5ad(path, backed="r")
print(adata.shape)
print(adata.obs.columns.tolist())
assert "library_label" in adata.obs.columns
assert "cluster_alias" in adata.obs.columns
assert "number_of_cells" in adata.obs.columns
assert "number_of_cells_expressing" in adata.layers
assert adata.layers["number_of_cells_expressing"].shape == adata.shape
assert adata.obs["number_of_cells"].min() > 0
adata.file.close()
print("Pseudobulk structure validation succeeded")
PY
Validate that all detection counts are between zero and the total number of contributing cells:
python - <<'PY'
import anndata as ad
import numpy as np
from scipy import sparse
path = "/results/Developing_Mouse_Visual_Cortex_library_cluster_pseudobulk.h5ad"
adata = ad.read_h5ad(path, backed="r")
detected = sparse.coo_matrix(adata.layers["number_of_cells_expressing"])
number_of_cells = adata.obs["number_of_cells"].to_numpy()
assert np.all(detected.data >= 0)
assert np.all(detected.data <= number_of_cells[detected.row])
adata.file.close()
print("Detection-count validation passed")
PY
Capture and mount the pseudobulk H5AD
- Verify the H5AD exists in the workstation results directory:
ls -lh /root/capsule/results. If the workstation exposes the output only at/results, usereadlink -f /resultsto identify the corresponding location. - From the active Cloud Workstation interface, use the available results-capture control to create a Data Asset from
/root/capsule/results. If that control is unavailable, generate a reproducible run and create the Data Asset from the result in the Capsule Timeline. - Name it Developing Mouse Visual Cortex Pseudobulk with Detection Counts and describe
X,number_of_cells_expressing, the grouping variables, and the originating script. - Open the created Data Asset and verify that the H5AD is present before stopping the workstation.
- On the Capsule page, use the data-asset management panel to attach the asset. Use mount name
mouse_vis_cortex_pseudobulk. - Resume a fresh workstation and locate the mounted file with
find /data -maxdepth 3 -type f -name "*.h5ad" -print.
find /data -type f -name "*.h5ad" -print
After the Data Asset has been validated, the temporary ABC Atlas cache can be removed:
rm -rf -- /data/abc_atlas_cache
Run create_mouse_aging_parquet.py
code/create_mouse_aging_parquet.py
Reads the mounted pseudobulk H5AD and writes the app-ready gene-level Parquet resource plus metadata. Each gene file contains sample_id, summed_counts, and unsigned-integer number_of_cells_expressing. The script validates the detection layer against number_of_cells and performs a Parquet read-back check. It does not create the statistics table; statistics are deliberately generated in the next independent step.
Run:
python /code/create_mouse_aging_parquet.py \
--input "/data/mouse_vis_cortex_pseudobulk/Developing_Mouse_Visual_Cortex_library_cluster_pseudobulk.h5ad" \
--output-dir "/results/mouse_vis_cortex_parquet"
Expected output:
/results/mouse_vis_cortex_parquet/
├── counts_by_gene/
│ ├── gene=<gene_key>/
│ │ └── data.parquet
│ └── ...
├── metadata/
│ ├── obs_metadata.parquet
│ ├── gene_map.parquet
│ └── CPM_scaling.csv
└── manifest.json
- Each gene directory currently contains one Parquet file, but the app reads the directory as a dataset so additional files are supported.
- Every new gene file must contain
sample_id,summed_counts, andnumber_of_cells_expressing. obs_metadata.parquetcontains the full pseudobulk metadata and uniquesample_id.gene_map.parquetmaps user-facing gene symbols to filesystem-safe gene keys.CPM_scaling.csvcontainslibrary_labelandCPM_scaling_factorin the same row order as every gene file and metadata table.
Run create_statistics.py
code/create_statistics.py
Reads the pseudobulk H5AD and generates the final exploratory statistics table independently of the Parquet conversion. Keeping this separate permits statistics to be revised without regenerating thousands of gene files.
Run:
python /code/create_statistics.py
The script writes:
/results/mouse_gene_summary_statistics.csv
Move or copy the generated file into the app-ready directory:
mkdir -p /results/mouse_vis_cortex_parquet/statistics
cp /results/mouse_gene_summary_statistics.csv \
/results/mouse_vis_cortex_parquet/statistics/mouse_gene_summary_statistics.csv
The current table retains expression summaries and replaces the former (>0) pseudobulk metrics with true cell-detection summaries:
gene_symbol
ln(mean_CPM+1)
age_log2fc
frac_expr
age_frac_diff
max_ROI
log2fc_ROI
max_frac_ROI
frac_diff_ROI
max_subclass
log2fc_subclass
max_frac_subclass
frac_diff_subclass
gene_type
age_log2fccompares mean CPM among the script-defined postnatal reference group with mean CPM among embryonic observations.frac_expris the overall weighted fraction expressing: summed expressing-cell counts divided by summed total-cell counts.age_frac_diffis the postnatal-minus-embryonic percentage-point difference in fraction expressing.max_frac_ROIandmax_frac_subclassidentify the ROI and subclass with the highest weighted fraction expressing.frac_diff_ROIandfrac_diff_subclasssummarize the corresponding difference in fraction expressing.gene_typesupports exact category filtering in the statistics table.
Keep the six short names synchronized between create_statistics.py and table_column_definitions.csv so the table tooltips remain accurate.
Run create_value_sets.py
code/create_value_sets.py
Creates a common value_sets.csv containing authoritative label order and color definitions. The script combines taxonomy annotation terms with dataset-level categorical value sets.
The script reads:
- cluster_annotation_term.csv:
name,cluster_annotation_term_set_name,color_hex_triplet, andterm_order. - dataset value_sets.csv:
label,field,color_hex_triplet, andorder.
Run:
python /code/create_value_sets.py
Confirm the output is located at:
/results/mouse_vis_cortex_parquet/metadata/value_sets.csv
The normalized schema must be:
label,field,color_hex_triplet,order
Validate the complete back end
find /results/mouse_vis_cortex_parquet -type f | sort | head -30
find /results/mouse_vis_cortex_parquet -type f | wc -l
du -sh /results/mouse_vis_cortex_parquet
Verify key files:
python - <<'PY'
from pathlib import Path
import pandas as pd
import pyarrow.parquet as pq
root = Path("/results/mouse_vis_cortex_parquet")
required = [
root / "metadata" / "obs_metadata.parquet",
root / "metadata" / "gene_map.parquet",
root / "metadata" / "CPM_scaling.csv",
root / "metadata" / "value_sets.csv",
root / "statistics" / "mouse_gene_summary_statistics.csv",
]
for path in required:
assert path.exists(), path
metadata = pq.read_table(required[0]).to_pandas()
scaling = pd.read_csv(required[2])
assert len(metadata) == len(scaling)
assert metadata["library_label"].astype(str).tolist() == scaling["library_label"].astype(str).tolist()
gene_map = pq.read_table(required[1]).to_pandas()
assert metadata["sample_id"].is_unique
assert gene_map["gene_symbol"].is_unique
example = next((root / "counts_by_gene").glob("gene=*/data.parquet"))
columns = pq.read_schema(example).names
assert {"sample_id", "summed_counts", "number_of_cells_expressing"}.issubset(columns)
stats = pd.read_csv(required[4])
assert {"gene_symbol", "frac_expr", "age_frac_diff", "gene_type"}.issubset(stats.columns)
print("Back-end validation succeeded")
PY
Capture the complete app-ready back end
- Confirm the complete output is under
/results/mouse_vis_cortex_parquet. - Capture that directory as a second Code Ocean Data Asset for provenance and internal reuse.
- Open the asset and verify the
counts_by_gene,metadata, andstatisticsdirectories. - Attach it to any downstream Capsule that needs to inspect or republish the app-ready files.
3Publish Back-end Files to AWS
Configure the AWS CLI
Run AWS uploads from an interactive Code Ocean terminal using an approved AWS role or temporary credential for the target bucket. Never commit credentials to GitHub or save them in scripts, results, or Data Assets.
If the AWS CLI is not present, install it through the Code Ocean environment or current workstation, then verify:
aws --version
aws sts get-caller-identity
For temporary credentials:
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
export AWS_DEFAULT_REGION="us-west-2"
aws sts get-caller-identity
Upload and verify the app-ready directory
Target prefix used by the application:
s3://sea-ad-single-cell-profiling/Multiregion_2026/previous_objects/parquet/TEST_dev_mouse/
Dry-run first:
aws s3 sync \
"/results/mouse_vis_cortex_parquet/" \
"s3://sea-ad-single-cell-profiling/Multiregion_2026/previous_objects/parquet/TEST_dev_mouse/" \
--region us-west-2 \
--dryrun
Upload:
aws s3 sync \
"/results/mouse_vis_cortex_parquet/" \
"s3://sea-ad-single-cell-profiling/Multiregion_2026/previous_objects/parquet/TEST_dev_mouse/" \
--region us-west-2 \
--progress-multiline \
--progress-frequency 5
--delete to the real upload. Use it only with --dryrun for parity diagnostics.Compare paths and sizes without changing AWS:
LOCAL="/results/mouse_vis_cortex_parquet"
S3="s3://sea-ad-single-cell-profiling/Multiregion_2026/previous_objects/parquet/TEST_dev_mouse"
aws s3 sync \
"${LOCAL}/" \
"${S3}/" \
--region us-west-2 \
--dryrun \
--delete \
--size-only
No output indicates matching paths and sizes. Compare counts and bytes:
find "${LOCAL}" -type f | wc -l
find "${LOCAL}" -type f -printf '%s\n' | awk '{t += $1} END {printf "%.0f\n", t}'
aws s3 ls "${S3}/" --recursive --summarize --region us-west-2 | tail -2
Confirm anonymous access because the deployed Shiny app does not use AWS credentials:
aws s3 ls \
"${S3}/" \
--no-sign-request \
--region us-west-2
To overwrite the complete regenerated gene collection in its existing location while retaining unrelated remote objects:
LOCAL="/results/mouse_vis_cortex_parquet/counts_by_gene"
S3="s3://sea-ad-single-cell-profiling/Multiregion_2026/previous_objects/parquet/TEST_dev_mouse/counts_by_gene"
aws s3 sync "${LOCAL}/" "${S3}/" --region us-west-2 --dryrun
aws s3 sync "${LOCAL}/" "${S3}/" --region us-west-2 --only-show-errors
Do not use --delete for this production synchronization unless removal of remote-only files is explicitly intended.
Confirm that a representative public gene file contains the detection column:
aws s3 cp "${S3}/gene=Reln/data.parquet" /tmp/Reln_data.parquet --no-sign-request --region us-west-2
python - <<'PY'
import pyarrow.parquet as pq
path = "/tmp/Reln_data.parquet"
table = pq.read_table(path)
assert "number_of_cells_expressing" in table.column_names
print(table.schema)
PY
To replace only the statistics table after recalculation:
aws s3 cp \
"/results/mouse_gene_summary_statistics.csv" \
"${S3}/statistics/mouse_gene_summary_statistics.csv" \
--region us-west-2
4Build and Test the Shiny Application
Application files
code/shiny/app.R
The complete R Shiny application. It loads lightweight metadata and statistics at startup, lazily retrieves selected gene directories from public S3, calculates CPM, caches retrieved genes, supports stacked categorical and numeric filters, and provides trajectory, heatmap, dot, violin, and two-gene correlation plots. Plot views include exploratory Spearman, Moran's I, Kruskal-Wallis, or Pearson statistics as appropriate.
code/shiny/abc_atlas_gene_url_function.R and abc_atlas_schema_kiwi.txt
The R function uses Node and kiwi-schema to replace the default Reln gene in a known working ABC Atlas state and return an otherwise equivalent gene-specific URL. app.R sources the function and rebuilds the sidebar link whenever a newly retrieved gene becomes loaded_gene().
code/shiny/table_column_definitions.csv
Provides local statistics-table header definitions using the required columns column_names and column_definitions. Keep this file beside app.R and include it in every deployment bundle.
code/shiny/www/
Static logos and browser assets. The current app requires Developing_Mouse_Visual_Cortex_logo.png and allen_institute_logo.svg. If tokens.csv exists with columns token, type, and name, the app displays an alphabetical searchable glossary; absent or invalid token files disable the glossary without stopping the app.
code/shiny/renv.lock
Records the tested R package versions used for local reconstruction and GitHub-driven Posit Connect deployment.
code/shiny/.Rprofile
Activates the project-local renv environment during development. Do not bundle this file in a Posit Connect deployment.
code/shiny/.rscignore
Excludes development-only files from the app bundle.
.Rprofile
.Rproj.user
.Rhistory
.RData
renv
renv/library
renv/staging
rsconnect
manifest.json
Do not exclude renv.lock.
How the app reads the published data
The app uses an anonymous Arrow S3 filesystem:
s3_bucket_name <- "sea-ad-single-cell-profiling"
s3_region <- "us-west-2"
s3_prefix <- paste0(
"Multiregion_2026/previous_objects/parquet/",
"TEST_dev_mouse"
)
s3_anonymous <- TRUE
At startup the app reads:
metadata/obs_metadata.parquetmetadata/gene_map.parquetmetadata/CPM_scaling.csvmetadata/value_sets.csvstatistics/mouse_gene_summary_statistics.csv
For a selected gene, it opens the entire directory:
counts_by_gene/gene=<gene_key>/
This permits one or multiple Parquet files per gene while avoiding discovery of the complete gene collection. The reader joins by sample_id, calculates CPM = summed_counts × CPM_scaling_factor, validates detection counts against number_of_cells, and calculates fraction_expressing. Retrieved genes are cached in memory.
The app derives stage at startup: donor ages beginning with E are labeled embryonic, and ages beginning with P are labeled postnatal. This app-only metadata field is then available for filtering, faceting, axes, and colors.
Configure and test gene-specific ABC Atlas links
- Place
abc_atlas_gene_url_function.Randabc_atlas_schema_kiwi.txtbesideapp.R. - Confirm
app.Rcontainssource("abc_atlas_gene_url_function.R"). - Confirm Node and
kiwi-schemaare available in every runtime environment, including Posit Connect. - Test the function directly from
code/shiny:
Rscript - <<'RS'
source("abc_atlas_gene_url_function.R")
url <- create_abc_atlas_gene_url(desired_gene = "Sst")
stopifnot(grepl("^https://knowledge.brain-map.org/abcatlas#", url))
cat(url, "
")
RS
In the app, Plot in ABC Atlas is rendered only after gene data are available. The URL is regenerated from the current loaded_gene() after each successful Get gene data action.
Preserve compact URL bookmarks
The app uses URL bookmarking but excludes ordinary Shiny and DataTable inputs dynamically. Only the compact custom viewer_state is saved. This prevents oversized URLs such as those caused by gene_statistics_table_rows_all.
- Keep
observe({ setBookmarkExclude(names(input)) }, priority = 1000). - Save the primary gene, optional comparison gene, dynamic filter field-to-value mapping, metric, plot type, axes, facets, colors, limits, pagination, and display settings in
state$values$viewer_state. - During restoration, retain saved dynamic-filter values until the browser-created filter inputs exist and report the expected selections.
- Trigger plotting only after the gene data and all dynamic filters are ready.
- Capture plot settings in a
reactiveVal()before selecting the plot tab; do not rely on lazyeventReactive()evaluation.
Create and validate renv.lock
Generate the lockfile from a clean, known-working local R environment. Use R 4.3.3 and public repositories. Include the application packages plus rsconnect
setwd("code/shiny")
options(
repos = c(
CRAN = "https://cloud.r-project.org"
)
)
Sys.setenv(
RENV_CONFIG_REPOS_OVERRIDE =
"https://cloud.r-project.org"
)
packages_to_record <- c(
"shiny",
"bslib",
"arrow",
"dplyr",
"ggplot2",
"scales",
"shinycssloaders",
"cachem",
"DT",
"jsonlite",
"rsconnect"
)
renv::snapshot(
project = ".",
library = .libPaths(),
packages = packages_to_record,
repos = c(CRAN = "https://cloud.r-project.org"),
prompt = FALSE,
force = TRUE
)
renv::status()
Verify that direct package versions in the lockfile equal the working library and that the lockfile does not contain codeocean.internal. The validated plotting stack uses ggplot2 3.5.2. Node.js and kiwi-schema are not recorded in renv.lock; record them separately in the Code Ocean environment and ensure the Posit Connect runtime also provides them.
Test locally
setwd("code/shiny")
renv::restore()
shiny::runApp(".")
Test:
- Startup completes without AWS credentials.
- The gene-statistics table loads and sorts numeric fields numerically.
- Selecting a statistics row updates the gene selector.
- At least two genes can be retrieved.
- CPM equals
summed_counts × CPM_scaling_factor. - Fraction expressing equals
number_of_cells_expressing / number_of_cellsfor individual pseudobulks. - Dot area represents the weighted fraction expressing and missing combinations remain absent.
- Trajectory, heatmap, dot, violin, and two-gene correlation plots render.
- Stacked categorical and numeric filters work together, and categorical statistics-table filters update displayed rows.
- Manual expression limits, heatmap observation counts, and shared two-gene axis limits work as expected.
- Trajectory Spearman, matrix Moran's I, violin Kruskal-Wallis, expression-mode Pearson, and fraction-mode Spearman statistics render for eligible plots.
- Dot-plot notes report both expression and fraction-expressing Moran's I.
- Trajectory and violin pagination, facet height, facets per row, and vertical scrolling work without a regeneration loop.
- Resizing replays the current plot because
execOnResize = FALSE. - A bookmarked view restores genes, dynamic filters, values, plot settings, and the plot.
- The ABC Atlas link updates after every successful gene retrieval and opens the current gene.
- The glossary appears only when a valid
www/tokens.csvis present. - Age order, taxonomy order, colors, facets, and legends follow
value_sets.csv. - Browser resizing does not generate invalid plot dimensions or scrollbars.
- Both logos and all external links work.
Test in a Code Ocean RStudio workstation
Launch a fresh RStudio workstation after every environment rebuild. Test compiled package linkage before opening the application:
ldd /opt/conda/lib/R/library/arrow/libs/arrow.so | grep libstdc++
ldd /opt/conda/lib/R/library/curl/libs/curl.so | grep -E "ssl|crypto|curl"
Rscript -e 'library(arrow); library(curl); library(ggplot2); print(packageVersion("ggplot2"))'
Then run:
cd /root/capsule/code
Rscript -e 'shiny::runApp("shiny", host="0.0.0.0")'
5Deploy to Posit Connect
Perform the initial deployment
- Create a Posit Connect API key with publishing permission.
- Register
https://molgen-shiny.orgwithrsconnect::addServer(). - Authenticate with
rsconnect::connectApiUser(). - Deploy to a temporary test content item using strict lockfile resolution.
- After the test succeeds, deploy to the production content item.
setwd("code/shiny")
options(
repos = c(
CRAN = "https://cloud.r-project.org"
)
)
Sys.setenv(
RENV_CONFIG_REPOS_OVERRIDE =
"https://cloud.r-project.org"
)
app_files <- c(
"app.R",
"abc_atlas_gene_url_function.R",
"abc_atlas_schema_kiwi.txt",
"table_column_definitions.csv",
"renv.lock",
list.files(
"www",
recursive = TRUE,
full.names = TRUE
)
)
rsconnect::deployApp(
appDir = ".",
appFiles = app_files,
appPrimaryDoc = "app.R",
appId = "3906a801-fab5-41e4-9c78-d7ce47cc0065",
account = "<CONNECT_USERNAME>",
server = "molgen-shiny",
dependencyResolution = "strict",
packageRepositoryResolutionR = "lax",
forceUpdate = TRUE,
launch.browser = FALSE,
lint = TRUE,
logLevel = "verbose"
)
Production application:
https://molgen-shiny.org/dev_mouse_gene_exploration/
Validate the Posit Connect deployment
- Connect installs the exact package versions derived from
renv.lock. - Repository URLs in the build log are reachable from Connect and do not reference Code Ocean's private package proxy.
- The process starts without Arrow, OpenSSL, curl, C++ ABI, or S7/ggplot compatibility errors.
- The public S3 metadata loads at startup.
- All app interactions pass the same checks used locally.
- The content URL and vanity path are correct.
6Configure GitHub Auto-deployment
Add the Posit Connect API key to GitHub
In the repository, open Settings → Secrets and variables → Actions and create:
CONNECT_API_KEY
Use a dedicated Posit Connect API key whose account has permission to update content GUID 3906a801-fab5-41e4-9c78-d7ce47cc0065. Never commit the key.
Create .github/workflows/deploy-connect.yml
.github/workflows/deploy-connect.yml
Runs after changes under code/shiny/, restores the exact R environment from renv.lock, generates a fresh strict deployment manifest, verifies that no private Code Ocean repository URL is present, and updates the existing Connect content.
name: Deploy Mouse Aging App to Posit Connect
on:
push:
branches:
- main
paths:
- "code/shiny/**"
- ".github/workflows/deploy-connect.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: deploy-mouse-developing-vis-app
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-22.04
env:
RENV_PATHS_ROOT: ~/.cache/R/renv
RENV_CONFIG_REPOS_OVERRIDE: https://cloud.r-project.org
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
r-version: "4.3.3"
use-public-rspm: true
- name: Restore renv environment
uses: r-lib/actions/setup-renv@v2
with:
working-directory: code/shiny
cache-version: 2
- name: Verify restored deployment environment
shell: Rscript {0}
run: |
renv::load("code/shiny")
stopifnot(requireNamespace("rsconnect", quietly = TRUE))
cat("rsconnect:", as.character(packageVersion("rsconnect")), "\n")
cat(paste(.libPaths(), collapse = "\n"), "\n")
- name: Generate deployment manifest
shell: Rscript {0}
run: |
renv::load("code/shiny")
setwd("code/shiny")
options(
repos = c(
CRAN = "https://cloud.r-project.org"
)
)
Sys.setenv(
RENV_CONFIG_REPOS_OVERRIDE =
"https://cloud.r-project.org"
)
app_files <- c(
"app.R",
"abc_atlas_gene_url_function.R",
"abc_atlas_schema_kiwi.txt",
"table_column_definitions.csv",
"renv.lock",
list.files(
"www",
recursive = TRUE,
full.names = TRUE
)
)
rsconnect::writeManifest(
appDir = ".",
appFiles = app_files,
appPrimaryDoc = "app.R",
dependencyResolution = "strict",
packageRepositoryResolutionR = "lax",
verbose = TRUE
)
manifest_text <- paste(
readLines("manifest.json", warn = FALSE),
collapse = "\n"
)
stopifnot(
!grepl(
"codeocean.internal",
manifest_text,
fixed = TRUE
)
)
- name: Verify deployment files
shell: bash
run: |
test -f code/shiny/app.R
test -f code/shiny/abc_atlas_gene_url_function.R
test -f code/shiny/abc_atlas_schema_kiwi.txt
test -f code/shiny/table_column_definitions.csv
test -f code/shiny/renv.lock
test -f code/shiny/manifest.json
test -f code/shiny/www/allen_institute_logo.svg
test -f code/shiny/www/Developing_Mouse_Visual_Cortex_logo.png
test -f code/shiny/www/tokens.csv
- name: Deploy to Posit Connect
uses: posit-dev/connect-actions/deploy@main
with:
connect-server: https://molgen-shiny.org
connect-api-key: ${{ secrets.CONNECT_API_KEY }}
content-guid: 3906a801-fab5-41e4-9c78-d7ce47cc0065
path: code/shiny
draft: false
After committing the workflow, monitor the first run under the repository's Actions tab. The workflow can also be run manually because it includes workflow_dispatch.
Update R dependencies safely
- Make package changes in a clean local project environment.
- Run the application and complete functional tests.
- Run
renv::snapshot()with the explicit package list, includingrsconnect. - Run
renv::status()and verify critical installed and locked versions match. - Commit
renv.lockwith the app change. - Push to a branch and, if desired, test against a temporary Connect content item before merging to
main.
rsconnect::writeManifest() from the restored project library, so rsconnect and its recursive dependencies must be present in renv.lock even though the Shiny runtime does not call them.7Publish Documentation with GitHub Pages
Publish index.html and this guide
- Place
index.htmlanddeployment_documentation.htmlat the repository root. - In GitHub, open Settings → Pages.
- Select
mainand/(root)as the publishing source. - Commit and push both HTML files.
- Add a link from
index.htmlto this guide if desired.
Because the repository is private, GitHub may assign a unique private Pages subdomain. Routine deployments update the content at that same private Pages address.
Maintenance Checklist
When only app.R or CSS changes
- Test locally.
- Commit changes under
code/shiny/. - Push or merge to
main. - Confirm the GitHub Action succeeds.
- Validate the live app.
When an R package changes
- Update the local project library.
- Test all application functionality.
- Run the explicit
renv::snapshot(). - Commit both code and
renv.lock. - Review the Connect build log after deployment.
Commit and deploy future application updates
After editing and testing the application locally, review the changed files, commit them, and push the branch. Changes under code/shiny/ trigger the Posit Connect deployment workflow after they reach main; root documentation updates are published through the configured GitHub Pages source.
git status
git diff
git add code/shiny/app.R \
code/shiny/table_column_definitions.csv \
code/shiny/renv.lock \
index.html \
deployment_documentation.html
git commit -m "Update mouse gene expression viewer"
git push origin <branch-name>
If only some files changed, include only those files in git add. Do not commit manifest.json, local renv libraries, generated scientific data, credentials, or ignored runtime artifacts.
After pushing, merge the tested branch to main using the repository's normal review process. Confirm the GitHub Actions deployment succeeds and validate the live app. For a direct update from main:
git checkout main
git pull --ff-only origin main
git status
git add <changed-files>
git commit -m "Describe the application update"
git push origin main
table_column_definitions.csv accompanies app.R, and update renv.lock only when the tested package environment changed.When source data or preprocessing changes
- Regenerate the pseudobulk H5AD if the aggregation definition changed.
- Capture the revised H5AD as a versioned Data Asset.
- Regenerate gene Parquet and metadata.
- Regenerate statistics independently.
- Regenerate
value_sets.csvif taxonomy, anatomical labels, order, or colors changed. - Validate all files and capture the complete output as a Data Asset.
- Upload to a new AWS test prefix.
- Test the app against the test prefix.
- Promote the tested prefix or update
s3_prefixinapp.R. - Push the app change to trigger deployment.
Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
object 'counts_dataset' not found | The global dataset was removed for faster startup but read_gene() still references it. | Open the selected counts_by_gene/gene=<gene_key>/ directory inside read_gene(); do not open the full collection at startup. |
| First gene is as slow as startup used to be | A delayed global dataset still discovers all gene partitions. | Remove the delayed global dataset and open only the selected gene directory. |
CXXABI_1.3.15 not found | Arrow loads an older system libstdc++. | Install current Conda C++ runtimes and place /opt/conda/lib first in LD_LIBRARY_PATH. |
libssl.so.1.1 not found for R curl | An old R curl binary was compiled against OpenSSL 1.1. | Install current libcurl/openssl and build the R curl package against that environment. |
Incompatible methods ("Ops.S7_object", "+.gg") | Incompatible ggplot2/S7 package stack. | Use the validated ggplot2 3.5.2 environment. |
Library and lockfile are out of sync | The active R library differs from renv.lock. | Rebuild and test a clean library, then snapshot the tested package versions. Use strict resolution only after synchronization. |
there is no package called 'rsconnect' in GitHub Actions | The project library was restored but not activated in the standalone Rscript step, or rsconnect was omitted from the lockfile. | Record rsconnect in renv.lock and call renv::load("code/shiny") before using it. |
Connect cannot resolve codeocean.internal | The manifest captured Code Ocean's private package proxy. | Set public repositories before manifest generation and fail the workflow if the private URL appears. |
invalid 'width' or 'height' in plotting | Plot output or parent containers report a transient invalid dimension. | Use one explicit responsive sizing model, allow containers to shrink with min-height: 0, and avoid competing height calculations. |
Windows shell script reports invalid pipefail | CRLF line endings. | Convert code/run to LF and enforce line endings with .gitattributes. |
414 Request-URI Too Large | Automatic bookmarking serialized DataTable and dynamic-input state, especially all table row indices. | Dynamically exclude all ordinary inputs and store only the compact custom viewer_state. |
Operation not allowed without an active reactive context during restoration | A reactive value was read outside a reactive consumer. | Wrap the read in isolate(), and keep dynamic filter restoration state until inputs confirm their values. |
| The first plot requires two clicks | Lazy eventReactive() evaluation races with plot-tab activation. | Capture settings into a reactiveVal() first, then select the plot tab in session$onFlushed(). |
| Plot continuously regenerates after resizing | execOnResize = TRUE reruns the complete expression while reactive height and scrollbar geometry change. | Use execOnResize = FALSE; regenerate only after Generate plot or bookmark restoration. |
| LOESS warnings about singularities or failed groups | Some color/facet groups have too few distinct progression values for stable LOESS fitting. | These warnings concern smoothing groups; use a linear fit or no fit for sparse configurations, and first ensure resize-triggered redraw loops are disabled. |
| Facet page errors such as missing TRUE/FALSE or argument length zero | The dynamically created page input is briefly NULL. | Default missing page size to 12 and missing or invalid page number to 1 before any if condition. |
| The ABC Atlas button keeps the first gene | The URL was calculated only for the first click or stored in a nonreactive browser handler. | Regenerate a reactive URL whenever loaded_gene() changes and render the link from that value. |
kiwi-schema cannot be found | Node cannot resolve the globally installed npm module. | Install kiwi-schema with npm and set NODE_PATH=/opt/conda/lib/node_modules. |
References
- Developing Mouse Visual Cortex dataset description
- Developing Mouse Visual Cortex taxonomy
- ABC Atlas Access repository
- Continuous cell-type diversification in mouse visual cortex development
- Code Ocean Git provider integration
- Using renv for reproducible Posit Connect deployments
- Publishing to Posit Connect from GitHub Actions
- Posit Connect GitHub Actions
- mouse-developing-vis-app repository
renv.lock, a working Posit Connect content item, and a GitHub Action that updates that content whenever code/shiny/ changes.