v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

v0.3.0 - #43

Merged
RaredonLab merged 9 commits into
mainfrom
dev
Jun 1, 2026
Merged

v0.3.0#43
RaredonLab merged 9 commits into
mainfrom
dev

Conversation

@RaredonLab

Copy link
Copy Markdown
Owner

No description provided.

msraredonand others added 9 commits May 29, 2026 11:53
Addresses issue #32 and related UX feedback: the app now only computes
and renders the final state after a user stops interacting, stale
in-flight responses are cancelled rather than overwriting current data,
and a 'Computing...' badge gives real-time feedback during any fetch.
AbortController added to all five data-fetching hooks (useEdges,
useTranscripts, useCellBoundaries, useCellColors, useEdgeColors): when a
new debounce fires the previous in-flight request is cancelled, so
intermediate pan positions or partially-dialled settings never flash onto
the canvas. Debounce for settings-driven hooks (useCellColors,
useEdgeColors metadata) bumped from 150 ms to 400 ms so rapid sequential
gene/LRM/colorBy changes collapse to a single server request.
useEdges is split into two independent effects:
- Structural effect (viewport/density deps only): POSTs to /query-grouped
for positions, metadata, lrm_count, and score_sum. Never re-runs on
LRM filter changes.
- Score effect (viewport + hiddenLrms deps): POSTs to new /query-scores
endpoint for {edge, visible_lrm_count, visible_score_sum} only. Picks
the cheaper query direction automatically — WHERE lrm IN (visible_set)
when the visible set is small (e.g. user selects 10/559 LRMs → ~98%
less parquet scanned), CASE WHEN NOT IN otherwise. Short-circuits
entirely when no LRM filter is active.
New RenderingStatus component shows a spinner badge in the viewer corner
after a 400 ms onset delay whenever any layer for that panel is fetching.
Disappears immediately on completion. Works per-panel in split-screen.
Loading state tracked via loadingKeys Set in Zustand store.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
perf: debounce final-state rendering and decouple LRM score fetches
Two related bugs fixed in useCellColors and useEdgeColors:
1. Clamp/palette changes (slider drag, palette swap, reset range) were
triggering a full 400 ms debounce + server re-fetch before the canvas
updated, because clamp?.low, clamp?.high, and palette were deps of the
fetch effect. Slider position and canvas were therefore out of sync
until the fetch returned, producing the "drag then image jumps" effect.
Fix: split each hook into a fetch effect (data params only — no clamp
or palette deps) and a separate remap effect that applies clamp +
palette synchronously to cached raw values. Canvas now updates in the
same render cycle as the slider.
2. After "reset range" in lrm_set edge color mode the canvas showed a
false bright-yellow render (most edges saturated) while the legend
correctly showed vmax. Root cause: lrmSetResult useMemo used
clamp?.high ?? p95 as the upper bound, but ClampableLegend used
clamp?.high ?? vmax. When clamp.high was null after reset these
diverged, so the rendering silently clamped at p95 while the legend
displayed vmax.
Fix: lrmSetResult now uses clamp?.high ?? max (matching the legend).
The Viewer.jsx auto-calibration effect that sets clamp.high = edgeP95
on each data load still fires normally and provides the p95-calibrated
initial view.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rotation (resolves#31):
- ⟲ / ⟳ toolbar buttons rotate each panel ±90°; number input accepts any
arbitrary angle 0–359°. A green ×-reset appears when non-zero.
- OSD viewport.setRotation() keeps tile display and pan/zoom correct.
- All 14 deck.gl layers receive a modelMatrix (CW rotation around the
current viewport pivot), recomputed on every OSD viewport-change event.
Deck.gl applies the same matrix during its picking pass, so
click-to-select requires no manual coordinate transforms.
- syncDeckFromOSD expands the data-fetch bbox to cover the full rotated
rectangle's footprint (rotatedBbox helper), ensuring corner data is
always fetched at arbitrary angles.
- viewportActual added to store (un-expanded OSD bounds) so the ⇔ Match
zoom feature continues to use the true visible width rather than the
padded fetch bbox — fixes the Match regression introduced by rotation.
- screenToData inverse-rotates screen→image coords so annotation
polygons and measurements are stored in original image space.
- Measurement labels forward-rotate before screen projection.
- panelRotations [0,0] in store; per-panel, survives split ↔ single toggle.
Versioning:
- Bump to v0.3.0 in backend/app/main.py and frontend/package.json.
- vite.config.js injects __APP_VERSION__ from package.json at build time.
- App.jsx sets document.title = "TissuePlex v0.3.0" on mount.
- LayerPanel version badge now shows immediately (build-time constant)
rather than waiting for the /health fetch; /health is still checked and
a mismatch warning is shown if frontend/backend versions differ.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RaredonLab
RaredonLab merged commit 1627e4a into mainJun 1, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@RaredonLab@msraredon