Skip to content

Add OpenRig-inspired topology navigation cleanup - #42

Merged
TraderSamwise merged 7 commits into
masterfrom
phase/openrig-native-ui-next
May 27, 2026
Merged

Add OpenRig-inspired topology navigation cleanup#42
TraderSamwise merged 7 commits into
masterfrom
phase/openrig-native-ui-next

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented May 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • make topology project URL state authoritative across shell/sidebar/topology
  • clarify clickable topology map nodes and truncate map labels
  • fix native routing black screen by removing the duplicate root redirect route and adding the Expo Router native entrypoint

Verification

  • yarn typecheck
  • yarn lint
  • yarn test
  • app focused eslint/typecheck
  • Chrome MCP topology navigation/back validation
  • iOS simulator paints the real app shell

Summary by CodeRabbit

  • New Features

    • Views now support URL-based state management, enabling users to bookmark and share specific view configurations (selected documents, agents, services, and view modes).
  • Improvements

    • Refined navigation consistency across dashboard, library, topology, and notification sections.
    • Added loading indicators during asynchronous state initialization.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ab143e93-4304-4fba-a342-599eceb82a98

📥 Commits

Reviewing files that changed from the base of the PR and between abebc98 and ccd931b.

📒 Files selected for processing (5)
  • app/app/(main)/(tabs)/_layout.tsx
  • app/app/(main)/(tabs)/topology/index.tsx
  • app/app/(main)/_layout.tsx
  • app/components/ProjectSidebar.tsx
  • app/lib/view-location.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/lib/view-location.ts
  • app/app/(main)/_layout.tsx
  • app/app/(main)/(tabs)/_layout.tsx
  • app/app/(main)/(tabs)/topology/index.tsx

📝 Walkthrough

Walkthrough

This PR refactors the app to drive UI state from URL search parameters instead of component-local React state. It introduces a centralized view-location module with href builders (buildViewHref, detailHrefForPath, parentViewHrefForPath), extracts chat and service screens into reusable components, synchronizes an effectiveProjectPath from URL through the main layout to all polling effects, and updates all five tabs (dashboard, notifications, library, project, topology) to use URL-driven state for lens, section, document, and mode selections.

Changes

URL-driven routing and state refactor

Layer / File(s) Summary
View-location URL/href infrastructure and tests
app/lib/view-location.ts, app/lib/view-location.test.ts
New centralized module for URL parsing and Expo Router href construction: buildViewHref filters empty parameters, mergeViewParams combines search state, detailHrefForPath builds stack-local detail routes with URL encoding, parentViewHrefForPath links to parent, plus search normalization helpers with browser location fallback. Comprehensive test coverage validates parameter handling and route construction.
AgentChatScreen and ServiceDetailScreen extraction
app/components/screens/AgentChatScreen.tsx, app/components/screens/ServiceDetailScreen.tsx
Two new shared screen components: AgentChatScreen manages agent session state (sessionId sync, token fetching, heartbeat/polling for updates), renders transcript/terminal split with auto-scroll, header with back/invite/toggle controls, share panel with invite/remove/leave actions, and conditional service availability messaging; ServiceDetailScreen looks up service from state, renders service details (status, worktree, command/args/shell/preview) with back navigation and removal callback.
App entry and root layout refactoring
app/index.js, app/app/_layout.tsx
Establishes expo-router/entry as app entrypoint, removes RootIndex auth redirect component, and refactors RootLayout stack to apply headerShown: false to the (main) route group instead of index.
Main layout effective project path synchronization
app/app/(main)/_layout.tsx
MainLayout derives effectiveProjectPath from projectPathFromSearchOrLocation (URL fallback to local atom), uses platform-specific pre-paint effect (useLayoutEffect on web, useEffect on native) to sync selectedProjectPathAtom and clear selectedSessionIdAtom when URL-derived project differs and session is not shared; desktop-state and notifications polling effects now key all store operations (read/write/clear) off effectiveProjectPath instead of selectedProjectPath.
Tab configuration and platform behavior
app/app/(main)/(tabs)/_layout.tsx, app/lib/main-tabs.ts, app/app/(main)/(tabs)/(inbox)/notifications/_layout.tsx, app/app/(main)/(tabs)/library/_layout.tsx, app/app/(main)/(tabs)/project/_layout.tsx, app/app/(main)/(tabs)/topology/_layout.tsx
MainTabsLayout makes freezeOnBlur platform-dependent (native only), useMainTabNavigation reads selectedProjectPath and navigates via buildViewHref(tab.href, { project }), and each of five tabs gains a stack layout (NotificationsStackLayout, LibraryStackLayout, ProjectStackLayout, TopologyStackLayout, plus dashboard inherits existing) with headerShown: false and three routes each (index, agent/[sessionId]/chat, service/[serviceId]).
Tab route migrations to URL-driven state
app/app/(main)/(tabs)/(dashboard)/*, app/app/(main)/(tabs)/(inbox)/notifications/*, app/app/(main)/(tabs)/library/*, app/app/(main)/(tabs)/project/*, app/app/(main)/(tabs)/topology/*
All five tabs migrate selections (lens, section, document, mode) from local state to URL search params: each imports Expo Router hooks (useGlobalSearchParams, useRouter, usePathname), introduces a resolve*() helper to normalize query values, derives selected state via useGlobalSearchParams and resolveLens/Section/Mode, navigates via router.replace(buildViewHref(...)) on chip/row changes instead of setState, and updates detail navigation to use detailHrefForPath(pathname, kind, id, projectPath). Dashboard/notifications/library/project/topology screens become thin re-exports of AgentChatScreen and ServiceDetailScreen.
Shell and sidebar navigation integration
app/components/MobileTabBar.tsx, app/components/ProjectSidebar.tsx
MobileTabBar imports useRouter and buildViewHref, navigates via router.navigate(buildViewHref(tabRoute.href, { project: selectedProjectPath })); ProjectSidebar derives effectiveProjectPath from searchParams.project ?? selectedProjectPath, computes effectiveProject and endpoint, uses usePrePaintEffect to auto-close picker when effective path changes, navigates via router.replace with shared href builders, and passes desktopState as explicit prop to WorktreeTree with loading placeholder ("Loading project state...") when endpoint exists but desktopState is null.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 With URLs now deep and anchored true,
Screens extracted, shared for all to view,
State flows through params, no local confusion,
Effective paths sync without intrusion,
Five tabs reborn in harmony's embrace!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the primary changes, focusing on topology navigation cleanup and routing refactoring across multiple navigation systems.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase/openrig-native-ui-next

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/app/(main)/(tabs)/_layout.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

app/app/(main)/(tabs)/topology/index.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

app/app/(main)/_layout.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 2 others

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/app/`(main)/_layout.tsx:
- Around line 52-53: Prevent reuse of a previously selected project's endpoint
when the URL points to a different project by only falling back to
selectedProjectEndpoint if the URL project matches the currently selected
project; change the endpoint computation (variable endpoint) to prefer
effectiveProject.serviceEndpoint, but if effectiveProject is missing only use
selectedProjectEndpoint when urlProjectPath === selectedProject?.projectPath
(otherwise leave endpoint undefined/null) so writes cannot land under the wrong
effectiveProjectPath.

In `@app/app/`(main)/(tabs)/_layout.tsx:
- Line 16: The freezeOnBlur flag is currently enabled for web due to an inverted
Platform.OS check; update the assignment (the object/property where freezeOnBlur
is set) to enable freezeOnBlur only on native platforms by changing the
condition to Platform.OS !== "web" (or explicitly Platform.OS === "ios" ||
Platform.OS === "android") so that freezeOnBlur is false on web and true on
native.

In `@app/app/`(main)/(tabs)/topology/index.tsx:
- Around line 451-453: The endpoint fallback to selectedProjectEndpoint can
point to a different project; change the binding so endpoint is strictly the
URL-resolved project's endpoint (use const endpoint = project?.serviceEndpoint;)
and remove the ?? selectedProjectEndpoint fallback; then audit code that
consumes endpoint (and any fetch/render logic) to handle undefined endpoints
gracefully or explicitly use selectedProjectEndpoint only when project ===
selectedProject. Reference: project, projects, urlProjectPath,
selectedProjectEndpoint, desktopStateFamily, desktopState.

In `@app/components/AppShell.tsx`:
- Around line 24-31: The two useEffect hooks wrongly include sidebarOpen in
their dependency arrays which causes the drawer to immediately close when
opened; fix by removing sidebarOpen from both deps: make the "on load" effect
run only on mount (change the deps to [] and keep the body using isMobile &&
sidebarOpen to close on initial load), and make the "on navigation" effect run
only when pathname changes (set its deps to [pathname, setSidebarOpen] or
[pathname] if you prefer and add a // eslint-disable-next-line
react-hooks/exhaustive-deps lint comment); update the effects that reference
useEffect, isMobile, sidebarOpen, setSidebarOpen, and pathname accordingly.

In `@app/components/ProjectSidebar.tsx`:
- Around line 528-531: The effect in usePrePaintEffect closes the picker
immediately because showPicker is included in the dependency array; remove
showPicker from the deps so the effect only runs when effectiveProjectPath or
selectedProjectPath change, and keep the unconditional setShowPicker(false)
inside the effect; add a single-line lint suppression for
react-hooks/exhaustive-deps to acknowledge the intentional omission (referencing
usePrePaintEffect, showPicker, setShowPicker, effectiveProjectPath,
selectedProjectPath).

In `@app/lib/view-location.ts`:
- Around line 55-64: mergeViewParams currently uses the nullish coalescing
operator (??) so passing explicit nulls in next (e.g. { mode: null }) falls back
to the current search and cannot clear params; update the merge logic to treat
only undefined as "no override" and allow null to pass through: for project,
mode, lens, section, document change each expression from "next.X ?? fallback"
to "next.X !== undefined ? next.X : fallback" (for project keep using
projectPathFromSearch(current.project) as the fallback and for the others keep
cleanSearchValue(current.<field>) as the fallback) so explicit nulls clear state
while undefined still merges from current.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 058c8ea7-8000-4279-b0ef-a8f7e26b2c7c

📥 Commits

Reviewing files that changed from the base of the PR and between 4f878ab and abebc98.

📒 Files selected for processing (32)
  • app/app/(main)/(tabs)/(dashboard)/agent/[sessionId]/chat.tsx
  • app/app/(main)/(tabs)/(dashboard)/index.tsx
  • app/app/(main)/(tabs)/(dashboard)/service/[serviceId].tsx
  • app/app/(main)/(tabs)/(inbox)/notifications/_layout.tsx
  • app/app/(main)/(tabs)/(inbox)/notifications/agent/[sessionId]/chat.tsx
  • app/app/(main)/(tabs)/(inbox)/notifications/index.tsx
  • app/app/(main)/(tabs)/(inbox)/notifications/service/[serviceId].tsx
  • app/app/(main)/(tabs)/_layout.tsx
  • app/app/(main)/(tabs)/library/_layout.tsx
  • app/app/(main)/(tabs)/library/agent/[sessionId]/chat.tsx
  • app/app/(main)/(tabs)/library/index.tsx
  • app/app/(main)/(tabs)/library/service/[serviceId].tsx
  • app/app/(main)/(tabs)/project/_layout.tsx
  • app/app/(main)/(tabs)/project/agent/[sessionId]/chat.tsx
  • app/app/(main)/(tabs)/project/index.tsx
  • app/app/(main)/(tabs)/project/service/[serviceId].tsx
  • app/app/(main)/(tabs)/topology/_layout.tsx
  • app/app/(main)/(tabs)/topology/agent/[sessionId]/chat.tsx
  • app/app/(main)/(tabs)/topology/index.tsx
  • app/app/(main)/(tabs)/topology/service/[serviceId].tsx
  • app/app/(main)/_layout.tsx
  • app/app/_layout.tsx
  • app/app/index.tsx
  • app/components/AppShell.tsx
  • app/components/MobileTabBar.tsx
  • app/components/ProjectSidebar.tsx
  • app/components/screens/AgentChatScreen.tsx
  • app/components/screens/ServiceDetailScreen.tsx
  • app/index.js
  • app/lib/main-tabs.ts
  • app/lib/view-location.test.ts
  • app/lib/view-location.ts
💤 Files with no reviewable changes (2)
  • app/app/index.tsx
  • app/app/_layout.tsx

Comment thread app/app/(main)/_layout.tsx Outdated
Comment thread app/app/(main)/(tabs)/_layout.tsx Outdated
Comment thread app/app/(main)/(tabs)/topology/index.tsx Outdated
Comment thread app/components/AppShell.tsx Outdated
Comment thread app/components/ProjectSidebar.tsx
Comment thread app/lib/view-location.ts Outdated
@TraderSamwise
TraderSamwise merged commit c620930 into master May 27, 2026
1 check passed
@TraderSamwise
TraderSamwise deleted the phase/openrig-native-ui-next branch May 27, 2026 03:10
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.

1 participant