Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d2b0ab5
Add long-format parse adapters + declare openms-insight dependency
claude Jun 1, 2026
79aebad
docs: add OpenMS-Insight migration status + parity contract
claude Jun 1, 2026
cd22c2e
Add real-data integration tests against bundled example workspace
claude Jun 1, 2026
83b18a2
Phase 1: FLASHDeconv OpenMS-Insight viewer engine (flagged)
claude Jun 1, 2026
d8513db
Add SessionStart hook for reproducible web-session setup
claude Jun 1, 2026
50b8716
Phase 2: FLASHTnT OpenMS-Insight viewer engine (flagged)
claude Jun 1, 2026
e6a5a2a
Phase 3: FLASHQuant OpenMS-Insight viewer engine (flagged)
claude Jun 1, 2026
6ca3ef2
docs: no-feature-loss audit + browser checklist (deletion gate)
claude Jun 1, 2026
0351269
Default the OpenMS-Insight viewer engine on; install it (with Vue bun…
claude Jun 2, 2026
53a1e2b
CI: build OpenMS-Insight Vue bundle in unit-tests; address review nits
claude Jun 2, 2026
f94e5ed
Fix FLASHTnT Sequence View 'Component unavailable' (parquet sequence_…
claude Jun 2, 2026
4c09d40
FLASHTnT Sequence View: feed mass header + precomputed fragments + peaks
claude Jun 2, 2026
51b1534
FLASHDeconv parity: heatmap click cross-links, 3D x=mz*charge, raw y-…
claude Jun 2, 2026
4c73503
FLASHTnT: wire the augmented-spectrum tagger overlay
claude Jun 2, 2026
fd283f1
Parity polish: Deconv/TnT table column-defs, charge labels, best-per-…
claude Jun 2, 2026
83574ea
Parity: TnT residue→tag cross-link, Deconv mass highlight, Quant per-…
claude Jun 2, 2026
90bac88
FLASHQuant parity: legacy column titles, drop extra column, dataset-s…
claude Jun 2, 2026
48106f6
Parity round 2: Deconv/TnT fixes from the fan-out re-audit
claude Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/bash
# SessionStart hook: install FLASHApp deps + the OpenMS-Insight sibling repo so
# tests and the migration work in Claude Code on the web.
#
# Layout assumed (Claude Code on the web clones siblings under the same parent):
# <parent>/FLASHApp (this repo, $CLAUDE_PROJECT_DIR)
# <parent>/OpenMS-Insight (visualization library dependency)
#
# The OpenMS-Insight wheel force-includes its built js-component/dist, so the
# Vue bundle must be built BEFORE the editable install. We handle that ordering
# and degrade gracefully if the sibling repo isn't present.
set -euo pipefail

# Only run in the remote (web) environment; local setups are managed by the user.
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
PARENT_DIR="$(dirname "$PROJECT_DIR")"
OI_DIR="$PARENT_DIR/OpenMS-Insight"

echo "[session-start] FLASHApp setup starting"

# --- 1. Python dependencies for FLASHApp -----------------------------------
# requirements.txt pins the app deps (streamlit, polars, pyopenms, scipy, ...).
# It also lists `openms-insight @ git+...`, but building that from the git URL
# fails (the wheel force-includes a js bundle that isn't built in a fresh
# clone) AND aborts the whole install. So strip that line here and install the
# local sibling separately in step 2.
REQ_TMP="$(mktemp)"
grep -ivE '^openms-insight[[:space:]]*[@=<>!~]' "$PROJECT_DIR/requirements.txt" > "$REQ_TMP" || cp "$PROJECT_DIR/requirements.txt" "$REQ_TMP"
python3 -m pip install --user --quiet -r "$REQ_TMP" || {
echo "[session-start] WARNING: requirements.txt install hit an error; continuing"
}
rm -f "$REQ_TMP"

# pytest for the test suite (not in the app requirements).
python3 -m pip install --user --quiet pytest pytest-cov

# --- 2. OpenMS-Insight sibling (build Vue bundle, then editable install) ----
if [ -d "$OI_DIR" ]; then
echo "[session-start] Building OpenMS-Insight Vue bundle"
# npm install is cache-friendly and idempotent (prefer over npm ci so the
# cached container state is reused across sessions).
( cd "$OI_DIR/js-component" && npm install --no-audit --no-fund --silent && npm run build ) || {
echo "[session-start] WARNING: Vue bundle build failed; OI install may be degraded"
}

# The wheel force-includes openms_insight/js-component/dist. Vite builds to
# js-component/dist (repo root), so mirror it into the package path the build
# backend expects, making the editable install succeed.
if [ -d "$OI_DIR/js-component/dist" ]; then
mkdir -p "$OI_DIR/openms_insight/js-component"
rm -rf "$OI_DIR/openms_insight/js-component/dist"
cp -r "$OI_DIR/js-component/dist" "$OI_DIR/openms_insight/js-component/dist"
fi

echo "[session-start] Installing OpenMS-Insight (editable)"
python3 -m pip install --user --quiet -e "$OI_DIR" || {
echo "[session-start] WARNING: editable OI install failed; falling back to PYTHONPATH"
# Fallback: make it importable directly from source.
echo "export PYTHONPATH=\"$OI_DIR:\${PYTHONPATH:-}\"" >> "${CLAUDE_ENV_FILE:-/dev/null}"
}
else
echo "[session-start] OpenMS-Insight not found at $OI_DIR (skipping; clone it as a sibling for the migration)"
fi

# --- 3. Persist environment for the session --------------------------------
# FLASHApp imports as top-level `src.*`; ensure the repo root is importable.
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
echo "export PYTHONPATH=\"$PROJECT_DIR:\${PYTHONPATH:-}\"" >> "$CLAUDE_ENV_FILE"
# User-site installs land here; make sure they're on PATH for pytest etc.
echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> "$CLAUDE_ENV_FILE"
fi

echo "[session-start] FLASHApp setup complete"
14 changes: 14 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
}
]
}
]
}
}
27 changes: 23 additions & 4 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,32 @@ jobs:
cache: pip
cache-dependency-path: requirements.txt

- name: Set up Node
# Needed to build the OpenMS-Insight Vue bundle (see install step).
uses: actions/setup-node@v4
with:
node-version: "21"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
# Pinned runtime deps (pyopenms is needed so ParameterManager imports
# cleanly at collection time) plus test-only deps. fakeredis backs the
# QueueManager/WorkflowManager tests, which pytest.importorskip it.
pip install -r requirements.txt
# OpenMS-Insight is declared in requirements.txt as a git dependency,
# but its wheel force-includes a Vue bundle that is gitignored, so a
# raw `pip install git+…` fails at metadata generation. Build the
# bundle and install from the checkout, mirroring the Dockerfiles and
# .claude/hooks/session-start.sh.
git clone --depth 1 --branch claude/peaceful-mayer-YqiXZ \
https://github.com/t0mdavid-m/OpenMS-Insight.git /tmp/openms-insight
(cd /tmp/openms-insight/js-component && npm install --no-audit --no-fund && npm run build)
mkdir -p /tmp/openms-insight/openms_insight/js-component
cp -r /tmp/openms-insight/js-component/dist \
/tmp/openms-insight/openms_insight/js-component/dist
pip install /tmp/openms-insight
# Remaining deps minus the openms-insight git line (installed above);
# pyopenms is needed so ParameterManager imports cleanly at collection
# time. fakeredis backs the QueueManager/WorkflowManager tests.
grep -ivE '^openms-insight[[:space:]]*[@=<>!~]' requirements.txt > /tmp/req.txt
pip install -r /tmp/req.txt
pip install pytest fakeredis

- name: Run unit tests
Expand Down
33 changes: 32 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ RUN pip install dist/*.whl

# Install other dependencies (excluding pyopenms)
COPY requirements.txt ./requirements.txt
RUN grep -Ev '^pyopenms([=<>!~].*)?$' requirements.txt > requirements_cleaned.txt && mv requirements_cleaned.txt requirements.txt
RUN grep -Ev '^pyopenms([=<>!~].*)?$' requirements.txt \
| grep -Ev '^openms-insight[[:space:]]*[@=<>!~]' > requirements_cleaned.txt \
&& mv requirements_cleaned.txt requirements.txt
RUN pip install -r requirements.txt

WORKDIR /
Expand Down Expand Up @@ -144,6 +146,28 @@ WORKDIR /openms-streamlit-vue-component
RUN npm install
RUN npm run build

# Build the OpenMS-Insight Vue bundle and stage a pip-installable checkout.
# OpenMS-Insight's wheel (hatchling) force-includes openms_insight/js-component/dist
# only if it exists on disk, but that bundle is gitignored — so installing the
# package straight from its git URL ships no frontend. Build the bundle here with
# node and hand the populated checkout to the python stage. Pinned to the
# validated commit for reproducible images (override OPENMS_INSIGHT_REF to bump).
FROM node:21 AS openms-insight-build
ARG OPENMS_INSIGHT_REPO=https://github.com/t0mdavid-m/OpenMS-Insight.git
ARG OPENMS_INSIGHT_REF=2767f1d1dea651c47ac104a05dd8efa4e30fe961
# Bust the clone cache when REF tracks a moving branch (no-op for a pinned SHA).
ADD https://api.github.com/repos/t0mdavid-m/OpenMS-Insight/commits/$OPENMS_INSIGHT_REF oi-version.json
RUN git clone ${OPENMS_INSIGHT_REPO} /OpenMS-Insight \
&& git -C /OpenMS-Insight checkout ${OPENMS_INSIGHT_REF}
WORKDIR /OpenMS-Insight/js-component
RUN npm install --no-audit --no-fund && npm run build
# Vite emits to js-component/dist; mirror it into the package path the wheel
# force-includes and rendering/bridge.py loads at runtime.
RUN mkdir -p /OpenMS-Insight/openms_insight/js-component \
&& cp -r dist /OpenMS-Insight/openms_insight/js-component/dist
# Slim the checkout to what the wheel build needs (drop node_modules, .git, tests).
RUN rm -rf /OpenMS-Insight/js-component /OpenMS-Insight/.git /OpenMS-Insight/tests

# Prepare and run streamlit app.
FROM compile-openms AS run-app

Expand Down Expand Up @@ -190,6 +214,13 @@ COPY presets.json /app/presets.json
# Copy the pre-built Vue/JS component (built in the js-build stage above).
COPY --from=js-build openms-streamlit-vue-component/dist /app/js-component/dist

# Install OpenMS-Insight — the reusable visualization components that back the
# default viewer engine (FLASHAPP_USE_OPENMS_INSIGHT, on by default). Installed
# from the bundle-built checkout staged in the openms-insight-build stage, because
# the package's git tree omits the (gitignored) pre-built Vue bundle.
COPY --from=openms-insight-build /OpenMS-Insight /tmp/openms-insight
RUN mamba run -n streamlit-env pip install /tmp/openms-insight && rm -rf /tmp/openms-insight

# add cron job to the crontab
RUN echo "0 3 * * * /root/miniforge3/envs/streamlit-env/bin/python /app/clean-up-workspaces.py >> /app/clean-up-workspaces.log 2>&1" | crontab -

Expand Down
33 changes: 32 additions & 1 deletion Dockerfile.arm
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ RUN pip install dist/*.whl

# Install other dependencies (excluding pyopenms)
COPY requirements.txt ./requirements.txt
RUN grep -Ev '^pyopenms([=<>!~].*)?$' requirements.txt > requirements_cleaned.txt && mv requirements_cleaned.txt requirements.txt
RUN grep -Ev '^pyopenms([=<>!~].*)?$' requirements.txt \
| grep -Ev '^openms-insight[[:space:]]*[@=<>!~]' > requirements_cleaned.txt \
&& mv requirements_cleaned.txt requirements.txt
RUN pip install -r requirements.txt

WORKDIR /
Expand Down Expand Up @@ -139,6 +141,28 @@ WORKDIR /openms-streamlit-vue-component
RUN npm install
RUN npm run build

# Build the OpenMS-Insight Vue bundle and stage a pip-installable checkout.
# OpenMS-Insight's wheel (hatchling) force-includes openms_insight/js-component/dist
# only if it exists on disk, but that bundle is gitignored — so installing the
# package straight from its git URL ships no frontend. Build the bundle here with
# node and hand the populated checkout to the python stage. Pinned to the
# validated commit for reproducible images (override OPENMS_INSIGHT_REF to bump).
FROM node:21 AS openms-insight-build
ARG OPENMS_INSIGHT_REPO=https://github.com/t0mdavid-m/OpenMS-Insight.git
ARG OPENMS_INSIGHT_REF=2767f1d1dea651c47ac104a05dd8efa4e30fe961
# Bust the clone cache when REF tracks a moving branch (no-op for a pinned SHA).
ADD https://api.github.com/repos/t0mdavid-m/OpenMS-Insight/commits/$OPENMS_INSIGHT_REF oi-version.json
RUN git clone ${OPENMS_INSIGHT_REPO} /OpenMS-Insight \
&& git -C /OpenMS-Insight checkout ${OPENMS_INSIGHT_REF}
WORKDIR /OpenMS-Insight/js-component
RUN npm install --no-audit --no-fund && npm run build
# Vite emits to js-component/dist; mirror it into the package path the wheel
# force-includes and rendering/bridge.py loads at runtime.
RUN mkdir -p /OpenMS-Insight/openms_insight/js-component \
&& cp -r dist /OpenMS-Insight/openms_insight/js-component/dist
# Slim the checkout to what the wheel build needs (drop node_modules, .git, tests).
RUN rm -rf /OpenMS-Insight/js-component /OpenMS-Insight/.git /OpenMS-Insight/tests

# Prepare and run streamlit app.
FROM compile-openms AS run-app

Expand Down Expand Up @@ -171,6 +195,13 @@ COPY presets.json /app/presets.json
# Copy the pre-built Vue/JS component (built in the js-build stage above).
COPY --from=js-build openms-streamlit-vue-component/dist /app/js-component/dist

# Install OpenMS-Insight — the reusable visualization components that back the
# default viewer engine (FLASHAPP_USE_OPENMS_INSIGHT, on by default). Installed
# from the bundle-built checkout staged in the openms-insight-build stage, because
# the package's git tree omits the (gitignored) pre-built Vue bundle.
COPY --from=openms-insight-build /OpenMS-Insight /tmp/openms-insight
RUN mamba run -n streamlit-env pip install /tmp/openms-insight && rm -rf /tmp/openms-insight

# add cron job to the crontab
RUN echo "0 3 * * * /root/miniforge3/envs/streamlit-env/bin/python /app/clean-up-workspaces.py >> /app/clean-up-workspaces.log 2>&1" | crontab -

Expand Down
36 changes: 31 additions & 5 deletions content/FLASHDeconv/FLASHDeconvViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,39 @@

from pathlib import Path

from src.common.common import page_setup, save_params
from src.common.common import page_setup, save_params, use_openms_insight
from src.workflow.FileManager import FileManager
from src.render.render import render_grid

# Migration flag: render each experiment panel with the OpenMS-Insight engine
# (src.render_oi) instead of the legacy flash_viewer_grid. Default ON; opt out
# with FLASHAPP_USE_OPENMS_INSIGHT=0 (see src.common.common.use_openms_insight).
USE_OPENMS_INSIGHT = use_openms_insight()

DEFAULT_LAYOUT = [['ms1_deconv_heat_map'], ['scan_table', 'mass_table'],
['anno_spectrum', 'deconv_spectrum'], ['3D_SN_plot']]


def render_panel(dataset_id, layout_rows, file_manager, tool, exp_key, grid_key=None):
"""Render one experiment panel with the selected engine.

Legacy path delegates to render_grid (flash_viewer_grid). OpenMS-Insight path
delegates to src.render_oi.render_experiment with a per-panel StateManager.
"""
if USE_OPENMS_INSIGHT:
from src.render_oi import render_experiment

has_sequence = get_sequence() is not None
render_experiment(
dataset_id, layout_rows, file_manager,
panel_key=(grid_key or exp_key), has_sequence=has_sequence,
)
else:
if grid_key is not None:
render_grid(dataset_id, layout_rows, file_manager, tool, exp_key, grid_key)
else:
render_grid(dataset_id, layout_rows, file_manager, tool, exp_key)

def select_experiment():
# Map display name back to experiment ID
st.session_state.selected_experiment0 = display_name_to_id[st.session_state.selected_experiment_dropdown]
Expand Down Expand Up @@ -84,7 +110,7 @@ def get_sequence():
on_change=select_experiment
)
if 'selected_experiment0' in st.session_state:
render_grid(
render_panel(
st.session_state.selected_experiment0, layout[0], file_manager,
'flashdeconv', "selected_experiment0", 'flash_viewer_grid_0'
)
Expand All @@ -97,7 +123,7 @@ def get_sequence():
)
if f"selected_experiment1" in st.session_state:
with st.spinner('Loading component...'):
render_grid(
render_panel(
st.session_state["selected_experiment1"], layout[1],
file_manager, 'flashdeconv', 'selected_experiment1',
'flash_viewer_grid_1'
Expand All @@ -114,7 +140,7 @@ def get_sequence():


if 'selected_experiment0' in st.session_state:
render_grid(
render_panel(
st.session_state.selected_experiment0, layout[0], file_manager,
'flashdeconv', 'selected_experiment0'
)
Expand All @@ -135,7 +161,7 @@ def get_sequence():
)
# if #experiment input files are less than #layouts, all the pre-selection will be the first experiment
if f"selected_experiment{exp_index}" in st.session_state:
render_grid(
render_panel(
st.session_state["selected_experiment%d" % exp_index],
layout[exp_index], file_manager, 'flashdeconv',
"selected_experiment%d" % exp_index,
Expand Down
23 changes: 18 additions & 5 deletions content/FLASHQuant/FLASHQuantViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
from pathlib import Path

from src.workflow.FileManager import FileManager
from src.common.common import page_setup, save_params
from src.common.common import page_setup, save_params, use_openms_insight
# from src.render.components import flash_viewer_grid_component, FlashViewerComponent, FLASHQuant
from src.render.render import render_grid

# Migration flag (shared across workflows): render with the OpenMS-Insight engine.
# Default ON; opt out with FLASHAPP_USE_OPENMS_INSIGHT=0 (see
# common.use_openms_insight).
USE_OPENMS_INSIGHT = use_openms_insight()

# page initialization
params = page_setup()

Expand All @@ -33,10 +38,18 @@
st.selectbox("choose experiment", results, key="selected_experiment0_quant")
selected_exp0 = st.session_state.selected_experiment0_quant

render_grid(
st.session_state.selected_experiment0_quant, [['quant_visualization']],
file_manager, 'flashquant', 'selected_experiment0_quant'
)
if USE_OPENMS_INSIGHT:
from src.render_oi import render_experiment_quant

render_experiment_quant(
st.session_state.selected_experiment0_quant, file_manager,
panel_key='selected_experiment0_quant',
)
else:
render_grid(
st.session_state.selected_experiment0_quant, [['quant_visualization']],
file_manager, 'flashquant', 'selected_experiment0_quant'
)

# # Get data
# quant_df = file_manager.get_results(selected_exp0, 'quant_dfs')['quant_dfs']
Expand Down
Loading
Loading