Skip to content

feat: observation list toggle#1795

Merged
cimigree merged 10 commits intodevelopfrom
feat/obs-list-toggle
Apr 8, 2026
Merged

feat: observation list toggle#1795
cimigree merged 10 commits intodevelopfrom
feat/obs-list-toggle

Conversation

@cimigree
Copy link
Copy Markdown
Contributor

added on top of #1782
closes #1711

Summary

  • When Early Access in enabled, Adds a filter toggle to the Observations List
  • Users can switch between All (all observations) and Just You (only observations created on this device)
  • Filter state persists across navigation and resets to "All" on app restart or after 1 hour of inactivity, and when switching projects

Implementation details

  • New ObservationFilterContext (Zustand + MMKV persistence) manages filter state
  • Filtering uses the createdBy field on observation/track documents to match the current device ID
  • Both observations and tracks are filtered

Testing

  • Adds E2E test (tests/e2e/specs/observations/observation-filter-toggle.test.ts) covering:
    • Toggle appears when Early Access is on, hidden when off
    • Default state is "All" active
    • Toggling between All and Just You updates active state correctly
    • Observations remain visible after toggling (single-device test)
    • I can't actually test whether or not the filter really works because I can't get the observations from another device, so I will add that to the manual testing document.
  • Updated docs/EndToEndTests/Observations.md

Screenshots

No Toggle (no early access)

image

All
image

Just You
image

@cimigree cimigree requested a review from ErikSin March 30, 2026 21:45
Copy link
Copy Markdown
Contributor

@ErikSin ErikSin left a comment

Choose a reason for hiding this comment

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

I think we can get ride of the provider completely and hold all the state in observationsList/index.tsx. Because it is a tab IT always remain mounted, so if the user sets state in that page, that state does not reset when the user navigates away (like it normally does in react). So therefore we dont actually need a context to hold the state.

Eg:

export function ObservationList(){

 const isEarlyAccessEnabled = useEarlyAccessState(s => s.isEarlyAccessEnabled);
 const [filterMode, setFilterMode] = React.useState<'all' | 'mine'>('all');
 const lastInteractionRef = React.useRef<number | null>(null);

useFocusEffect(
    // check on every navigation event back to this page whether it has been an hour and the filter should reset
    React.useCallback(() => {
      if (
        lastInteractionRef.current !== null &&
        Date.now() - lastInteractionRef.current > 1_HOUR
      ) {
        setFilterMode('all');
        lastInteractionRef.current = null;
      }
    }, []),
  );

 function handleFilterModeChange(mode: 'all' | 'mine'){
   setFilterMode(mode);
   lastInteractionRef.current = Date.now();
 {

 const filteredData = //filtered data here
 
 return (
   {isEarlyAccessEnabled && (
        <ObservationFilterToggle
          filterMode={filterMode}
          onFilterModeChange={handleFilterModeChange}
        />
      )}

   < observations={filteredData}
     ...REST OF OBSERVATION LIST />
 )
}

What do you think? This feel a little more verbose, but simpler architecturally

createJSONStorage,
persist as createPersistedState,
} from 'zustand/middleware';
import * as v from 'valibot';
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.

I don't think we should use a persisted store here. The requirement is that when the app resets, the state reset (it goes back to showing all observations). In otherwords it does not need to know the state before it was reset since its alway shows all observations and therefore, the state does not need to be persisted.

Comment thread src/frontend/contexts/AppProviders.tsx Outdated
<EarlyAccessStoreProvider
value={earlyAccessStore}>
<AuthProvider>{children}</AuthProvider>
<ObservationFilterStoreProvider
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.

I think we should scope this provider. The ObservationFilterStoreProvider is only being used by the observation list, so lets just wrap the observation list in this context so its not being consumed by the entire app

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.

Upon further thought, I don't think we need a provider. The state can live all in the observation list

Comment on lines +74 to +78
React.useEffect(() => {
if (isEarlyAccessEnabled) {
checkAndResetIfNeeded();
}
}, [isEarlyAccessEnabled, checkAndResetIfNeeded]);
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.

this check should happen everytime the user is navigated back to this page. The requirement is that it should reset every hour. Right now it will only reset if early access is enabled or disabled.

There could be the case where the user is on this page for over an hour (and hasn't navigated away and back to this page). If thats the case, it wont reset, but I think that is ok. We would have to create a timer, which feel like overkill and I think it ok to just check on navigation events.

@cimigree
Copy link
Copy Markdown
Contributor Author

cimigree commented Apr 7, 2026

@ErikSin, I am afraid to say I didn't realize the state was not reset when the user navigated away! So I think your solution is a great one given that truth.
I updated to include your changes. I also had to add a useEffect to reset when the early access mode is turned off...

@cimigree cimigree requested a review from ErikSin April 7, 2026 15:40
@ErikSin
Copy link
Copy Markdown
Contributor

ErikSin commented Apr 7, 2026

@ErikSin, I am afraid to say I didn't realize the state was not reset when the user navigated away!

I only know this from spending DAYS trying to figure out why the location hook we were using in the map screen was not unmounting when the user navigated away.

Copy link
Copy Markdown
Contributor

@ErikSin ErikSin left a comment

Choose a reason for hiding this comment

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

It looks good. Sorry you had to go through the work of creating and deleting the provider, but the subsequent changes are architecturally really simple which is nice.

1 minor problem, but not a blocker

Comment on lines +72 to +87
setFilterMode('all');
lastInteractionRef.current = null;
}
}, []),
);

React.useEffect(() => {
if (!isEarlyAccessEnabled) {
setFilterMode('all');
lastInteractionRef.current = null;
}
}, [isEarlyAccessEnabled]);

function handleFilterModeChange(mode: 'all' | 'mine') {
setFilterMode(mode);
lastInteractionRef.current = Date.now();
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.

There is a slight discrepancy in how we are setting the lastInteractionRef. In the useFocusEffect and the useEffect when filterMode is being set to all we are setting lastInteractionRef to null. In the handleFilterModeChange we are setting lastInteractionRef to a date regardless.

I think the best would be to just change it in handleFilterModeChange

eg:

function handleFilterModeChange(mode: 'all' | 'mine') {
  setFilterMode(mode);
  lastInteractionRef.current = mode === 'mine' ? Date.now() : null;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +78 to +83
React.useEffect(() => {
if (!isEarlyAccessEnabled) {
setFilterMode('all');
lastInteractionRef.current = null;
}
}, [isEarlyAccessEnabled]);
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.

Sorry, one more thing, I think we can get rid of this useEffect entitely. I think the filteredData handles the early access state.

If the user had it filterMode set to 'mine' and then turned off the early access, the state would not be reset (but the user would still see ALL observations). And then if they turn early access on again (before 1 hour has passed), THEN the state would be set to 'mine' which i don't think is a problem

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@cimigree cimigree merged commit b50579d into develop Apr 8, 2026
11 checks passed
@cimigree cimigree deleted the feat/obs-list-toggle branch April 8, 2026 15:37
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.

Filtered data view

2 participants