Skip to content

This is the Crystal Lattice Control desktop frontend (React Native macOS), bootstrapped using @react-native-community/cli and react-native-macos.

Versions

  • Desktop: react-native@0.81.5 + react-native-macos@0.81.1 (desktop/crystal-lattice-control-desktop-rn/package.json:1)
  • Mobile (reference): react-native@0.82.1 (mobile/crystal-lattice-control-rn/package.json:1)

Monorepo shared packages

This app imports workspace packages via file: dependencies:

  • @crystal-lattice/frontend-sdk
  • @crystal-lattice/frontend-sdk-react
  • @crystal-lattice/frontend-rn-components

Metro + TS are configured to handle symlinks (desktop/crystal-lattice-control-desktop-rn/metro.config.js:1, desktop/crystal-lattice-control-desktop-rn/tsconfig.json:1).

User documentation

Development documentation

Getting Started

Note: Make sure you have completed the Set Up Your Environment guide before proceeding.

Step 1: Start Metro

First, you will need to run Metro, the JavaScript build tool for React Native.

To start the Metro dev server, run the following command from the root of your React Native project:

# Using npm
npm start

# OR using Yarn
yarn start

Step 2: Build and run your app

With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app:

Android

# Using npm
npm run android

# OR using Yarn
yarn android

iOS

For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps).

The first time you create a new project, run the Ruby bundler to install CocoaPods itself:

bundle install

Then, and every time you update your native dependencies, run:

bundle exec pod install

For more information, please visit CocoaPods Getting Started guide.

# Using npm
npm run ios

# OR using Yarn
yarn ios

macOS

macOS builds require Xcode and CocoaPods.

cd macos
pod install
cd ..
npm run macos

If you prefer using Bundler to pin CocoaPods versions, use bundle exec pod install --project-directory=macos instead.

Troubleshooting: bundle install fails building json

If you see an error like extconf failed while installing json, you are likely using the system Ruby (/usr/bin/ruby) which may not ship buildable headers on newer macOS/Xcode SDKs.

Recommended fix: install a modern Ruby and retry:

brew install ruby
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

cd desktop/crystal-lattice-control-desktop-rn
rm -rf vendor/bundle
gem install bundler
bundle install
bundle exec pod install --project-directory=macos

Alternative (skip Bundler):

brew install cocoapods
pod install --project-directory=macos

If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.

This is one way to run your app — you can also build it directly from Android Studio or Xcode.

Desktop transient UI

On macOS, anchored transient controls such as action menus and session filters must use the native NSPopover bridge. Do not present these controls through React Native Modal or the shared modal-backed dropdown surface. Keep reusable content in shared React components and mount that content in the desktop popover root.

macOS UI tests

The desktop app uses the existing XCUITest target on the host Mac rather than Detox. Run all macOS UI tests with:

npm run test:macos-ui

Run the focused native session-filter popover test with:

npm run test:macos-ui:session-filters

The focused command launches a deterministic harness that renders the production sessions panel and shared filter controls.

Inspect agent instructions

Crystal Lattice compiles project guidance into the effective developer/system message used by an agent session. To include task-file guidance:

  1. Open the active chat.
  2. Select Settings.
  3. In the Session settings window, enable Include TASKS Guidance.

Open the chat's session actions and select Show compiled system message to display the effective message in a separate action-display window. Inspection does not send a model request, save the session, or add to the conversation. Lengthy prompts scroll inside the window. Check that the expected project instructions and, when enabled, task guidance are present.

Step 3: Modify your app

Now that you have successfully run the app, let's make changes!

Open App.tsx in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by Fast Refresh.

When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload:

  • Android: Press the R key twice or select "Reload" from the Dev Menu, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS).
  • iOS: Press R in iOS Simulator.

Congratulations! :tada:

You've successfully run and modified your React Native App. :partying_face:

Now what?

  • If you want to add this new React Native code to an existing application, check out the Integration guide.
  • If you're curious to learn more about React Native, check out the docs.

Troubleshooting

If you're having issues getting the above steps to work, see the Troubleshooting page.

Learn More

To learn more about React Native, take a look at the following resources:

Transcript scroll diagnostics

Desktop transcript navigation and content-follow diagnostics are disabled by default. Enable concise lifecycle logging before the app is registered:

globalThis.CLC_DEBUG_CHAT_SCROLL_COORDINATOR = true;

Lifecycle mode reports navigation start/completion, bounded content-follow lease start/renewal/expiration, interaction cancellation, and operation timeouts. Repeated lease renewals are rate-limited so streaming logs remain copyable.

For frame-level native observations and correction writes, additionally set:

globalThis.CLC_DEBUG_CHAT_SCROLL_COORDINATOR_VERBOSE = true;

Verbose mode is intended for focused manual or harness runs and should normally remain disabled.

The entry point emits one [chatScrollBootstrap] line when lifecycle diagnostics are enabled. Each mounted transcript then emits one configuration event. Check both lines before interpreting a trace:

maxEdgeCorrections=60
manualBottomConvergenceMaxMs=15000
initialBottomConvergenceMaxMs=15000

Far FlatList item-anchor navigation additionally reports:

listAnchorStarted
listAnchorProgress
listAnchorCompleted
listAnchorStopped

The start event includes the effective timeoutMs and maxAttempts. These settings can be configured before application modules are loaded:

globalThis.CLC_FLATLIST_MAX_EDGE_CORRECTIONS = 60;
globalThis.CLC_FLATLIST_ITEM_ANCHOR_TIMEOUT_MS = 15000;
globalThis.CLC_FLATLIST_ITEM_ANCHOR_MAX_ATTEMPTS = 200;

Stable-geometry manual detachment

The coordinator classifies movement away from a pinned bottom using native event-to-event bottom gaps and geometry revisions.

An unexplained movement with stable content/viewport geometry reports:

awayMotionCandidate
reason=stableGeometryAwayMotion

The event includes bottom-gap and offset deltas, geometry revisions, active operation details, and whether the movement opposed a recent programmatic target.

An accepted candidate immediately reports:

manualBottomDetached
reason=stableGeometryAwayMotion

It latches manuallyDetached affinity, clears any follow lease, and cancels the active content-follow operation. Semantic content changes cannot reattach the transcript.

Stable manual movement back toward the bottom that crosses into the measured bottom threshold reports:

manualBottomReattachCandidate
manualBottomReattached

This changes affinity back to pinned without issuing a scroll command or starting a lease. The next semantic content change can then use normal bounded following. Active or recent programmatic navigation cannot trigger this manual-return transition. Successful explicit bottom navigation also reattaches after convergence.

A candidate that is compatible with a recent programmatic target is retained as a diagnostic but reports:

awayMotionDetachSuppressed
reason=recentProgrammaticMovementNotOpposed

An away movement observed while layout or semantic content is still settling reports:

awayMotionIgnored
reason=geometryChanged|recentGeometryChange|semanticContentPending

Candidate output is rate-limited to four lines per second and ignored output to two lines per second so a short manual gesture remains copyable.

License

Copyright 2026 Dynamic Programming Solutions Kft.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.