Skip to content

Handle channel selection state for Zarr additions - #361

Merged
xinaesthete merged 4 commits into
mainfrom
codex/fix-channel-state-bugs
Mar 13, 2026
Merged

Handle channel selection state for Zarr additions#361
xinaesthete merged 4 commits into
mainfrom
codex/fix-channel-state-bugs

Conversation

@xinaesthete

@xinaesthete xinaesthete commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Guard the color channel UI so failures (e.g., when adding a Zarr channel) clear loading indicators, remove optimistic channels, and reuse a shared buildSelectionForSource helper instead of ad-hoc defaults.
  • Update the avivatorish state/hooks/utils to derive selections from loader metadata, normalize globals, and keep channel/image stats logic in sync with the new helpers.

Testing

  • Not run (not requested)

Summary by CodeRabbit

  • Bug Fixes

    • Improved channel addition error handling with automatic rollback on failures
    • Enhanced loading state management during channel operations
  • UI Improvements

    • Refined channel selection with better default handling
    • Optimized histogram rendering and formatting
    • Consolidated UI feedback during channel-related operations

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@xinaesthete has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 32 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e9a666c-fe5c-46ef-9255-ae13d076b8d6

📥 Commits

Reviewing files that changed from the base of the PR and between 320cd36 and ed47aec.

📒 Files selected for processing (1)
  • src/react/components/ColorChannelComponents.tsx
📝 Walkthrough

Walkthrough

Refactored ColorChannelComponents.tsx to replace legacy imports with utility-focused components and reintroduce D3 histogram logic. Improved channel selection defaulting, restructured channel addition flow to add-fetch-apply, added granular error handling with rollback, and consolidated loading state management across channel interactions.

Changes

Cohort / File(s) Summary
Channel Management & State
src/react/components/ColorChannelComponents.tsx
Replaced legacy internal state imports with utility-focused alternatives; improved channel selection with safe defaults (e.g., c ?? 0); refactored channel addition flow to perform addition, fetch statistics, then apply properties with granular loading state and rollback error handling.
Histogram & UI Logic
src/react/components/ColorChannelComponents.tsx
Reintroduced D3-based histogram rendering with formatting consolidations; streamlined range sorting, edges computation, and brush interaction handlers; simplified inline ternary logic within histogram rendering.
Minor Consistency Updates
src/react/components/ColorChannelComponents.tsx
Refined error messages and console logs; updated value handling across channel-related component interactions without altering core functionality.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #317: Overlapping channel-management logic modifications including channel defaulting, addition, and initialization patterns with similar state/ID handling concerns.
  • PR #358: Shares code-level changes to ColorChannelComponents.tsx, specifically histogram rendering, brush/scale handling, and raster/statistics propagation logic.

Poem

🐰 With histograms bright and channels so true,
We've added some safety in all that we do—
When channels won't stick, we revert with a bound,
D3 logic restored, our vision is sound! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: handling channel selection state during Zarr additions. It directly addresses the core objective of guarding the color channel UI and managing state when adding Zarr channels.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/fix-channel-state-bugs
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/react/components/ColorChannelComponents.tsx (1)

119-149: ⚠️ Potential issue | 🟠 Major

Clear the loading slot instead of removing it.

removeIsChannelLoading() in src/react/components/avivatorish/state.tsx:269-273 splices the array. On this path the channel still exists, so Line 149 shifts every later loading flag left and leaves isChannelLoading out of sync with selections and ids. Keep removeIsChannelLoading() for optimistic add rollback, but just clear the slot here.

🛠️ Suggested fix
                     } catch (e) {
                         console.error("failed to load channel");
                         console.error(e);
-                        removeIsChannelLoading(index);
+                        setIsChannelLoading(index, false);
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/react/components/ColorChannelComponents.tsx` around lines 119 - 149, In
the catch block of the onChange handler (inside the async selection load code),
don't call removeIsChannelLoading(index) because that splices the
isChannelLoading array and misaligns it with selections/ids; instead clear the
loading slot for that channel by calling setIsChannelLoading(index, false) (or
equivalent to set the boolean at that index), leaving removeIsChannelLoading()
reserved for optimistic-add rollback paths.
🧹 Nitpick comments (1)
src/react/components/avivatorish/state.tsx (1)

290-292: Drop the extra MobX observer boundary.

VivProvider only forwards vivStores and children, so normal React prop changes already re-render it. Wrapping a pure context provider in observer() just adds another MobX subscription layer here.

Based on learnings: in this codebase, observer() is only needed for components that directly access MobX observables or stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/react/components/avivatorish/state.tsx` around lines 290 - 292,
VivProvider is unnecessarily wrapped with MobX observer which adds an extra
subscription layer even though it only forwards vivStores and children; remove
the observer(...) wrapper so VivProvider is a plain functional component that
returns <VivContext.Provider value={vivStores}>{children}</VivContext.Provider>,
keeping the same PropsWithChildren and VivContextType types and exported name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/react/components/avivatorish/utils.ts`:
- Around line 294-345: The code builds dimensions with zip(labels, shape) but
only later adds an interleaved "c" via getSourceDimensionMap, causing zip to
miss sample-only axes; update buildSelectionForSource to first call
getSourceDimensionMap(source) and derive a normalized dimensions array from that
map (e.g., Array.from(dimensionMap.values()) mapping to {name, size}), then pass
that normalized list into getDefaultGlobalSelection and use it for selection
logic; ensure you keep using clampSelectionValue and that nextSelection keys use
the canonical dimension.name so buildSelectionForSource, getSourceDimensionMap,
and getDefaultGlobalSelection all agree on dimensions.

---

Outside diff comments:
In `@src/react/components/ColorChannelComponents.tsx`:
- Around line 119-149: In the catch block of the onChange handler (inside the
async selection load code), don't call removeIsChannelLoading(index) because
that splices the isChannelLoading array and misaligns it with selections/ids;
instead clear the loading slot for that channel by calling
setIsChannelLoading(index, false) (or equivalent to set the boolean at that
index), leaving removeIsChannelLoading() reserved for optimistic-add rollback
paths.

---

Nitpick comments:
In `@src/react/components/avivatorish/state.tsx`:
- Around line 290-292: VivProvider is unnecessarily wrapped with MobX observer
which adds an extra subscription layer even though it only forwards vivStores
and children; remove the observer(...) wrapper so VivProvider is a plain
functional component that returns <VivContext.Provider
value={vivStores}>{children}</VivContext.Provider>, keeping the same
PropsWithChildren and VivContextType types and exported name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d341d6e4-fe8e-4bfe-8291-c56771ba2281

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9bc84 and ad0d7af.

📒 Files selected for processing (4)
  • src/react/components/ColorChannelComponents.tsx
  • src/react/components/avivatorish/hooks.ts
  • src/react/components/avivatorish/state.tsx
  • src/react/components/avivatorish/utils.ts

Comment thread src/react/components/avivatorish/utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/react/components/avivatorish/utils.ts`:
- Line 31: The current OME-TIFF detection uses case-sensitive includes checks
(name.includes("ome.tiff") || name.includes("ome.tif") ||
name.includes(".companion.ome")), which misses uppercase/mixed-case extensions;
fix it by normalizing the filename to lowercase before checking (e.g., const
lower = name.toLowerCase(); then use lower.includes("ome.tiff") ||
lower.includes("ome.tif") || lower.includes(".companion.ome")), or alternatively
use a case-insensitive regex (e.g., /ome\.tiff|ome\.tif|\.companion\.ome/i) to
perform the test so detection is case-insensitive.
- Around line 202-203: The code dereferences urlOrFile[0] without checking for
an empty array, causing a TypeError for [] before your UnsupportedBrowserError
is thrown; update the conditional that currently reads like if
(isArray(urlOrFile) && typeof urlOrFile[0].arrayBuffer !== "function") to first
check urlOrFile.length > 0 (e.g., if (isArray(urlOrFile) && urlOrFile.length > 0
&& typeof urlOrFile[0].arrayBuffer !== "function")), and separately handle the
empty-array case (throw a clear error or return early) so empty file arrays are
guarded before accessing [0] while still throwing an appropriate error (for
example using UnsupportedBrowserError or a more specific InvalidInput error)
when needed.
- Line 608: The SizeUnit type currently references only SI_PREFIXES symbols but
sizeToMeters expects full unit strings like "mm"/"cm"/"µm" (it strips a trailing
"m"), so update the SizeUnit definition to represent the full unit form (prefix
+ "m") and include the bare "m" case; e.g. derive it from SI_PREFIXES by using a
template literal like `${typeof SI_PREFIXES[number]["symbol"]}m` plus "m" (or
otherwise map SI_PREFIXES symbols to `${symbol}m`) so SizeUnit matches the
values accepted by sizeToMeters and callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 497704af-e03d-42bd-bd9e-958552e63734

📥 Commits

Reviewing files that changed from the base of the PR and between ad0d7af and 66e0dac.

📒 Files selected for processing (1)
  • src/react/components/avivatorish/utils.ts

Comment thread src/react/components/avivatorish/utils.ts Outdated
Comment thread src/react/components/avivatorish/utils.ts Outdated
Comment thread src/react/components/avivatorish/utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/react/components/ColorChannelComponents.tsx (1)

103-150: ⚠️ Potential issue | 🟠 Major

Do not splice isChannelLoading when a channel selection fetch fails.

Line 149 removes the loading slot (removeIsChannelLoading(index)) even though no channel is removed. That shifts loading flags and misaligns indices across channels.

Suggested fix
-    const { setIsChannelLoading, isChannelLoading, removeIsChannelLoading, use3d } = useViewerStore(
-        ({ setIsChannelLoading, isChannelLoading, removeIsChannelLoading, use3d }) => ({
+    const { setIsChannelLoading, isChannelLoading, use3d } = useViewerStore(
+        ({ setIsChannelLoading, isChannelLoading, use3d }) => ({
             setIsChannelLoading,
             isChannelLoading,
-            removeIsChannelLoading,
             use3d,
         }),
         shallow,
     );
...
                     } catch (e) {
                         console.error("failed to load channel");
                         console.error(e);
-                        removeIsChannelLoading(index);
+                        setIsChannelLoading(index, false);
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/react/components/ColorChannelComponents.tsx` around lines 103 - 150, The
catch block currently calls removeIsChannelLoading(index) which splices the
loading array and shifts indices; instead, clear the loading flag for that
channel without removing the slot by calling setIsChannelLoading(index, false)
(or the equivalent setter) in the catch handler of the onChange async flow that
calls getSingleSelectionStats; update references to removeIsChannelLoading only
for actual channel removals, not for fetch failures, to preserve index
alignment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/react/components/ColorChannelComponents.tsx`:
- Around line 552-589: The onClick handler captures a transient numeric index
and then awaits async work, which can become stale when multiple rapid clicks
overlap; change the flow so you don't rely on the captured `index` after awaits
— either serialize add operations (use a simple in-memory lock/queue) or make
`addChannel` return a stable channel identifier/object and use that identifier
to call `setPropertiesForChannel`, `setIsChannelLoading`, `removeChannel`, and
`removeIsChannelLoading` instead of the captured `index`; ensure
`setIsChannelLoading` is set/cleared tied to that stable id and that
rollback/removal uses the same id so props won’t be applied to the wrong channel
when `getSingleSelectionStats` resolves.

---

Outside diff comments:
In `@src/react/components/ColorChannelComponents.tsx`:
- Around line 103-150: The catch block currently calls
removeIsChannelLoading(index) which splices the loading array and shifts
indices; instead, clear the loading flag for that channel without removing the
slot by calling setIsChannelLoading(index, false) (or the equivalent setter) in
the catch handler of the onChange async flow that calls getSingleSelectionStats;
update references to removeIsChannelLoading only for actual channel removals,
not for fetch failures, to preserve index alignment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40375a4f-3ee1-4026-94b3-86f155e58b7e

📥 Commits

Reviewing files that changed from the base of the PR and between 66e0dac and 320cd36.

📒 Files selected for processing (1)
  • src/react/components/ColorChannelComponents.tsx

Comment thread src/react/components/ColorChannelComponents.tsx
@xinaesthete
xinaesthete merged commit 95f0950 into main Mar 13, 2026
4 checks passed
@xinaesthete
xinaesthete deleted the codex/fix-channel-state-bugs branch March 13, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants