Full Code of alvarlagerlof/rsc-parser for AI

main a23e9607b114 cached
183 files
5.2 MB
1.4M tokens
286 symbols
1 requests
Download .txt
Showing preview only (5,502K chars total). Download the full file or copy to clipboard to get everything.
Repository: alvarlagerlof/rsc-parser
Branch: main
Commit: a23e9607b114
Files: 183
Total size: 5.2 MB

Directory structure:
gitextract_pei3ls9p/

├── .changeset/
│   ├── README.md
│   ├── config.json
│   └── lucky-numbers-roll.md
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── apply-version.sh
├── examples/
│   └── embedded-example/
│       ├── .gitignore
│       ├── .prettierignore
│       ├── CHANGELOG.md
│       ├── LICENSE
│       ├── README.md
│       ├── app/
│       │   ├── action/
│       │   │   └── page.tsx
│       │   ├── error/
│       │   │   └── [name]/
│       │   │       └── page.tsx
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── other/
│       │   │   └── page.tsx
│       │   ├── page.tsx
│       │   └── suspense/
│       │       └── page.tsx
│       ├── eslint.config.js
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.cjs
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       ├── turbo.json
│       └── vercel.json
├── package.json
├── packages/
│   ├── chrome-extension/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── CHANGELOG.md
│   │   ├── LICENSE
│   │   ├── devtoolsPanel.html
│   │   ├── eslint.config.js
│   │   ├── package.json
│   │   ├── public/
│   │   │   ├── devtoolsPage.html
│   │   │   └── manifest.json
│   │   ├── src/
│   │   │   ├── RscDevtoolsExtension.tsx
│   │   │   ├── assets/
│   │   │   │   ├── contentScript.ts
│   │   │   │   ├── devtoolsPage.ts
│   │   │   │   ├── fetchPatcherInjector.ts
│   │   │   │   └── readNextJsScriptTagsInjector.ts
│   │   │   ├── dev/
│   │   │   │   └── reactPreamble.ts
│   │   │   ├── main.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── core/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── CHANGELOG.md
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── eslint.config.js
│   │   ├── jest.config.cjs
│   │   ├── jest.setup.js
│   │   ├── package.json
│   │   ├── postcss.config.cjs
│   │   ├── src/
│   │   │   ├── color.ts
│   │   │   ├── components/
│   │   │   │   ├── BottomPanel.stories.tsx
│   │   │   │   ├── BottomPanel.tsx
│   │   │   │   ├── EndTimeContext.tsx
│   │   │   │   ├── FlightResponseChunkConsole.stories.tsx
│   │   │   │   ├── FlightResponseChunkConsole.tsx
│   │   │   │   ├── FlightResponseChunkDebugInfo.stories.tsx
│   │   │   │   ├── FlightResponseChunkDebugInfo.tsx
│   │   │   │   ├── FlightResponseChunkHint.stories.tsx
│   │   │   │   ├── FlightResponseChunkHint.tsx
│   │   │   │   ├── FlightResponseChunkModel.stories.tsx
│   │   │   │   ├── FlightResponseChunkModel.tsx
│   │   │   │   ├── FlightResponseChunkModule.stories.tsx
│   │   │   │   ├── FlightResponseChunkModule.tsx
│   │   │   │   ├── FlightResponseChunkText.tsx
│   │   │   │   ├── FlightResponseChunkUnknown.tsx
│   │   │   │   ├── FlightResponseIcons.tsx
│   │   │   │   ├── GenericErrorBoundaryFallback.tsx
│   │   │   │   ├── IconButton.tsx
│   │   │   │   ├── Logo.stories.tsx
│   │   │   │   ├── Logo.tsx
│   │   │   │   ├── OverflowButton.tsx
│   │   │   │   ├── PanelLayout.stories.tsx
│   │   │   │   ├── PanelLayout.tsx
│   │   │   │   ├── RecordButton.stories.tsx
│   │   │   │   ├── RecordButton.tsx
│   │   │   │   ├── RequestDetail.tsx
│   │   │   │   ├── RequestDetailTabEmptyState.tsx
│   │   │   │   ├── RequestDetailTabHeaders.stories.tsx
│   │   │   │   ├── RequestDetailTabHeaders.tsx
│   │   │   │   ├── RequestDetailTabNetwork.stories.tsx
│   │   │   │   ├── RequestDetailTabNetwork.tsx
│   │   │   │   ├── RequestDetailTabParsedPayload.stories.tsx
│   │   │   │   ├── RequestDetailTabParsedPayload.tsx
│   │   │   │   ├── RequestDetailTabRawPayload.stories.tsx
│   │   │   │   ├── RequestDetailTabRawPayload.tsx
│   │   │   │   ├── RequestDetailTabTimings.stories.tsx
│   │   │   │   ├── RequestDetailTabTimings.tsx
│   │   │   │   ├── TimeScrubber.stories.tsx
│   │   │   │   ├── TimeScrubber.tsx
│   │   │   │   ├── ViewerPayload.stories.tsx
│   │   │   │   ├── ViewerPayload.tsx
│   │   │   │   ├── ViewerStreams.stories.tsx
│   │   │   │   ├── ViewerStreams.tsx
│   │   │   │   ├── ViewerStreamsEmptyState.stories.tsx
│   │   │   │   ├── ViewerStreamsEmptyState.tsx
│   │   │   │   ├── isDev.ts
│   │   │   │   └── useTabStoreWithTransitions.ts
│   │   │   ├── copyEventsToClipboard.ts
│   │   │   ├── eventArrayHelpers.ts
│   │   │   ├── events.ts
│   │   │   ├── example-data/
│   │   │   │   ├── alvar-dev.ts
│   │   │   │   ├── gh-fredkiss-dev.ts
│   │   │   │   └── nextjs-org.ts
│   │   │   ├── fetchPatcher.ts
│   │   │   ├── main.ts
│   │   │   ├── readNextJsScriptTags.ts
│   │   │   └── tailwind.css
│   │   ├── tailwind.config.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── embedded/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── CHANGELOG.md
│   │   ├── LICENSE
│   │   ├── RscDevtoolsPanel.tsx
│   │   ├── eslint.config.js
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── react-client/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc
│   │   ├── CHANGELOG.md
│   │   ├── eslint.config.js
│   │   ├── flight.ts
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── ReactDOMTypes.ts
│   │   │   ├── ReactFlightClient.ts
│   │   │   ├── ReactFlightClientConfigBrowser.ts
│   │   │   ├── ReactFlightClientConfigBundlerWebpack.ts
│   │   │   ├── ReactFlightImportMetadata.ts
│   │   │   ├── ReactFlightServerConfigDOM.ts
│   │   │   ├── ReactSymbols.ts
│   │   │   ├── ReactTypes.ts
│   │   │   └── crossOriginStrings.ts
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── storybook/
│   │   ├── .gitignore
│   │   ├── .storybook/
│   │   │   ├── main.ts
│   │   │   ├── preview-body.html
│   │   │   └── preview.ts
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vercel.json
│   └── website/
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── LICENSE
│       ├── app/
│       │   ├── ViewerPayloadClientWrapper.tsx
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   └── page.tsx
│       ├── eslint.config.js
│       ├── jest.config.js
│       ├── jest.setup.js
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.cjs
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       ├── turbo.json
│       └── vercel.json
├── renovate.json
└── turbo.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .changeset/README.md
================================================
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)


================================================
FILE: .changeset/config.json
================================================
{
  "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [
    [
      "@rsc-parser/chrome-extension",
      "@rsc-parser/core",
      "@rsc-parser/website",
      "@rsc-parser/embedded",
      "@rsc-parser/react-client"
    ]
  ],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}


================================================
FILE: .changeset/lucky-numbers-roll.md
================================================
---
'@rsc-parser/chrome-extension': patch
'@rsc-parser/embedded-example': patch
'@rsc-parser/core': patch
'@rsc-parser/embedded': patch
'@rsc-parser/react-client': patch
'@rsc-parser/storybook': patch
'@rsc-parser/website': patch
---

Uninstall unused web-vitals package


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  pull_request_target:
    # Types must be filtered to prevent getting stuck when a
    # bot with GITHUB_TOKEN pushes commits against the PR branch
    types:
      - opened
      - reopened
  pull_request:
    branches:
      - main

jobs:
  build_and_test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - uses: oven-sh/setup-bun@v2

      - name: bun install
        run: bun install

      - name: Turborepo
        run: bun run ci

      - name: Archive production artifacts
        uses: actions/upload-artifact@v6
        with:
          name: dist.zip
          path: |
            packages/chrome-extension/dist


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v6
        with:
          ref: ${{github.event.pull_request.head.sha}}
          fetch-depth: 0

      - name: Setup Node.js 18
        uses: actions/setup-node@v6
        with:
          node-version: 24.14.1

      - uses: oven-sh/setup-bun@v2

      - name: Install Dependencies
        run: bun install

      - name: Run CI
        run: bun run ci

      - name: Create Release Pull Request
        uses: changesets/action@v1
        id: changesets
        with:
          version: bun run version
          publish: bun run release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Zip dist
        if: steps.changesets.outputs.published == 'true'
        run: |
          cd packages/chrome-extension
          zip dist.zip -r dist

      - name: Edit release
        if: steps.changesets.outputs.published == 'true'
        uses: xresloader/upload-to-github-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          file: 'packages/chrome-extension/dist.zip'
          update_latest_release: true


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
/coverage


# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo

# turborepo
.turbo


================================================
FILE: .prettierignore
================================================
.yarn

================================================
FILE: .prettierrc
================================================
{
  "trailingComma": "all",
  "singleQuote": true
}


================================================
FILE: .vscode/settings.json
================================================
{
  "css.lint.unknownAtRules": "ignore",
  "typescript.tsdk": "node_modules/typescript/lib"
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Alvar Lagerlöf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# RSC Parser

This is a parser for React Server Components (RSC) when sent over the network. React uses a format to represent a tree of components/html or metadata such as required imports, suspense boundaries, and css/fonts that needs to be loaded.

I made this tool to more easily let you understand the data and explore it visually.

## Comparison

<div style="display: flex; flex-direction: row;">

<img width="49%" alt="image" src="https://github.com/alvarlagerlof/rsc-parser/assets/14835120/f4384956-74e0-4647-a27e-b154a8716afa">

<img width="49%" alt="image" src="https://github.com/alvarlagerlof/rsc-parser/assets/14835120/f8f96e38-fc29-4348-8d83-1a04cd6322aa">
</div>

## How do I use this?

### Extension

There is a Chrome Extension that you can add [here](https://chromewebstore.google.com/detail/rsc-devtools/jcejahepddjnppkhomnidalpnnnemomn).

### npm package

You can also add the parser as a package to your project. You'll get an embedded panel component that you can render in a layout.tsx for example.

First, install [@rsc-parser/embedded](https://www.npmjs.com/package/@rsc-parser/embedded) from npm like: `bun add @rsc-parser/embedded`

Then you can load it in a `layout.tsx` for example.

```tsx
import { Suspense, lazy } from "react";

const RscDevtoolsPanel = lazy(() =>
  import("@rsc-parser/embedded").then(module => ({
    default: module.RscDevtoolsPanel,
  })),
);

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        {/* Use any condition or flag you want here */ }
        {process.env.NODE_ENV === "development" ? (
          <Suspense>
            <RscDevtoolsPanel />
          </Suspense>
        ) : null}
      </body>
    </html>
  );
```

### Website

There is also a website that you can use by manually copy pasting RSC payloads.

1. Go to a site that uses the NextJS App router or generally is based on React Server components.
2. Open the network tab in your dev tools
3. Reload.
4. Look for fetch responses the payload roughly looks like json, but each like starts with something like `O:`, `1:I`, `b:` or similar.
5. Copy the text and paste it into the form on https://rsc-parser.vercel.app/
6. Explore!

## It crashed!

Please make an issue on https://github.com/alvarlagerlof/rsc-parser/issues/new and include the text content that the parser was unable to handle.


================================================
FILE: apply-version.sh
================================================
#!/bin/bash
set -e

# Step 1: Extract newVersion from ./packages/core/package.json
new_version=$(jq -r '.version' ./packages/core/package.json)
echo "Found new version: $new_version"

# Step 2: Update the version in manifest.json
jq --arg version "$new_version" '.version = $version' packages/chrome-extension/public/manifest.json > tmp.json && mv tmp.json packages/chrome-extension/public/manifest.json
echo "Applied new version: $_new_version"

# Step 3: Clean up
bun run prettier -w packages/chrome-extension/public/manifest.json


================================================
FILE: examples/embedded-example/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts


================================================
FILE: examples/embedded-example/.prettierignore
================================================
.next
.turbo

================================================
FILE: examples/embedded-example/CHANGELOG.md
================================================
# @rsc-parser/embedded-example

## 1.1.2

### Patch Changes

- c965537: Add "repository" to /embedded package.json
- Updated dependencies [c965537]
  - @rsc-parser/embedded@1.1.2

## 1.1.1

### Patch Changes

- e0b935a: Improve console chunk list rendering
- Updated dependencies [e0b935a]
  - @rsc-parser/embedded@1.1.1

## 1.1.0

### Minor Changes

- 6ce957a: Add support for rendering console chunks
- 83cbaaf: Link to debug info references

### Patch Changes

- 6d9815c: Improve readability when a tab has multiple panels
- Updated dependencies [6ce957a]
- Updated dependencies [83cbaaf]
- Updated dependencies [6d9815c]
  - @rsc-parser/embedded@1.1.0

## 1.0.0

### Major Changes

- 65231fc: Fix dev check in the Chrome extension

  BREAKING: `createFlightResponse` from @rsc-parser/react-client now takes a `__DEV__` parameter.

### Patch Changes

- 44cc2a8: Fix meter styling
- 1860787: Fixed a few react runtime errors
- Updated dependencies [44cc2a8]
- Updated dependencies [1860787]
- Updated dependencies [65231fc]
  - @rsc-parser/embedded@1.0.0

## 0.9.2

### Patch Changes

- 5fb463b: Specify "files" for @rsc-parser/react-client
- Updated dependencies [5fb463b]
  - @rsc-parser/embedded@0.9.2

## 0.9.1

### Patch Changes

- 71a3eac: Properly externalize react in the core and embedded packages
- Updated dependencies [71a3eac]
  - @rsc-parser/embedded@0.9.1

## 0.9.0

### Minor Changes

- 0f2a24a: Fix network graph max call stack error

### Patch Changes

- Updated dependencies [0f2a24a]
  - @rsc-parser/embedded@0.9.0

## 0.8.0

### Minor Changes

- 3db1a5f: Don't flip recording state when running triggerReadNextJsScriptTags
- 1478826: Add one error boundary per tab panel in RequestDetail

### Patch Changes

- Updated dependencies [3db1a5f]
- Updated dependencies [1478826]
  - @rsc-parser/embedded@0.8.0

## 0.7.0

### Minor Changes

- 531f06e: Update fork of ReactFlightClient.js
- 4bf3887: Make it possible to read Next.js script tags from the Chrome extension

### Patch Changes

- Updated dependencies [531f06e]
- Updated dependencies [4bf3887]
  - @rsc-parser/embedded@0.7.0

## 0.6.0

### Minor Changes

- 3f5a1c5: Make the "Headers" tab table column 1 min-size smaller, and make the `BottomPanel` default size larger
- 22cbbbf: Redesign `RequestTab` again
- f8836e5: Make request tab multiline by default

### Patch Changes

- 0002b9a: Add flex wrapping to the tabs in `RequestDetail`
- Updated dependencies [3f5a1c5]
- Updated dependencies [0002b9a]
- Updated dependencies [22cbbbf]
- Updated dependencies [f8836e5]
  - @rsc-parser/embedded@0.6.0

## 0.5.1

### Patch Changes

- 1c0b883: Fix `contentScript.ts` bundling to avoid ESM imports
- d3228c2: Remove debug console.log statements
- Updated dependencies [1c0b883]
- Updated dependencies [d3228c2]
  - @rsc-parser/embedded@0.5.1

## 0.5.0

### Minor Changes

- 44a478e: Create a Timings tab
- b07521b: Capture request and response headers
- 2aa4133: Refactor fetch patching and events
- c937a64: Rename Chrome extension root component from `App` to `RscDevtoolsExtension`
- 8ae76c5: Make the `BottomPanel` easier to resize
- 56627ae: Improve Headers tab design and add general information
- 4096674: Improve the `Raw` tab
- 2883dd1: Restructure @rsc-parser/chrome-extension files
- e155780: Create an `OverflowButton` for less important actions in `PanelLayout`
- a4eca6d: Add text on top of links in `RequestDetailTabNetwork`
- 65d0acd: Create `@rsc-parser/react-client`

### Patch Changes

- fe3446e: Make it possible to position the `BottomPanel` on the right
- 57c8a9b: Refactor `ViewerStreams` internals to allow a single list of tabs
- 095e40e: Ensure that `react` and `react-dom` are externalized
- a3a202c: Sync the version @rsc-parser/embedded-example with the other packages
- 800fced: Decrease the minimum panel sizes in `FlightResponseSelector`
- Updated dependencies [44a478e]
- Updated dependencies [b07521b]
- Updated dependencies [2aa4133]
- Updated dependencies [fe3446e]
- Updated dependencies [c937a64]
- Updated dependencies [8ae76c5]
- Updated dependencies [56627ae]
- Updated dependencies [4096674]
- Updated dependencies [57c8a9b]
- Updated dependencies [2883dd1]
- Updated dependencies [095e40e]
- Updated dependencies [a3a202c]
- Updated dependencies [800fced]
- Updated dependencies [e155780]
- Updated dependencies [a4eca6d]
- Updated dependencies [65d0acd]
  - @rsc-parser/embedded@0.5.0

## 0.18.2

### Patch Changes

- 5356856: Only show links for which there are nodes in FlightResponseTabNetwork
- Updated dependencies [5356856]
  - @rsc-parser/embedded@0.4.2

## 0.18.1

### Patch Changes

- e129d14: Specify files in @rsc-parser/core
- Updated dependencies [e129d14]
  - @rsc-parser/embedded@0.4.1

## 0.18.0

### Minor Changes

- 61a3d5a: Expose `unstable_Viewer`

### Patch Changes

- Updated dependencies [61a3d5a]
  - @rsc-parser/embedded@0.4.0

## 0.17.1

### Patch Changes

- 4542777: Fix Chrome Extension bundling
- e09465a: Add typecheck command
- 4f48e76: Add format command everywher
- ed20b59: Remove console logs
- Updated dependencies [4542777]
- Updated dependencies [e09465a]
- Updated dependencies [4f48e76]
- Updated dependencies [ed20b59]
  - @rsc-parser/embedded@0.3.1

## 0.17.0

### Minor Changes

- f313d13: Compile Chrome extension scripts using Vite
- ee03219: Move fetch patching code to @rsc-parser/core
- 4d0f42e: Added support for server actions

### Patch Changes

- 2cd0dd4: Export `unstable_createFlightResponse`
- 2836e57: Add a button to read Next.js payload script tags
- faa158f: Export `unstable_createFlightResponse`
- Updated dependencies [f313d13]
- Updated dependencies [2cd0dd4]
- Updated dependencies [2836e57]
- Updated dependencies [ee03219]
- Updated dependencies [faa158f]
- Updated dependencies [4d0f42e]
  - @rsc-parser/embedded@0.3.0

## 0.16.4

### Patch Changes

- 71e069c: Set a higher z-index for the BottomPanel when when open
- Updated dependencies [71e069c]
  - @rsc-parser/embedded@0.2.4

## 0.16.3

### Patch Changes

- f181ce8: Scope styles for RscDevtoolsPanel
- 39e168a: Stop wrapping <style> in <head> in RscDevtoolsPanel
- Updated dependencies [f181ce8]
- Updated dependencies [39e168a]
  - @rsc-parser/embedded@0.2.3

## 0.16.2

### Patch Changes

- 268463a: Make @rsc-parser/core dependency in @rsc-parser/embedded a dev dependency
- Updated dependencies [268463a]
  - @rsc-parser/embedded@0.2.2

## 0.16.1

### Patch Changes

- f7390f2: Make @rsc-parser/embedded non-private
- Updated dependencies [f7390f2]
  - @rsc-parser/embedded@0.2.1

## 0.16.0

### Minor Changes

- c4d4a03: Introduce @rsc-parser/embedded and @rsc-parser/embeded-example

### Patch Changes

- Updated dependencies [c4d4a03]
  - @rsc-parser/embedded@0.2.0


================================================
FILE: examples/embedded-example/LICENSE
================================================
MIT License

Copyright (c) 2023 Alvar Lagerlöf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: examples/embedded-example/README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


================================================
FILE: examples/embedded-example/app/action/page.tsx
================================================
import Link from 'next/link';

export default function ActionPage() {
  return (
    <>
      <Link href="/">Go to home page</Link>
      {/* @ts-expect-error Types */}
      <form action={action}>
        <input type="text" name="name" placeholder="Name" />
        <input type="submit" value="Submit" />
      </form>
    </>
  );
}

const action = async (formData: FormData) => {
  'use server';
  await new Promise((resolve) => setTimeout(resolve, 500));
  return formData;
};


================================================
FILE: examples/embedded-example/app/error/[name]/page.tsx
================================================
export default function Pokemon({
  params,
}: {
  params: Promise<{ name: string }>;
}) {
  // @ts-expect-error Incorrect on purpose
  const name = params.name;

  return <span>The pokemon is: {name}</span>;
}


================================================
FILE: examples/embedded-example/app/globals.css
================================================
@import 'tailwindcss';
@config '../tailwind.config.ts';


================================================
FILE: examples/embedded-example/app/layout.tsx
================================================
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import { RscDevtoolsPanel } from '@rsc-parser/embedded';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <main className="p-20">{children}</main>

        <RscDevtoolsPanel />
      </body>
    </html>
  );
}


================================================
FILE: examples/embedded-example/app/other/page.tsx
================================================
import Link from 'next/link';

export default function Other() {
  return (
    <>
      <p>Other page</p>
      <Link href="/">Go to home page</Link>
    </>
  );
}


================================================
FILE: examples/embedded-example/app/page.tsx
================================================
import Link from 'next/link';

export default function Home() {
  return (
    <>
      <p>Home page</p>
      <Link href="/other">Go to other page</Link>
      <br />
      <Link href="/action">Go to action page</Link>
      <br />
      <Link href="/suspense">Go to suspense page</Link>
      <br />
      <Link href="/error/foo">Go to error page</Link>
    </>
  );
}


================================================
FILE: examples/embedded-example/app/suspense/page.tsx
================================================
import Link from 'next/link';
import { Suspense } from 'react';

export default function SuspensePage() {
  return (
    <>
      <Link href="/">Go to home page</Link>
      <Suspense fallback="Loading...">
        <SlowComponent />
      </Suspense>
    </>
  );
}

async function SlowComponent() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return <div>Slow component</div>;
}


================================================
FILE: examples/embedded-example/eslint.config.js
================================================
// @ts-check

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default [
  {
    ignores: ['dist/**', '.next/**', '.turbo/**'],
  },
  ...[eslint.configs.recommended, ...tseslint.configs.recommended].map(
    (conf) => ({
      ...conf,
      files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
    }),
  ),
  {
    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
    rules: {
      curly: ['error', 'all'],
    },
  },
];


================================================
FILE: examples/embedded-example/next.config.ts
================================================
import { NextConfig } from 'next';

const nextConfig: NextConfig = {};

export default nextConfig;


================================================
FILE: examples/embedded-example/package.json
================================================
{
  "name": "@rsc-parser/embedded-example",
  "packageManager": "bun@1.3.3",
  "version": "1.1.2",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev --turbopack -p 4000",
    "build": "next build",
    "start": "next start",
    "lint": "eslint",
    "format": "prettier . --check --config ../../.prettierrc",
    "typecheck": "tsc --noEmit",
    "clean": "rm -rf .next"
  },
  "dependencies": {
    "@rsc-parser/embedded": "workspace:^",
    "next": "16.0.10",
    "react": "19.1.0",
    "react-dom": "19.1.0"
  },
  "devDependencies": {
    "@eslint/eslintrc": "3.3.1",
    "@eslint/js": "9.29.0",
    "@tailwindcss/postcss": "4.1.10",
    "@types/node": "24.0.1",
    "@types/react": "19.1.8",
    "@types/react-dom": "19.1.6",
    "eslint": "9.29.0",
    "eslint-config-next": "16.0.8",
    "postcss": "8.5.5",
    "tailwindcss": "4.1.10",
    "turbo": "2.5.4",
    "typescript": "5.8.3",
    "typescript-eslint": "8.47.0"
  },
  "resolutions": {
    "@types/react": "19.1.8",
    "@types/react-dom": "19.1.6"
  }
}


================================================
FILE: examples/embedded-example/postcss.config.cjs
================================================
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
  },
};


================================================
FILE: examples/embedded-example/tailwind.config.ts
================================================
import type { Config } from 'tailwindcss';

const config: Config = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [],
};
export default config;


================================================
FILE: examples/embedded-example/tsconfig.json
================================================
{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    },
    "target": "ES2017"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    "tailwind.config.ts",
    "tailwind.config.mjs"
  ],
  "exclude": ["node_modules"]
}


================================================
FILE: examples/embedded-example/turbo.json
================================================
{
  "$schema": "https://turbo.build/schema.json",
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"],
      "dependsOn": ["^build"]
    },
    "lint": {}
  }
}


================================================
FILE: examples/embedded-example/vercel.json
================================================
{
  "github": {
    "silent": true
  },
  "buildCommand": "turbo run build"
}


================================================
FILE: package.json
================================================
{
  "name": "rsc-parser",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/alvarlagerlof/rsc-parser",
  "author": "Alvar Lagerlöf <14835120+alvarlagerlof@users.noreply.github.com>",
  "license": "MIT",
  "private": true,
  "workspaces": [
    "packages/*",
    "examples/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "clean": "turbo run clean",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "typecheck": "turbo run typecheck",
    "format": "turbo run format",
    "ci": "turbo run ci",
    "dev": "turbo run dev",
    "version": "changeset version && bash ./apply-version.sh",
    "release": "changeset publish",
    "knip": "knip"
  },
  "packageManager": "bun@1.3.3",
  "devDependencies": {
    "@changesets/cli": "2.30.0",
    "knip": "5.88.1",
    "prettier": "3.8.1",
    "turbo": "2.8.21"
  }
}


================================================
FILE: packages/chrome-extension/.gitignore
================================================
**/*.trace
**/*.zip
**/*.tar.gz
**/*.tgz
**/*.log

package-lock.json
**/*.bun

# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

dist/**/*

vite.config.ts.timestamp*

================================================
FILE: packages/chrome-extension/.prettierignore
================================================
dist

================================================
FILE: packages/chrome-extension/CHANGELOG.md
================================================
# @rsc-parser/chrome-extension

## 1.1.2

### Patch Changes

- c965537: Add "repository" to /embedded package.json
- Updated dependencies [c965537]
  - @rsc-parser/core@1.1.2

## 1.1.1

### Patch Changes

- e0b935a: Improve console chunk list rendering
- Updated dependencies [e0b935a]
  - @rsc-parser/core@1.1.1

## 1.1.0

### Minor Changes

- 6ce957a: Add support for rendering console chunks
- 83cbaaf: Link to debug info references

### Patch Changes

- 6d9815c: Improve readability when a tab has multiple panels
- Updated dependencies [6ce957a]
- Updated dependencies [83cbaaf]
- Updated dependencies [6d9815c]
  - @rsc-parser/core@1.1.0

## 1.0.0

### Major Changes

- 65231fc: Fix dev check in the Chrome extension

  BREAKING: `createFlightResponse` from @rsc-parser/react-client now takes a `__DEV__` parameter.

### Patch Changes

- 44cc2a8: Fix meter styling
- 1860787: Fixed a few react runtime errors
- Updated dependencies [44cc2a8]
- Updated dependencies [1860787]
- Updated dependencies [65231fc]
  - @rsc-parser/core@1.0.0

## 0.9.2

### Patch Changes

- 5fb463b: Specify "files" for @rsc-parser/react-client
- Updated dependencies [5fb463b]
  - @rsc-parser/core@0.9.2

## 0.9.1

### Patch Changes

- 71a3eac: Properly externalize react in the core and embedded packages
- Updated dependencies [71a3eac]
  - @rsc-parser/core@0.9.1

## 0.9.0

### Minor Changes

- 0f2a24a: Fix network graph max call stack error

### Patch Changes

- Updated dependencies [0f2a24a]
  - @rsc-parser/core@0.9.0

## 0.8.0

### Minor Changes

- 3db1a5f: Don't flip recording state when running triggerReadNextJsScriptTags
- 1478826: Add one error boundary per tab panel in RequestDetail

### Patch Changes

- Updated dependencies [3db1a5f]
- Updated dependencies [1478826]
  - @rsc-parser/core@0.8.0

## 0.7.0

### Minor Changes

- 531f06e: Update fork of ReactFlightClient.js
- 4bf3887: Make it possible to read Next.js script tags from the Chrome extension

### Patch Changes

- Updated dependencies [531f06e]
- Updated dependencies [4bf3887]
  - @rsc-parser/core@0.7.0

## 0.6.0

### Minor Changes

- 3f5a1c5: Make the "Headers" tab table column 1 min-size smaller, and make the `BottomPanel` default size larger
- 22cbbbf: Redesign `RequestTab` again
- f8836e5: Make request tab multiline by default

### Patch Changes

- 0002b9a: Add flex wrapping to the tabs in `RequestDetail`
- Updated dependencies [3f5a1c5]
- Updated dependencies [0002b9a]
- Updated dependencies [22cbbbf]
- Updated dependencies [f8836e5]
  - @rsc-parser/core@0.6.0

## 0.5.1

### Patch Changes

- 1c0b883: Fix `contentScript.ts` bundling to avoid ESM imports
- d3228c2: Remove debug console.log statements
- Updated dependencies [1c0b883]
- Updated dependencies [d3228c2]
  - @rsc-parser/core@0.5.1

## 0.5.0

### Minor Changes

- 44a478e: Create a Timings tab
- b07521b: Capture request and response headers
- 2aa4133: Refactor fetch patching and events
- c937a64: Rename Chrome extension root component from `App` to `RscDevtoolsExtension`
- 8ae76c5: Make the `BottomPanel` easier to resize
- 56627ae: Improve Headers tab design and add general information
- 4096674: Improve the `Raw` tab
- 2883dd1: Restructure @rsc-parser/chrome-extension files
- e155780: Create an `OverflowButton` for less important actions in `PanelLayout`
- a4eca6d: Add text on top of links in `RequestDetailTabNetwork`
- 65d0acd: Create `@rsc-parser/react-client`

### Patch Changes

- fe3446e: Make it possible to position the `BottomPanel` on the right
- 57c8a9b: Refactor `ViewerStreams` internals to allow a single list of tabs
- 095e40e: Ensure that `react` and `react-dom` are externalized
- a3a202c: Sync the version @rsc-parser/embedded-example with the other packages
- 800fced: Decrease the minimum panel sizes in `FlightResponseSelector`
- Updated dependencies [44a478e]
- Updated dependencies [b07521b]
- Updated dependencies [2aa4133]
- Updated dependencies [fe3446e]
- Updated dependencies [c937a64]
- Updated dependencies [8ae76c5]
- Updated dependencies [56627ae]
- Updated dependencies [4096674]
- Updated dependencies [57c8a9b]
- Updated dependencies [2883dd1]
- Updated dependencies [095e40e]
- Updated dependencies [a3a202c]
- Updated dependencies [800fced]
- Updated dependencies [e155780]
- Updated dependencies [a4eca6d]
- Updated dependencies [65d0acd]
  - @rsc-parser/core@0.5.0

## 0.4.2

### Patch Changes

- 5356856: Only show links for which there are nodes in FlightResponseTabNetwork
- Updated dependencies [5356856]
  - @rsc-parser/core@0.4.2

## 0.4.1

### Patch Changes

- e129d14: Specify files in @rsc-parser/core
- Updated dependencies [e129d14]
  - @rsc-parser/core@0.4.1

## 0.4.0

### Minor Changes

- 61a3d5a: Expose `unstable_Viewer`

### Patch Changes

- Updated dependencies [61a3d5a]
  - @rsc-parser/core@0.4.0

## 0.3.1

### Patch Changes

- 4542777: Fix Chrome Extension bundling
- e09465a: Add typecheck command
- 4f48e76: Add format command everywher
- ed20b59: Remove console logs
- Updated dependencies [4542777]
- Updated dependencies [e09465a]
- Updated dependencies [4f48e76]
- Updated dependencies [ed20b59]
  - @rsc-parser/core@0.3.1

## 0.3.0

### Minor Changes

- f313d13: Compile Chrome extension scripts using Vite
- ee03219: Move fetch patching code to @rsc-parser/core
- 4d0f42e: Added support for server actions

### Patch Changes

- 2cd0dd4: Export `unstable_createFlightResponse`
- 2836e57: Add a button to read Next.js payload script tags
- faa158f: Export `unstable_createFlightResponse`
- Updated dependencies [f313d13]
- Updated dependencies [2cd0dd4]
- Updated dependencies [2836e57]
- Updated dependencies [ee03219]
- Updated dependencies [faa158f]
- Updated dependencies [4d0f42e]
  - @rsc-parser/core@0.3.0

## 0.2.4

### Patch Changes

- 71e069c: Set a higher z-index for the BottomPanel when when open
- Updated dependencies [71e069c]
  - @rsc-parser/core@0.2.4

## 0.2.3

### Patch Changes

- f181ce8: Scope styles for RscDevtoolsPanel
- 39e168a: Stop wrapping <style> in <head> in RscDevtoolsPanel
- Updated dependencies [f181ce8]
- Updated dependencies [39e168a]
  - @rsc-parser/core@0.2.3

## 0.2.2

### Patch Changes

- 268463a: Make @rsc-parser/core dependency in @rsc-parser/embedded a dev dependency
- Updated dependencies [268463a]
  - @rsc-parser/core@0.2.2

## 0.2.1

### Patch Changes

- f7390f2: Make @rsc-parser/embedded non-private
- Updated dependencies [f7390f2]
  - @rsc-parser/core@0.2.1

## 0.2.0

### Minor Changes

- cbfa10f: Move some UI previously defined in @rsc-parser/chrome-extension into @rsc-parser/core
- c4d4a03: Introduce @rsc-parser/embedded and @rsc-parser/embeded-example
- 583cf09: Create a `useRscMessages` hook

### Patch Changes

- Updated dependencies [cbfa10f]
- Updated dependencies [c4d4a03]
- Updated dependencies [583cf09]
  - @rsc-parser/core@0.2.0

## 0.1.15

### Patch Changes

- 38bff39: Use `chunk` name instead of `row`
- d621e61: Improved rendering for unknown chunks #771
- d621e61: Added more data to `originalValue` #772
- 1207d60: Add network graph tab to FlightResponse
- d621e61: Added support for debug info chunks #769
- Updated dependencies [38bff39]
- Updated dependencies [d621e61]
- Updated dependencies [d621e61]
- Updated dependencies [1207d60]
- Updated dependencies [d621e61]
  - @rsc-parser/core@0.1.15

## 0.1.14

### Patch Changes

- ecb36c4: Fix extension button color in light mode
- Updated dependencies [ecb36c4]
  - @rsc-parser/core@0.1.14

## 0.1.13

### Patch Changes

- f6ed105: Stop rendering the end time
- Updated dependencies [f6ed105]
  - @rsc-parser/core@0.1.13

## 0.1.12

### Patch Changes

- 8dede95: Made font sizes more consistent
- Updated dependencies [8dede95]
  - @rsc-parser/core@0.1.12

## 0.1.11

### Patch Changes

- 611207b: Integrate parser from the ReactFlightClient source
- c3240e7: Remove unuzed zod dependency
- Updated dependencies [611207b]
- Updated dependencies [c3240e7]
  - @rsc-parser/core@0.1.11

## 0.1.10

### Patch Changes

- 09b3e5e: Fixed parsing (react updated its format)
- Updated dependencies [09b3e5e]
  - @rsc-parser/core@0.1.10

## 0.1.9

### Patch Changes

- ab6c252: Fix zip path for release upload
- Updated dependencies [ab6c252]
  - @rsc-parser/core@0.1.9

## 0.1.8

### Patch Changes

- d77eb98: Add id
- Updated dependencies [d77eb98]
  - @rsc-parser/core@0.1.8

## 0.1.7

### Patch Changes

- 6050c00: Change publish logic
- Updated dependencies [6050c00]
  - @rsc-parser/core@0.1.7

## 0.1.6

### Patch Changes

- d32eda1: Test release
- Updated dependencies [d32eda1]
  - @rsc-parser/core@0.1.6

## 0.1.5

### Patch Changes

- 6593bb5: Don't run CI workflow on pushes to main
- Updated dependencies [6593bb5]
  - @rsc-parser/core@0.1.5

## 0.1.4

### Patch Changes

- b58d575: Test release

## 0.1.3

### Patch Changes

- 5ed8752: Continued setting up changesets
- Updated dependencies [5ed8752]
  - @rsc-parser/core@0.1.3

## 0.1.2

### Patch Changes

- 4113fa7: Test release

## 0.1.1

### Patch Changes

- Bugs fixed:
  - When navigating for the first time, the "load event would sometimes be triggered #396
  - Messages from all tabs are accepted by all devtools panels #151


================================================
FILE: packages/chrome-extension/LICENSE
================================================
MIT License

Copyright (c) 2023 Alvar Lagerlöf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: packages/chrome-extension/devtoolsPanel.html
================================================
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + TS</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap"
      rel="stylesheet"
    />
    <style>
      html {
        scrollbar-gutter: stable;
        background: rgb(241, 245, 249);
      }

      @media (prefers-color-scheme: dark) {
        html {
          background: rgb(15, 23, 42);
        }
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>


================================================
FILE: packages/chrome-extension/eslint.config.js
================================================
// @ts-check

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default [
  {
    ignores: ['dist/**', '.turbo/**', 'vite.config.ts.timestamp*'],
  },
  ...[eslint.configs.recommended, ...tseslint.configs.recommended].map(
    (conf) => ({
      ...conf,
      files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
    }),
  ),
  {
    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
    rules: {
      curly: ['error', 'all'],
    },
  },
];


================================================
FILE: packages/chrome-extension/package.json
================================================
{
  "name": "@rsc-parser/chrome-extension",
  "version": "1.1.2",
  "license": "MIT",
  "packageManager": "bun@1.3.3",
  "private": true,
  "dependencies": {
    "@rsc-parser/core": "workspace:*",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "react-error-boundary": "6.0.3"
  },
  "devDependencies": {
    "@ariakit/react": "0.4.24",
    "@eslint/js": "9.39.4",
    "@types/react": "19.2.14",
    "@types/react-dom": "19.2.3",
    "@typescript-eslint/eslint-plugin": "8.57.2",
    "@typescript-eslint/parser": "8.57.2",
    "@vitejs/plugin-react": "4.7.0",
    "autoprefixer": "10.4.27",
    "chrome-types": "0.1.425",
    "concurrently": "9.2.1",
    "esbuild": "0.27.4",
    "eslint": "9.39.4",
    "postcss": "8.5.8",
    "prettier": "3.8.1",
    "type-fest": "5.5.0",
    "typescript": "5.9.3",
    "typescript-eslint": "8.57.2",
    "vite": "7.3.1",
    "vite-plugin-html-config": "2.0.2"
  },
  "scripts": {
    "lint": "eslint",
    "format": "prettier . --check --config ../../.prettierrc",
    "typecheck": "tsc --noEmit",
    "dev": "vite build --mode development && vite dev",
    "build": "vite build && sed -i'.backup' 's| http://localhost:6020||g' dist/manifest.json && rm dist/manifest.json.backup",
    "clean": "rm -rf dist"
  },
  "type": "module"
}


================================================
FILE: packages/chrome-extension/public/devtoolsPage.html
================================================
<body>
  <script src="assets/devtoolsPage.js"></script>
</body>


================================================
FILE: packages/chrome-extension/public/manifest.json
================================================
{
  "name": "RSC Devtools",
  "version": "1.1.2",
  "description": "React Server Components network visualizer",
  "content_security_policy": {
    "extension_pages": "script-src 'self' http://localhost:6020; object-src 'self'"
  },
  "manifest_version": 3,
  "devtools_page": "devtoolsPage.html",
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["assets/contentScript.js"],
      "all_frames": true
    }
  ],
  "web_accessible_resources": [
    {
      "resources": ["assets/fetchPatcherInjector.js"],
      "matches": ["http://*/*", "https://*/*"]
    },
    {
      "resources": ["assets/readNextJsScriptTagsInjector.js"],
      "matches": ["http://*/*", "https://*/*"]
    }
  ]
}


================================================
FILE: packages/chrome-extension/src/RscDevtoolsExtension.tsx
================================================
import React, {
  startTransition,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from 'react';

import {
  ViewerStreams,
  ViewerStreamsEmptyState,
  PanelLayout,
  Logo,
  RecordButton,
  OverflowButton,
  copyEventsToClipboard,
} from '@rsc-parser/core';
import '@rsc-parser/core/style.css';
import {
  RscEvent,
  StartRecordingEvent,
  isEvent,
  isRscChunkEvent,
  isRscEvent,
  isStopRecordingEvent,
  ReadNextJsScriptTagsEvent,
} from '@rsc-parser/core/events';

export function RscDevtoolsExtension() {
  const {
    isRecording,
    startRecording,
    events,
    clearEvents,
    triggerReadNextJsScriptTags,
  } = useRscEvents();

  return (
    <PanelLayout
      header={
        <>
          <Logo variant="wide" />
          <RecordButton
            isRecording={isRecording}
            onClickRecord={startRecording}
          />
        </>
      }
      buttons={
        <>
          <OverflowButton
            menuItems={
              <>
                <button onClick={() => clearEvents()}>Clear events</button>
                {process.env.NODE_ENV === 'development' ? (
                  <button
                    onClick={() => {
                      copyEventsToClipboard({ events });
                    }}
                  >
                    Copy events to clipboard
                  </button>
                ) : null}
                <button
                  onClick={() => {
                    triggerReadNextJsScriptTags();
                  }}
                >
                  Read Next.js script tag payload
                </button>
              </>
            }
          />
        </>
      }
    >
      {events.length === 0 ? (
        <ViewerStreamsEmptyState />
      ) : (
        <ViewerStreams events={events} />
      )}
    </PanelLayout>
  );
}

function useRscEvents() {
  const [events, setEvents] = useState<RscEvent[]>([]);
  const [isRecording, setIsRecording] = useState(false);
  const tabId = useMemo(() => Date.now(), []);

  useEffect(() => {
    function handleEvent(
      request: unknown,
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      _sender: unknown,
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      _sendResponse: unknown,
    ) {
      if (!isEvent(request)) {
        return true;
      }

      // If the event is from a different tab, ignore it
      if (request.data.tabId !== tabId) {
        return true;
      }

      if (isStopRecordingEvent(request)) {
        setIsRecording(false);
        setEvents([]);
        return true;
      }

      if (!isRscEvent(request)) {
        return true;
      }

      // It's possible that this lookup will miss a duplicated event if another
      // one is being added at the same time. I haven't seen this happen in practice.
      if (
        isRscChunkEvent(request) &&
        events
          .filter(isRscChunkEvent)
          .some((item) =>
            arraysEqual(item.data.chunkValue, request.data.chunkValue),
          )
      ) {
        return true;
      }

      startTransition(() => {
        setEvents((previous) => [...previous, request]);
      });

      return true;
    }

    chrome.runtime.onMessage.addListener(handleEvent);

    return () => {
      chrome.runtime.onMessage.removeListener(handleEvent);
    };
  }, []);

  const startRecording = useCallback(() => {
    setIsRecording(true);

    chrome.tabs.sendMessage(chrome.devtools.inspectedWindow.tabId, {
      type: 'START_RECORDING',
      data: {
        tabId: tabId,
      },
    } satisfies StartRecordingEvent);
  }, [tabId]);

  const clearEvents = useCallback(() => {
    setEvents([]);
  }, []);

  const triggerReadNextJsScriptTags = useCallback(() => {
    chrome.tabs.sendMessage(chrome.devtools.inspectedWindow.tabId, {
      type: 'READ_NEXT_JS_SCRIPT_TAGS',
      data: {
        tabId: tabId,
      },
    } satisfies ReadNextJsScriptTagsEvent);
  }, []);

  return {
    isRecording,
    startRecording,
    events,
    clearEvents,
    triggerReadNextJsScriptTags,
  };
}

function arraysEqual(a: unknown[], b: unknown[]) {
  if (a === b) {
    return true;
  }
  if (a == null || b == null) {
    return false;
  }
  if (a.length !== b.length) {
    return false;
  }

  // If you don't care about the order of the elements inside
  // the array, you should sort both arrays here.
  // Please note that calling sort on an array will modify that array.
  // you might want to clone your array first.

  for (let i = 0; i < a.length; ++i) {
    if (a[i] !== b[i]) {
      return false;
    }
  }
  return true;
}


================================================
FILE: packages/chrome-extension/src/assets/contentScript.ts
================================================
import {
  type RscEvent,
  type StopRecordingEvent,
} from '@rsc-parser/core/events';
// import { RscEvent, isRscEvent } from "@rsc-parser/core/events";
// import {
//   StopRecordingEvent,
//   isStartRecordingEvent,
// } from "@rsc-parser/core/events";

// TODO: Find a way that these can be imported that makes them bundled
// into the output file without using ESM imports
function isStartRecordingEvent(event: unknown) {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'START_RECORDING'
  );
}
function isRscRequestEvent(event: unknown) {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'RSC_REQUEST'
  );
}
function isRscResponseEvent(event: unknown) {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'RSC_RESPONSE'
  );
}
function isRscChunkEvent(event: unknown) {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'RSC_CHUNK'
  );
}
export function isReadNextJsScriptTagsEvent(event: unknown) {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'READ_NEXT_JS_SCRIPT_TAGS'
  );
}
function isRscEvent(event: unknown) {
  return (
    isRscRequestEvent(event) ||
    isRscResponseEvent(event) ||
    isRscChunkEvent(event)
  );
}

/**
 * injectScript - Inject internal script to available access to the `window`
 *
 * @param  {type} file_path Local path of the internal script.
 * @param  {type} tag The tag as string, where the script will be append (default: 'body').
 * @see    {@link http://stackoverflow.com/questions/20499994/access-window-variable-from-content-script}
 */
function injectScript(file_path: string, tag: string) {
  const node = document.getElementsByTagName(tag)[0];
  const script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('src', file_path);
  node.appendChild(script);
}

// This is used in the devtools panel to only accept messages from the current tab
let tabId: number | undefined = undefined;

// Only inject the fetch patch script when the START_RECORDING evebt
// is received from the devtools panel

chrome.runtime.onMessage.addListener(function (request) {
  if (isStartRecordingEvent(request)) {
    // Store the tabId so that the devtools panel can filter messages to
    // only show the ones from the current tab
    tabId = request.data.tabId;

    injectScript(
      chrome.runtime.getURL('assets/fetchPatcherInjector.js'),
      'body',
    );
  }

  if (isReadNextJsScriptTagsEvent(request)) {
    // Store the tabId so that the devtools panel can filter messages to
    // only show the ones from the current tab
    tabId = request.data.tabId;
    injectScript(
      chrome.runtime.getURL('assets/readNextJsScriptTagsInjector.js'),
      'body',
    );
  }

  return true;
});

// This code passes along events from fetchPatcherInjector
// and readNextJsScriptTagsInjector to the devtools panel
window.addEventListener(
  'message',
  function (event) {
    // We only accept events from this window to itself [i.e. not from any iframes]
    if (event.source != window) {
      return;
    }

    if (typeof tabId !== 'number') {
      return;
    }

    if (isRscEvent(event.data)) {
      const baseEvent = event.data;
      baseEvent.data.tabId = tabId;

      chrome.runtime.sendMessage(baseEvent satisfies RscEvent);
    }
  },
  false,
);

// When the content script is unloaded (like for a refresh), send a message to the devtools panel to stop recording
window.addEventListener('beforeunload', () => {
  if (!tabId) {
    return;
  }

  chrome.runtime.sendMessage({
    type: 'STOP_RECORDING',
    data: { tabId },
  } satisfies StopRecordingEvent);
});


================================================
FILE: packages/chrome-extension/src/assets/devtoolsPage.ts
================================================
function handleShown() {
  //console.log("panel is being shown");
}

function handleHidden() {
  //console.log("panel is being hidden");
}

chrome.devtools.panels.create(
  'RSC Devtools',
  '',
  './devtoolsPanel.html',
  function (panel) {
    // console.log("Hello! from panel create");
    panel.onShown.addListener(handleShown);
    panel.onHidden.addListener(handleHidden);
  },
);


================================================
FILE: packages/chrome-extension/src/assets/fetchPatcherInjector.ts
================================================
import { fetchPatcher } from '@rsc-parser/core/fetchPatcher';

fetchPatcher({
  onRscEvent: (event) => {
    // Forward the message so that the content script can pick it up
    window.postMessage(event, '*');
  },
});


================================================
FILE: packages/chrome-extension/src/assets/readNextJsScriptTagsInjector.ts
================================================
import { readNextJsScriptTags } from '@rsc-parser/core/readNextJsScriptTags';

(() => {
  const events = readNextJsScriptTags();

  if (events) {
    for (const event of events) {
      window.postMessage(event, '*');
    }
  }
})();


================================================
FILE: packages/chrome-extension/src/dev/reactPreamble.ts
================================================
// @ts-expect-error Vite special sauce
import RefreshRuntime from 'http://localhost:6020/@react-refresh';
RefreshRuntime.injectIntoGlobalHook(window);
// @ts-expect-error Vite special sauce
window.$RefreshReg$ = () => {};
// @ts-expect-error Vite special sauce
window.$RefreshSig$ = () => (type) => type;
// @ts-expect-error Vite special sauce
window.__vite_plugin_react_preamble_installed__ = true;


================================================
FILE: packages/chrome-extension/src/main.tsx
================================================
import 'vite/modulepreload-polyfill';

import React from 'react';
import ReactDOM from 'react-dom/client';
import { RscDevtoolsExtension } from './RscDevtoolsExtension.jsx';

try {
  if (typeof process === 'undefined') {
    process = {
      // @ts-expect-error Overriding process is meant to be done
      env: 'development',
    };
  }
} catch (error) {
  console.log('error', error);
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RscDevtoolsExtension />
  </React.StrictMode>,
);


================================================
FILE: packages/chrome-extension/src/vite-env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: packages/chrome-extension/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "types": ["chrome-types"]
  },
  "include": ["**/*.ts", "**/*.tsx", "jest.config.js"],
  "exclude": ["node_modules", "vite.config.ts.timestamp*"]
}


================================================
FILE: packages/chrome-extension/turbo.json
================================================
{
  "$schema": "https://turbo.build/schema.json",
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": ["dist/**"],
      "inputs": [
        "src/**",
        "vite.config.ts",
        "tsconfig.json",
        "postcss.config.cjs",
        "tailwind.config.cjs"
      ]
    }
  }
}


================================================
FILE: packages/chrome-extension/vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import htmlPlugin, { Options, ScriptTag } from 'vite-plugin-html-config';
import { resolve } from 'path';

const dev = {
  headScripts: [
    {
      src: 'http://localhost:6020/src/dev/reactPreamble.ts',
      type: 'module',
    } as ScriptTag,
    {
      src: 'http://localhost:6020/@vite/client',
      type: 'module',
    } as ScriptTag,
    {
      src: 'http://localhost:6020/src/main.tsx',
      type: 'module',
    } as ScriptTag,
  ],
} satisfies Options;

const build = {
  headScripts: [
    {
      async: false,
      src: './src/main.tsx',
      type: 'module',
    } as ScriptTag,
  ],
} satisfies Options;

export default defineConfig(({ mode }) => {
  if (mode === 'development') {
    return {
      // @ts-expect-error TODO: Fix type
      plugins: [react(), htmlPlugin(dev)],
      base: '',
      build: {
        outDir: './dist',
        minify: false,
        rollupOptions: {
          input: {
            main: resolve(__dirname, 'devtoolsPanel.html'),
            fetchPatcherInjector: resolve(
              __dirname,
              'src/assets/fetchPatcherInjector.ts',
            ),
            readNextJsScriptTagsInjector: resolve(
              __dirname,
              'src/assets/readNextJsScriptTagsInjector.ts',
            ),
            contentScript: resolve(__dirname, 'src/assets/contentScript.ts'),
            devtoolsPage: resolve(__dirname, 'src/assets/devtoolsPage.ts'),
          },
          output: {
            entryFileNames: `assets/[name].js`,
            chunkFileNames: `assets/[name].js`,
            assetFileNames: `assets/[name].[ext]`,
          },
        },
        sourcemap: 'inline',
      },
      server: {
        port: 6020,
      },
    };
  }

  return {
    // @ts-expect-error TODO: Fix type
    plugins: [react(), htmlPlugin(build)],
    base: '',
    build: {
      outDir: './dist',
      minify: false,
      rollupOptions: {
        input: {
          main: resolve(__dirname, 'devtoolsPanel.html'),
          fetchPatcherInjector: resolve(
            __dirname,
            'src/assets/fetchPatcherInjector.ts',
          ),
          readNextJsScriptTagsInjector: resolve(
            __dirname,
            'src/assets/readNextJsScriptTagsInjector.ts',
          ),
          contentScript: resolve(__dirname, 'src/assets/contentScript.ts'),
          devtoolsPage: resolve(__dirname, 'src/assets/devtoolsPage.ts'),
        },
        output: {
          entryFileNames: `assets/[name].js`,
          chunkFileNames: `assets/[name].js`,
          assetFileNames: `assets/[name].[ext]`,
        },
      },
      sourcemap: false,
    },
    server: {
      port: 6020,
    },
  };
});


================================================
FILE: packages/core/.gitignore
================================================
/node_modules
dist
vite.config.ts.timestamp*

================================================
FILE: packages/core/.prettierignore
================================================
vite.config.ts.timestamp*
src/example-data
dist

================================================
FILE: packages/core/CHANGELOG.md
================================================
# @rsc-parser/core

## 1.1.2

### Patch Changes

- c965537: Add "repository" to /embedded package.json
- Updated dependencies [c965537]
  - @rsc-parser/react-client@1.1.2

## 1.1.1

### Patch Changes

- e0b935a: Improve console chunk list rendering
- Updated dependencies [e0b935a]
  - @rsc-parser/react-client@1.1.1

## 1.1.0

### Minor Changes

- 6ce957a: Add support for rendering console chunks
- 83cbaaf: Link to debug info references

### Patch Changes

- 6d9815c: Improve readability when a tab has multiple panels
- Updated dependencies [6ce957a]
- Updated dependencies [83cbaaf]
- Updated dependencies [6d9815c]
  - @rsc-parser/react-client@1.1.0

## 1.0.0

### Major Changes

- 65231fc: Fix dev check in the Chrome extension

  BREAKING: `createFlightResponse` from @rsc-parser/react-client now takes a `__DEV__` parameter.

### Patch Changes

- 44cc2a8: Fix meter styling
- 1860787: Fixed a few react runtime errors
- Updated dependencies [44cc2a8]
- Updated dependencies [1860787]
- Updated dependencies [65231fc]
  - @rsc-parser/react-client@1.0.0

## 0.9.2

### Patch Changes

- 5fb463b: Specify "files" for @rsc-parser/react-client
- Updated dependencies [5fb463b]
  - @rsc-parser/react-client@0.9.2

## 0.9.1

### Patch Changes

- 71a3eac: Properly externalize react in the core and embedded packages
- Updated dependencies [71a3eac]
  - @rsc-parser/react-client@0.9.1

## 0.9.0

### Minor Changes

- 0f2a24a: Fix network graph max call stack error

### Patch Changes

- Updated dependencies [0f2a24a]
  - @rsc-parser/react-client@0.9.0

## 0.8.0

### Minor Changes

- 3db1a5f: Don't flip recording state when running triggerReadNextJsScriptTags
- 1478826: Add one error boundary per tab panel in RequestDetail

### Patch Changes

- Updated dependencies [3db1a5f]
- Updated dependencies [1478826]
  - @rsc-parser/react-client@0.8.0

## 0.7.0

### Minor Changes

- 531f06e: Update fork of ReactFlightClient.js
- 4bf3887: Make it possible to read Next.js script tags from the Chrome extension

### Patch Changes

- Updated dependencies [531f06e]
- Updated dependencies [4bf3887]
  - @rsc-parser/react-client@0.7.0

## 0.6.0

### Minor Changes

- 3f5a1c5: Make the "Headers" tab table column 1 min-size smaller, and make the `BottomPanel` default size larger
- 22cbbbf: Redesign `RequestTab` again
- f8836e5: Make request tab multiline by default

### Patch Changes

- 0002b9a: Add flex wrapping to the tabs in `RequestDetail`
- Updated dependencies [3f5a1c5]
- Updated dependencies [0002b9a]
- Updated dependencies [22cbbbf]
- Updated dependencies [f8836e5]
  - @rsc-parser/react-client@0.6.0

## 0.5.1

### Patch Changes

- 1c0b883: Fix `contentScript.ts` bundling to avoid ESM imports
- d3228c2: Remove debug console.log statements
- Updated dependencies [1c0b883]
- Updated dependencies [d3228c2]
  - @rsc-parser/react-client@0.5.1

## 0.5.0

### Minor Changes

- 44a478e: Create a Timings tab
- b07521b: Capture request and response headers
- 2aa4133: Refactor fetch patching and events
- c937a64: Rename Chrome extension root component from `App` to `RscDevtoolsExtension`
- 8ae76c5: Make the `BottomPanel` easier to resize
- 56627ae: Improve Headers tab design and add general information
- 4096674: Improve the `Raw` tab
- 2883dd1: Restructure @rsc-parser/chrome-extension files
- e155780: Create an `OverflowButton` for less important actions in `PanelLayout`
- a4eca6d: Add text on top of links in `RequestDetailTabNetwork`
- 65d0acd: Create `@rsc-parser/react-client`

### Patch Changes

- fe3446e: Make it possible to position the `BottomPanel` on the right
- 57c8a9b: Refactor `ViewerStreams` internals to allow a single list of tabs
- 095e40e: Ensure that `react` and `react-dom` are externalized
- a3a202c: Sync the version @rsc-parser/embedded-example with the other packages
- 800fced: Decrease the minimum panel sizes in `FlightResponseSelector`
- Updated dependencies [44a478e]
- Updated dependencies [56627ae]
- Updated dependencies [4096674]
- Updated dependencies [57c8a9b]
- Updated dependencies [a4eca6d]
- Updated dependencies [65d0acd]
  - @rsc-parser/react-client@0.5.0

## 0.4.2

### Patch Changes

- 5356856: Only show links for which there are nodes in FlightResponseTabNetwork

## 0.4.1

### Patch Changes

- e129d14: Specify files in @rsc-parser/core

## 0.4.0

### Minor Changes

- 61a3d5a: Expose `unstable_Viewer`

## 0.3.1

### Patch Changes

- 4542777: Fix Chrome Extension bundling
- e09465a: Add typecheck command
- 4f48e76: Add format command everywher
- ed20b59: Remove console logs

## 0.3.0

### Minor Changes

- f313d13: Compile Chrome extension scripts using Vite
- ee03219: Move fetch patching code to @rsc-parser/core
- 4d0f42e: Added support for server actions

### Patch Changes

- 2cd0dd4: Export `unstable_createFlightResponse`
- 2836e57: Add a button to read Next.js payload script tags
- faa158f: Export `unstable_createFlightResponse`

## 0.2.4

### Patch Changes

- 71e069c: Set a higher z-index for the BottomPanel when when open

## 0.2.3

### Patch Changes

- f181ce8: Scope styles for RscDevtoolsPanel
- 39e168a: Stop wrapping <style> in <head> in RscDevtoolsPanel

## 0.2.2

### Patch Changes

- 268463a: Make @rsc-parser/core dependency in @rsc-parser/embedded a dev dependency

## 0.2.1

### Patch Changes

- f7390f2: Make @rsc-parser/embedded non-private

## 0.2.0

### Minor Changes

- cbfa10f: Move some UI previously defined in @rsc-parser/chrome-extension into @rsc-parser/core
- c4d4a03: Introduce @rsc-parser/embedded and @rsc-parser/embeded-example
- 583cf09: Create a `useRscMessages` hook

## 0.1.15

### Patch Changes

- 38bff39: Use `chunk` name instead of `row`
- d621e61: Improved rendering for unknown chunks #771
- d621e61: Added more data to `originalValue` #772
- 1207d60: Add network graph tab to FlightResponse
- d621e61: Added support for debug info chunks #769

## 0.1.14

### Patch Changes

- ecb36c4: Fix extension button color in light mode

## 0.1.13

### Patch Changes

- f6ed105: Stop rendering the end time

## 0.1.12

### Patch Changes

- 8dede95: Made font sizes more consistent

## 0.1.11

### Patch Changes

- 611207b: Integrate parser from the ReactFlightClient source
- c3240e7: Remove unuzed zod dependency

## 0.1.10

### Patch Changes

- 09b3e5e: Fixed parsing (react updated its format)

## 0.1.9

### Patch Changes

- ab6c252: Fix zip path for release upload

## 0.1.8

### Patch Changes

- d77eb98: Add id

## 0.1.7

### Patch Changes

- 6050c00: Change publish logic

## 0.1.6

### Patch Changes

- d32eda1: Test release

## 0.1.5

### Patch Changes

- 6593bb5: Don't run CI workflow on pushes to main

## 0.1.3

### Patch Changes

- 5ed8752: Continued setting up changesets


================================================
FILE: packages/core/LICENSE
================================================
MIT License

Copyright (c) 2023 Alvar Lagerlöf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: packages/core/README.md
================================================
# @rsc-parser/core

> [!WARNING]
> This package is published on npm as `@rsc-parser/core` but its api cannot be considered stable. For now, there will be no guarantees about API stability. See `@rsc-parser/embedded` for a more stable package that you can use in your projects.


================================================
FILE: packages/core/eslint.config.js
================================================
// @ts-check

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default [
  {
    ignores: [
      'dist/**',
      'storybook-static/**',
      '.turbo/**',
      'vite.config.ts.timestamp*',
    ],
  },
  ...[eslint.configs.recommended, ...tseslint.configs.recommended].map(
    (conf) => ({
      ...conf,
      files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
    }),
  ),
  {
    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
    rules: {
      curly: ['error', 'all'],
    },
  },
];


================================================
FILE: packages/core/jest.config.cjs
================================================
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        //the content you'd placed at "global"
        tsconfig: './tsconfig.test.json',
      },
    ],
  },
};


================================================
FILE: packages/core/jest.setup.js
================================================
import('@testing-library/jest-dom');


================================================
FILE: packages/core/package.json
================================================
{
  "name": "@rsc-parser/core",
  "version": "1.1.2",
  "packageManager": "bun@1.3.3",
  "author": "Alvar Lagerlöf",
  "license": "MIT",
  "scripts": {
    "build": "vite build && bunx @tailwindcss/cli -o dist/style.css --minify",
    "dev": "concurrently  \"vite build --watch  --mode development\" \"bunx @tailwindcss/cli -o dist/style.css --minify --watch\"",
    "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules node_modules/jest/bin/jest.js --passWithNoTests",
    "test:dev": "bun run test --watch",
    "lint": "eslint",
    "format": "prettier . --check --config ../../.prettierrc",
    "typecheck": "tsc --noEmit",
    "clean": "rm -rf dist"
  },
  "files": [
    "package.json",
    "dist"
  ],
  "type": "module",
  "style": "./dist/style.css",
  "exports": {
    ".": {
      "import": "./dist/main.js",
      "types": "./dist/main.d.ts"
    },
    "./style.css": {
      "import": "./dist/style.css"
    },
    "./fetchPatcher": {
      "import": "./dist/fetchPatcher.js",
      "types": "./dist/fetchPatcher.d.ts"
    },
    "./readNextJsScriptTags": {
      "import": "./dist/readNextJsScriptTags.js",
      "types": "./dist/readNextJsScriptTags.d.ts"
    },
    "./events": {
      "import": "./dist/events.js",
      "types": "./dist/events.d.ts"
    }
  },
  "dependencies": {
    "@ariakit/react": "0.4.24",
    "@rsc-parser/react-client": "workspace:^",
    "d3": "7.9.0",
    "react-error-boundary": "6.0.3",
    "react-resizable-panels": "3.0.6"
  },
  "devDependencies": {
    "@eslint/eslintrc": "3.3.5",
    "@eslint/js": "9.39.4",
    "@storybook/react-vite": "10.3.3",
    "@tailwindcss/cli": "4.2.2",
    "@tailwindcss/postcss": "4.2.2",
    "@testing-library/jest-dom": "6.9.1",
    "@testing-library/react": "16.3.2",
    "@types/d3": "7.4.3",
    "@types/d3-drag": "3.0.7",
    "@types/jest": "30.0.0",
    "@types/react": "19.2.14",
    "@types/react-dom": "19.2.3",
    "@typescript-eslint/eslint-plugin": "8.57.2",
    "@typescript-eslint/parser": "8.57.2",
    "@vitejs/plugin-react": "4.7.0",
    "@vitejs/plugin-react-swc": "3.11.0",
    "concurrently": "9.2.1",
    "eslint": "9.39.4",
    "jest": "30.3.0",
    "jest-environment-jsdom": "30.3.0",
    "postcss": "8.5.8",
    "prettier": "3.8.1",
    "tailwindcss": "4.2.2",
    "ts-jest": "29.4.6",
    "typescript": "5.9.3",
    "typescript-eslint": "8.57.2",
    "vite": "7.3.1",
    "vite-plugin-dts": "4.5.4"
  },
  "peerDependencies": {
    "react": "19.2.4",
    "react-dom": "19.2.4"
  }
}


================================================
FILE: packages/core/postcss.config.cjs
================================================
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
  },
};


================================================
FILE: packages/core/src/color.ts
================================================
function random(seed: number) {
  const x = Math.sin(seed++) * 10000;
  return x - Math.floor(x);
}

export function getColorForFetch(requestId: string) {
  let number = 0;

  for (let i = 0; i < requestId.length; i++) {
    number += requestId.charCodeAt(i);
  }

  return `oklch(80% 0.15 ${random(number) * 360})`;
}


================================================
FILE: packages/core/src/components/BottomPanel.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { BottomPanel } from './BottomPanel';

const meta: Meta<typeof BottomPanel> = {
  component: BottomPanel,
};

export default meta;
type Story = StoryObj<typeof BottomPanel>;

export const example: Story = {
  args: {
    openButton: 'RSC',
    isOpen: true,
    children: 'Panel content',
    position: 'bottom',
  },
};


================================================
FILE: packages/core/src/components/BottomPanel.tsx
================================================
import React, { ReactNode, useState } from 'react';
import { Logo } from './Logo';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { IconButton } from './IconButton';

export function BottomPanel({
  openButton,
  children,
  isOpen,
  position,
}: {
  openButton: ReactNode;
  children: ReactNode;
  isOpen: boolean;
  position: 'bottom' | 'right';
}) {
  const [isDragging, setIsDragging] = useState(false);

  if (isOpen) {
    return (
      <PanelGroup
        direction={position === 'bottom' ? 'vertical' : 'horizontal'}
        // The `pointer-events-none` class is need to be able to click on the content underneath,
        // but if it's applied while dragging, the drag handler looses track very easily.
        className={`fixed left-0 top-0 size-full z-1000 ${isDragging ? '' : 'pointer-events-none'}`}
      >
        <Panel order={1} defaultSize={55} />
        <PanelResizeHandle
          className={`pointer-events-auto bg-slate-300 dark:bg-slate-700 ${position === 'bottom' ? 'h-3 w-full' : 'h-full w-3'}`}
          onDragging={(isDragging) => {
            setIsDragging(isDragging);
          }}
        />
        <Panel order={2} maxSize={75} minSize={20} defaultSize={45}>
          <div className="pointer-events-auto size-full overflow-y-auto bg-slate-100 scrollbar-gutter-stable dark:bg-slate-900">
            {children}
          </div>
        </Panel>
      </PanelGroup>
    );
  }

  return (
    <div className="fixed bottom-[20px] right-[80px] z-1000 flex size-[40px]">
      {openButton}
    </div>
  );
}

export function BottomPanelCloseButton({
  onClickClose,
}: {
  onClickClose: () => void;
}) {
  return (
    <IconButton onClick={onClickClose}>
      <svg version="1.1" viewBox="0 0 24 24" className="size-full">
        <title>Close</title>
        <path
          d="M18 18L12 12M12 12L6 6M12 12L18 6M12 12L6 18"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
      </svg>
    </IconButton>
  );
}

export function BottomPanelOpenButton({
  onClickOpen,
}: {
  onClickOpen: () => void;
}) {
  return (
    <button
      onClick={onClickOpen}
      className="size-10 rounded-full bg-slate-300 dark:bg-slate-700"
    >
      <Logo variant="small" />
    </button>
  );
}

export function BottomPanelPositionSwitchButton({
  currentPosition,
  setCurrentPosition,
}: {
  currentPosition: 'bottom' | 'right';
  setCurrentPosition: (position: 'bottom' | 'right') => void;
}) {
  return (
    <IconButton
      onClick={() => {
        setCurrentPosition(currentPosition === 'bottom' ? 'right' : 'bottom');
      }}
    >
      {currentPosition === 'bottom' ? (
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="800px"
          height="800px"
          viewBox="0 0 24 24"
          fill="currentColor"
          className="size-full"
        >
          <title>
            {currentPosition === 'bottom'
              ? 'Position to the right'
              : 'Position on the bottom'}
          </title>
          <path d="M19 3H5c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2h14c1.103 0 2-.897 2-2V5c0-1.103-.897-2-2-2zM5 5h9v14H5V5zm11 14V5h3l.002 14H16z" />
        </svg>
      ) : (
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="800px"
          height="800px"
          viewBox="0 0 24 24"
          fill="currentColor"
          className="size-full"
        >
          <path fill="none" d="M5 16h14.002v3H5zM5 5h14v9H5z" />
          <path d="M19 3H5c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2h14c1.103 0 2-.897 2-2V5c0-1.103-.897-2-2-2zm0 2 .001 9H5V5h14zM5 19v-3h14.002v3H5z" />
        </svg>
      )}
    </IconButton>
  );
}


================================================
FILE: packages/core/src/components/EndTimeContext.tsx
================================================
import React from 'react';
import {
  ReactNode,
  createContext,
  useContext,
  useEffect,
  useState,
  useTransition,
} from 'react';

const EndTimeContext = createContext<{
  endTime: number;
  visibleEndTime: number;
  changeEndTime: (value: number) => void;
  isPending: boolean;
}>({
  endTime: Infinity,
  visibleEndTime: Infinity,
  changeEndTime: () => {},
  isPending: false,
});

export function EndTimeProvider({
  maxEndTime,
  children,
}: {
  maxEndTime: number;
  children: ReactNode;
}) {
  const [endTime, setEndTime] = useState(Infinity);
  const [visibleEndTime, setVisibleEndTime] = useState(endTime);
  const [isPending, startTransition] = useTransition();

  const changeEndTime = (value: number) => {
    setVisibleEndTime(value);
    startTransition(() => {
      setEndTime(value);
    });
  };

  useEffect(() => {
    if (endTime !== maxEndTime) {
      changeEndTime(maxEndTime);
    }
  }, [maxEndTime]);

  return (
    <EndTimeContext.Provider
      value={{
        endTime,
        visibleEndTime,
        changeEndTime,
        isPending,
      }}
    >
      <div>{children}</div>
    </EndTimeContext.Provider>
  );
}

export function useEndTime() {
  const context = useContext(EndTimeContext);

  if (context === undefined) {
    throw new Error('useEndTime must be used within a EndTimeContext.Provider');
  }

  return context;
}


================================================
FILE: packages/core/src/components/FlightResponseChunkConsole.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { FlightResponseChunkConsole } from './FlightResponseChunkConsole';

const meta: Meta<typeof FlightResponseChunkConsole> = {
  component: FlightResponseChunkConsole,
};

export default meta;
type Story = StoryObj<typeof FlightResponseChunkConsole>;

export const WarnExample: Story = {
  args: {
    data: {
      methodName: 'warn',
      stackTrace: [
        [
          'Pokemon',
          '/Users/alvar/Code/alvarlagerlof/rsc-parser/examples/embedded-example/.next/server/chunks/ssr/_a0bf3aeb._.js',
          34,
          25,
        ],
      ],
      owner: {
        $$type: 'reference',
        id: 'b',
        identifier: '',
        type: 'Reference',
      },
      env: 'Server',
      args: [
        {
          $$type: 'reference',
          id: 'c',
          identifier: '',
          type: 'Error',
        },
      ],
    },
  },
};


================================================
FILE: packages/core/src/components/FlightResponseChunkConsole.tsx
================================================
import React from 'react';
import { ConsoleChunk } from '@rsc-parser/react-client';
import { FlightResponseChunkModel } from './FlightResponseChunkModel';

export function FlightResponseChunkConsole({
  data,
  onClickID,
}: {
  data: ConsoleChunk['value'];
  onClickID: (id: string) => void;
}) {
  return (
    <div className="flex flex-col gap-5">
      <div className="flex flex-row gap-1 items-center">
        <div className="bg-slate-300 px-2 py-1 rounded-full inline dark:bg-slate-700">
          {data.methodName.toUpperCase()}
        </div>
        <div className="bg-slate-300 px-2 py-1 rounded-full inline dark:bg-slate-700">
          {data.env}
        </div>
      </div>

      <div className="flex flex-col gap-1">
        <div className="font-semibold">Stack trace</div>
        <ul className="flex flex-col gap-2 list-disc">
          {data.stackTrace.map(
            ([functionName, fileName, lineNumber, columnNumber]) => {
              return (
                <li
                  className="ml-3.5"
                  key={JSON.stringify([
                    functionName,
                    fileName,
                    lineNumber,
                    columnNumber,
                  ])}
                >
                  <div>{functionName}</div>
                  <div className="text-slate-500 dark:text-slate-300">
                    {fileName} ({lineNumber}:{columnNumber})
                  </div>
                </li>
              );
            },
          )}
        </ul>
      </div>

      <div className="flex flex-col gap-1">
        <div className="font-semibold">Args</div>
        <ul className="flex flex-col gap-2 list-disc">
          {data.args.map((arg) => {
            return (
              <li className="ml-3.5">
                <FlightResponseChunkModel
                  key={JSON.stringify(arg)}
                  data={arg}
                  onClickID={onClickID}
                />
              </li>
            );
          })}
        </ul>
      </div>

      <div className="flex flex-col gap-1">
        <div className="font-semibold">Owner</div>
        <FlightResponseChunkModel data={data.owner} onClickID={onClickID} />
      </div>
    </div>
  );
}


================================================
FILE: packages/core/src/components/FlightResponseChunkDebugInfo.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { FlightResponseChunkDebugInfo } from './FlightResponseChunkDebugInfo';

const meta: Meta<typeof FlightResponseChunkDebugInfo> = {
  component: FlightResponseChunkDebugInfo,
};

export default meta;
type Story = StoryObj<typeof FlightResponseChunkDebugInfo>;

export const WarnExample: Story = {
  args: {
    data: {
      $$type: 'reference',
      id: 'd',
      identifier: '',
      type: 'Debug info',
    },
  },
};


================================================
FILE: packages/core/src/components/FlightResponseChunkDebugInfo.tsx
================================================
import React from 'react';
import { DebugInfoChunk } from '@rsc-parser/react-client';
import { FlightResponseChunkModel } from './FlightResponseChunkModel';

export function FlightResponseChunkDebugInfo({
  data,
  onClickID,
}: {
  data: DebugInfoChunk['value'];
  onClickID: (id: string) => void;
}) {
  return (
    <FlightResponseChunkModel
      key={JSON.stringify(data)}
      data={data}
      onClickID={onClickID}
    />
  );
}


================================================
FILE: packages/core/src/components/FlightResponseChunkHint.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { FlightResponseChunkHint } from './FlightResponseChunkHint';

const meta: Meta<typeof FlightResponseChunkHint> = {
  component: FlightResponseChunkHint,
};

export default meta;
type Story = StoryObj<typeof FlightResponseChunkHint>;

export const StyleExample: Story = {
  args: {
    data: JSON.stringify([
      '/_next/static/css/41e8bd728ca8294e.css?dpl=dpl_DbJSoidkMic3ZCp3P3DJMZxkvi32',
      'style',
    ]),
  },
};

export const FontExample: Story = {
  args: {
    data: JSON.stringify([
      '/_next/static/41e8bd728ca8294e.css',
      'font',
      { crossOrigin: 'anonymous', type: 'font/woff2' },
    ]),
  },
};


================================================
FILE: packages/core/src/components/FlightResponseChunkHint.tsx
================================================
import React from 'react';
import { HintChunk } from '@rsc-parser/react-client';

export function FlightResponseChunkHint({
  data,
}: {
  data: HintChunk['value'];
}) {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex flex-col gap-4">
        <h3 className="text-xl font-semibold">
          Load asset of type &quot;{JSON.stringify(data[1])}&quot;
        </h3>
      </div>
      <p>Path: {data[0]}</p>
      {data[2] ? (
        <p>
          Settings: <pre>{JSON.stringify(data[2], null, 2)}</pre>
        </p>
      ) : null}
    </div>
  );
}


================================================
FILE: packages/core/src/components/FlightResponseChunkModel.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { FlightResponseChunkModel } from './FlightResponseChunkModel';
import { REACT_ELEMENT_TYPE } from '@rsc-parser/react-client';

const meta: Meta<typeof FlightResponseChunkModel> = {
  argTypes: { onClickID: { action: 'clicked client reference' } },
  component: FlightResponseChunkModel,
};

export default meta;
type Story = StoryObj<typeof FlightResponseChunkModel>;

export const String: Story = {
  args: {
    data: 'L7SkxK6dEGIxPChCswhi8',
  },
};

export const StringArray: Story = {
  args: {
    data: ['L7SkxK6dEGIxPChCswhi8', 'children', 'main'],
  },
};

export const Boolean: Story = {
  args: {
    data: true,
  },
};

export const Number: Story = {
  args: {
    data: 0,
  },
};

export const Undefined: Story = {
  args: {
    data: undefined,
  },
};

export const Null: Story = {
  args: {
    data: null,
  },
};

export const EmptyElement: Story = {
  args: {
    data: { $$typeof: REACT_ELEMENT_TYPE, type: 'br', key: '0', props: {} },
  },
};

export const ElementWithChildren: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'p',
      key: '0',
      props: { children: 'Hello world' },
    },
  },
};

export const ElementsWithCodeChildren: Story = {
  args: {
    data: [
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'p',
        key: '0',
        props: { children: '{}' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'p',
        key: '0',
        props: { children: '()' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'p',
        key: '0',
        props: { children: '<>' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'p',
        key: '0',
        props: { children: '`' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'p',
        key: '0',
        props: { children: '``' },
      },
    ],
  },
};

export const StringProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'div',
      key: '0',
      props: { className: 'test' },
    },
  },
};

export const EscapedStringProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'div',
      key: '0',
      props: { className: 'this a "quote"' },
    },
  },
};

export const NumberProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'Component',
      key: '0',
      props: { something: 0 },
    },
  },
};

export const BooleanProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'Component',
      key: '0',
      props: { something: true },
    },
  },
};

export const ElementProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'Component',
      key: '0',
      props: {
        element: {
          $$typeof: REACT_ELEMENT_TYPE,
          type: 'div',
          key: '0',
          props: {},
        },
      },
    },
  },
};

export const ObjectProps: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'div',
      key: '0',
      props: { data: { some: 'thing', foo: 'bar' } },
    },
  },
};

export const NestedObjectProps: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'div',
      key: '0',
      props: { data: { some: 'thing', foo: { baz: 'bar' } } },
    },
  },
};

export const ArrayProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'div',
      key: '0',
      props: { data: ['test', 'hello'] },
    },
  },
};

export const ArrayObjectProp: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: 'div',
      key: '0',
      props: { x: { y: [{ foo: 'bar' }, { foo: 'bar' }] } },
    },
  },
};

export const ElementArray: Story = {
  args: {
    data: [
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'br',
        key: '0',
        props: {},
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'br',
        key: '0',
        props: {},
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'br',
        key: '0',
        props: {},
      },
    ],
  },
};

export const NestedElementArray: Story = {
  args: {
    data: [
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'div',
        key: '0',
        props: {
          children: [
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'br',
              key: '0',
              props: {},
            },
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'br',
              key: '0',
              props: {},
            },
          ],
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'div',
        key: '0',
        props: {
          children: [
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'br',
              key: '0',
              props: {},
            },
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'br',
              key: '0',
              props: {},
            },
          ],
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'div',
        key: '0',
        props: {
          children: [
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'br',
              key: '0',
              props: {},
            },
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'br',
              key: '0',
              props: {},
            },
          ],
        },
      },
    ],
  },
};

export const ClientRef: Story = {
  args: {
    data: {
      $$typeof: REACT_ELEMENT_TYPE,
      type: '$L9',
      key: '0',
      props: {},
    },
  },
};

export const FirstRowExample: Story = {
  args: {
    data: [
      'L7SkxK6dEGIxPChCswhi8',
      [
        [
          'children',
          '(main)',
          'children',
          'projects',
          ['projects', { children: ['__PAGE__', {}] }],
          '$L1',
          [
            [],
            [
              '$L2',
              {
                $$typeof: REACT_ELEMENT_TYPE,
                type: 'meta',
                key: null,
                props: { name: 'next-size-adjust' },
              },
            ],
          ],
        ],
      ],
    ],
  },
};
export const MetaTagsExample: Story = {
  args: {
    data: [
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '0',
        props: { charSet: 'utf-8' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'title',
        key: '1',
        props: { children: 'Projects' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '2',
        props: {
          name: 'description',
          content: "These are some of the projects I've worked on",
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '3',
        props: { name: 'theme-color', content: '#16a34a' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '4',
        props: {
          name: 'viewport',
          content: 'width=device-width, initial-scale=1',
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'link',
        key: '5',
        props: {
          rel: 'alternate',
          type: 'application/rss+xml',
          href: 'https://alvar.dev/feed.xml',
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '6',
        props: { property: 'og:site_name', content: 'alvar.dev' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '7',
        props: { property: 'og:image:type', content: 'image/png' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '8',
        props: {
          property: 'og:image',
          content:
            'https://alvar.dev/projects/opengraph-image-7gbxx9?92712a71ecaaf7f6',
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '9',
        props: { property: 'og:image:width', content: '1200' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '10',
        props: { property: 'og:image:height', content: '630' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '11',
        props: { name: 'twitter:card', content: 'summary_large_image' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '12',
        props: { name: 'twitter:site', content: '@alvarlagerlof' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '13',
        props: { name: 'twitter:creator', content: '@alvarlagerlof' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '14',
        props: { name: 'twitter:image:type', content: 'image/png' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '15',
        props: {
          name: 'twitter:image',
          content:
            'https://alvar.dev/projects/opengraph-image-7gbxx9?92712a71ecaaf7f6',
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '16',
        props: { name: 'twitter:image:width', content: '1200' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'meta',
        key: '17',
        props: { name: 'twitter:image:height', content: '630' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'link',
        key: '18',
        props: { rel: 'icon', href: '/favicons/favicon.ico' },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'link',
        key: '19',
        props: {
          rel: 'icon',
          href: '/favicons/favicon-16x16.png',
          sizes: '16x16',
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'link',
        key: '20',
        props: {
          rel: 'icon',
          href: '/favicons/favicon-32x32.png',
          sizes: '32x32',
        },
      },
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'link',
        key: '21',
        props: {
          rel: 'icon',
          href: '/favicons/favicon-192x192.png',
          sizes: '192x192',
        },
      },
    ],
  },
};

export const NextJsExample: Story = {
  args: {
    data: [
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: 'html',
        key: null,
        props: {
          lang: 'en',
          suppressHydrationWarning: true,
          children: [
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'head',
              key: null,
              props: {},
            },
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: 'body',
              key: null,
              props: {
                children: [
                  {
                    $$typeof: REACT_ELEMENT_TYPE,
                    type: '$L6',
                    key: null,
                    props: {},
                  },
                  {
                    $$typeof: REACT_ELEMENT_TYPE,
                    type: '$L7',
                    key: null,
                    props: {},
                  },
                  {
                    $$typeof: REACT_ELEMENT_TYPE,
                    type: '$L8',
                    key: null,
                    props: {
                      children: [
                        {
                          $$typeof: REACT_ELEMENT_TYPE,
                          type: '$L9',
                          key: null,
                          props: {},
                        },
                        {
                          $$typeof: REACT_ELEMENT_TYPE,
                          type: 'main',
                          key: null,
                          props: {
                            children: {
                              $$typeof: REACT_ELEMENT_TYPE,
                              type: '$La',
                              key: null,
                              props: {
                                parallelRouterKey: 'children',
                                segmentPath: ['children'],
                                error: '$undefined',
                                errorStyles: '$undefined',
                                loading: '$undefined',
                                loadingStyles: '$undefined',
                                hasLoading: false,
                                template: {
                                  $$typeof: REACT_ELEMENT_TYPE,
                                  type: '$Lb',
                                  key: null,
                                  props: {},
                                },
                                templateStyles: '$undefined',
                                notFound: '$undefined',
                                notFoundStyles: '$undefined',
                                childProp: {
                                  current: [
                                    {
                                      $$typeof: REACT_ELEMENT_TYPE,
                                      type: 'div',
                                      key: null,
                                      props: {
                                        className: 'home_root__yKyeQ',
                                        children: [
                                          {
                                            $$typeof: REACT_ELEMENT_TYPE,
                                            type: 'div',
                                            key: null,
                                            props: {
                                              id: 'geist-skip-nav',
                                              tabIndex: -1,
                                            },
                                          },
                                          {
                                            $$typeof: REACT_ELEMENT_TYPE,
                                            type: '$Lc',
                                            key: null,
                                            props: {},
                                          },
                                          {
                                            $$typeof: REACT_ELEMENT_TYPE,
                                            type: '$Ld',
                                            key: null,
                                            props: {
                                              children: {
                                                $$typeof: REACT_ELEMENT_TYPE,
                                                type: '$Le',
                                                key: null,
                                                props: {},
                                              },
                                            },
                                          },
                                          {
                                            $$typeof: REACT_ELEMENT_TYPE,
                                            type: '$Ld',
                                            key: null,
                                            props: {
                                              children: {
                                                $$typeof: REACT_ELEMENT_TYPE,
                                                type: '$Lf',
                                                key: null,
                                                props: {},
                                              },
                                            },
                                          },
                                          {
                                            $$typeof: REACT_ELEMENT_TYPE,
                                            type: '$Ld',
                                            key: null,
                                            props: {
                                              children: {
                                                $$typeof: REACT_ELEMENT_TYPE,
                                                type: '$L10',
                                                key: null,
                                                props: {},
                                              },
                                            },
                                          },
                                          {
                                            $$typeof: REACT_ELEMENT_TYPE,
                                            type: '$Ld',
                                            key: null,
                                            props: {
                                              children: {
                                                $$typeof: REACT_ELEMENT_TYPE,
                                                type: '$L11',
                                                key: null,
                                                props: {},
                                              },
                                            },
                                          },
                                        ],
                                      },
                                    },
                                    null,
                                  ],
                                  segment: '__PAGE__',
                                },
                                styles: [
                                  {
                                    $$typeof: REACT_ELEMENT_TYPE,
                                    type: 'link',
                                    key: '0',
                                    props: {
                                      rel: 'stylesheet',
                                      href: '/_next/static/css/9a9aedd1f702c897.css?dpl=dpl_DWRqk7ufoR23bkEq1XgenPvDAjTK',
                                      precedence: 'next',
                                    },
                                  },
                                ],
                              },
                            },
                          },
                        },
                        {
                          $$typeof: REACT_ELEMENT_TYPE,
                          type: '$Ld',
                          key: null,
                          props: {
                            children: {
                              $$typeof: REACT_ELEMENT_TYPE,
                              type: '$L12',
                              key: null,
                              props: {},
                            },
                          },
                        },
                      ],
                    },
                  },
                  {
                    $$typeof: REACT_ELEMENT_TYPE,
                    type: '$L13',
                    key: null,
                    props: {},
                  },
                ],
              },
            },
          ],
        },
      },
      null,
    ],
  },
};


================================================
FILE: packages/core/src/components/FlightResponseChunkModel.tsx
================================================
import React, {
  ReactNode,
  createContext,
  useContext,
  useState,
  useTransition,
} from 'react';
import {
  Disclosure,
  DisclosureContent,
  useDisclosureStore,
} from '@ariakit/react';
import { ErrorBoundary } from 'react-error-boundary';

import { GenericErrorBoundaryFallback } from './GenericErrorBoundaryFallback.jsx';
import { DownArrowIcon, RightArrowIcon } from './FlightResponseIcons.jsx';
import {
  createElement,
  Reference,
  isElement,
  isReference,
} from '@rsc-parser/react-client';

const ClickIDContext = createContext<{
  onClickID: (id: string) => void;
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
}>(null);

export function FlightResponseChunkModel({
  data,
  onClickID,
}: {
  data: unknown;
  onClickID: (id: string) => void;
}) {
  return (
    <div className="font-code ligatures-none">
      <ClickIDContext.Provider value={{ onClickID: onClickID }}>
        <Node value={data} />
      </ClickIDContext.Provider>
    </div>
  );
}

function Node({ value }: { value: unknown }) {
  return (
    <ErrorBoundary
      FallbackComponent={GenericErrorBoundaryFallback}
      key={JSON.stringify(value)}
    >
      <NodeSwitch value={value} />
    </ErrorBoundary>
  );
}

function NodeSwitch({ value }: { value: unknown }) {
  if (isElement(value)) {
    return <NodeElement value={value} />;
  }

  if (Array.isArray(value)) {
    return <NodeArray value={value} />;
  }

  if (isReference(value)) {
    return <NodeReference value={value} />;
  }

  if (value instanceof Set) {
    return <NodeSet value={value} />;
  }

  if (typeof value === 'symbol') {
    return <NodeSymbol value={value} />;
  }

  if (value instanceof Date) {
    return <NodeDate value={value} />;
  }

  if (Number.isNaN(value)) {
    return <NodeNaN />;
  }

  if (typeof value === 'bigint') {
    return <NodeBigInt value={value} />;
  }

  if (typeof value === 'number' && isFinite(value) === false && value > 0) {
    return <NodePositiveInfinity />;
  }

  if (typeof value === 'number' && isFinite(value) === false && value < 0) {
    return <NodeNegativeInfinity />;
  }

  if (typeof value === 'number') {
    return <NodeNumber value={value} />;
  }

  if (value === undefined) {
    return <NodeUndefined />;
  }

  if (value === null) {
    return <NodeNull />;
  }

  if (typeof value === 'string') {
    return <NodeString value={value} />;
  }

  if (
    value !== null &&
    typeof value === 'object' &&
    Array.isArray(value) === false &&
    !(value instanceof Array)
  ) {
    return <NodeObject value={value} />;
  }

  return <NodeUnknown value={value} />;
}

function removeChildren(props: Record<string, unknown>) {
  return Object.keys(props)
    .filter((key) => key !== 'children')
    .reduce<Record<string, unknown>>((result, current) => {
      result[current] = props[current];
      return result;
    }, {});
}

function NodeElement({
  value: { type, props },
}: {
  value: ReturnType<typeof createElement>;
}) {
  const [isOpen, setIsOpen] = useState(true);
  const [isPending, startTransition] = useTransition();
  const disclosure = useDisclosureStore({
    open: isOpen,
    setOpen: (open) => {
      startTransition(() => {
        setIsOpen(open);
      });
    },
  });

  const isInsideProps = useContext(PropsContext);

  if (isInsideProps === undefined) {
    throw new Error('PropsContext must be used within a PropsContext.Provider');
  }

  const propsWithoutChildren = removeChildren(props);
  const hasVisibleProps =
    propsWithoutChildren !== undefined &&
    Object.keys(propsWithoutChildren).length > 0;

  const newTag = typeof type === 'string' ? type : <Node value={type} />;

  if (Object.keys(props).length === 0) {
    return (
      <div>
        <span className={isInsideProps ? '' : 'ml-[18px]'}>
          <Purple>
            <LeftArrow />
          </Purple>
          <Pink>{newTag}</Pink>{' '}
          <Purple>
            /<RightArrow />
          </Purple>
        </span>
      </div>
    );
  }

  return (
    <ObjectContext.Provider value={false}>
      <Disclosure
        store={disclosure}
        className="-my-0.5 cursor-pointer rounded-lg py-0.5 outline outline-2 outline-transparent transition-all duration-200 ligatures-none hover:bg-slate-700/10 focus:bg-slate-700/10 dark:hover:bg-white/10 dark:focus:bg-white/10"
        style={{ opacity: isPending ? 0.7 : 1 }}
      >
        {isOpen ? <DownArrowIcon /> : <RightArrowIcon />}

        <Purple>
          <LeftArrow />
        </Purple>
        <Pink>{newTag}</Pink>
        {isOpen ? (
          <>
            {hasVisibleProps ? null : (
              <Purple>
                {props.children === undefined ? (
                  <>
                    {' '}
                    /<RightArrow />
                  </>
                ) : (
                  <RightArrow />
                )}
              </Purple>
            )}
          </>
        ) : (
          <>
            <Purple>
              <RightArrow />
            </Purple>

            <span className="mx-1 rounded-lg border-1 border-solid border-slate-400 px-1.5">
              ⋯
            </span>
            <Purple>
              <LeftArrow />/
            </Purple>
            <Pink>{newTag}</Pink>
            <Purple>
              <RightArrow />
            </Purple>
          </>
        )}
      </Disclosure>

      <DisclosureContent store={disclosure}>
        {isOpen ? (
          // This is kind of misusing <details><summary>, but it lets us
          // avoid rendering the children if it is not open
          <>
            {hasVisibleProps ? (
              <>
                <Props props={props} />
                <div className="pl-[18px]">
                  {props.children === undefined ? (
                    <>
                      <Purple>
                        /<RightArrow />
                      </Purple>
                    </>
                  ) : (
                    <Purple>
                      <RightArrow />
                    </Purple>
                  )}
                </div>
              </>
            ) : null}

            {props.children === undefined ? null : (
              <>
                <PropsContext.Provider value={false}>
                  <div className="flex flex-col items-start gap-2 pl-[calc(2ch+18px)]">
                    <ErrorBoundary fallback={<p>fail here</p>}>
                      <Node value={props.children} />
                    </ErrorBoundary>
                  </div>
                </PropsContext.Provider>

                <div className="pl-[18px]">
                  <Purple>
                    <LeftArrow />/
                  </Purple>
                  <Pink>{newTag}</Pink>
                  <Purple>
                    <RightArrow />
                  </Purple>
                </div>
              </>
            )}
          </>
        ) : null}
      </DisclosureContent>
    </ObjectContext.Provider>
  );
}

const PropsContext = createContext(false);

function Prop({ propKey, value }: { propKey: string; value: unknown }) {
  return (
    <>
      <Green>{propKey}</Green>
      <Pink>{`=`}</Pink>
      {(typeof value === 'string' && value === '$undefined') ||
      typeof value !== 'string' ? (
        <Blue>
          <LeftCurlyBrace />
        </Blue>
      ) : null}

      <PropsContext.Provider value={true}>
        <Node value={value} />
      </PropsContext.Provider>

      {(typeof value === 'string' && value === '$undefined') ||
      typeof value !== 'string' ? (
        <Blue>
          <RightCurlyBrace />
        </Blue>
      ) : null}
    </>
  );
}

function Props({ props }: { props: { [key: string]: unknown } }) {
  if (isReference(props)) {
    return (
      <div className="pl-[3ch]">
        <Node value={props} />
      </div>
    );
  }

  const rootProps = Object.keys(props);

  if (
    rootProps.length === 0 ||
    (rootProps.length === 1 && rootProps[0] === 'children')
  ) {
    return null;
  }

  return (
    <div className="pl-[3ch]">
      {rootProps
        .filter((rootProp) => rootProp !== 'children')
        .map((rootProp, i) => {
          return (
            <div key={rootProp}>
              <Prop propKey={rootProp} value={props[rootProp]} />
              {i < rootProps.length - 1 ? ' ' : null}
            </div>
          );
        })}
    </div>
  );
}

function NodeArray({ value }: { value: unknown[] }) {
  const isInsideProps = useContext(PropsContext);

  if (isInsideProps === undefined) {
    throw new Error('PropsContext must be used within a PropsContext.Provider');
  }

  if (value.length == 0) {
    return (
      <div>
        <LeftSquareBracket />
        <RightSquareBracket />
      </div>
    );
  }

  return (
    <div>
      {isInsideProps ? (
        <div className="pl-[2ch]">
          <LeftSquareBracket />
        </div>
      ) : null}
      <ul
        className={`flex w-full flex-col ${
          isInsideProps ? 'pl-[4ch]' : 'gap-1'
        }`}
      >
        {/* TODO: Why is this spread needed for arrays like `[undefined]` ? */}
        {[...value].map((subValue, i) => {
          return (
            <li key={JSON.stringify(subValue) + String(i)}>
              <Node value={subValue} />
              {isInsideProps && i !== value.length - 1 ? (
                <span className="">,</span>
              ) : null}
            </li>
          );
        })}
      </ul>
      {isInsideProps ? (
        <div className="pl-[2ch]">
          <RightSquareBracket />
        </div>
      ) : null}
    </div>
  );
}

function NodeReference({ value }: { value: Reference }) {
  return (
    <span className="inline-flex flex-row gap-2">
      <TabJumpButton destinationTab={value.id}>
        {value.id} ({value.identifier === '' ? null : `${value.identifier} - `}
        {value.type})
      </TabJumpButton>
    </span>
  );
}

function TabJumpButton({
  destinationTab,
  children,
}: {
  destinationTab: string;
  children: ReactNode;
}) {
  const { onClickID } = useContext(ClickIDContext);

  return (
    <button
      className="inline-flex flex-row items-center gap-1 rounded-sm bg-blue-300 px-1 py-0.5 text-left text-black dark:bg-slate-700 dark:text-white"
      onClick={() => {
        onClickID(destinationTab);
      }}
    >
      {children}{' '}
      <svg
        viewBox="0 0 24 24"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
        width="16px"
        height="16px"
      >
        <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
        <g
          id="SVGRepo_tracerCarrier"
          strokeLinecap="round"
          strokeLinejoin="round"
        ></g>
        <g id="SVGRepo_iconCarrier">
          <path
            d="M15.0001 13.5V9M15.0001 9H10.5001M15.0001 9L9.00024 14.9999M7.20024 20H16.8002C17.9203 20 18.4804 20 18.9082 19.782C19.2845 19.5903 19.5905 19.2843 19.7823 18.908C20.0002 18.4802 20.0002 17.9201 20.0002 16.8V7.2C20.0002 6.0799 20.0002 5.51984 19.7823 5.09202C19.5905 4.71569 19.2845 4.40973 18.9082 4.21799C18.4804 4 17.9203 4 16.8002 4H7.20024C6.08014 4 5.52009 4 5.09226 4.21799C4.71594 4.40973 4.40998 4.71569 4.21823 5.09202C4.00024 5.51984 4.00024 6.07989 4.00024 7.2V16.8C4.00024 17.9201 4.00024 18.4802 4.21823 18.908C4.40998 19.2843 4.71594 19.5903 5.09226 19.782C5.52009 20 6.08014 20 7.20024 20Z"
            stroke="currentColor"
            strokeWidth="1.5"
            strokeLinecap="round"
            strokeLinejoin="round"
          ></path>
        </g>
      </svg>
    </button>
  );
}

function NodeSet({ value }: { value: Set<unknown> }) {
  return (
    <JSContainerWrapperForObjects>
      Set([{[...value.values()].join(',')}])
    </JSContainerWrapperForObjects>
  );
}

function NodeSymbol({ value }: { value: symbol }) {
  return (
    <JSContainerWrapperForObjects>
      {value.toString()}
    </JSContainerWrapperForObjects>
  );
}

function NodeDate({ value }: { value: Date }) {
  return (
    <JSContainerWrapperForObjects>
      JS Date: {value.toString()}
    </JSContainerWrapperForObjects>
  );
}

function NodeNaN() {
  return <JSContainerWrapperForObjects>NaN</JSContainerWrapperForObjects>;
}

function NodeBigInt({ value }: { value: bigint }) {
  return (
    <JSContainerWrapperForObjects>
      BigInt: {value.toString()}
    </JSContainerWrapperForObjects>
  );
}

function NodePositiveInfinity() {
  return <JSContainerWrapperForObjects>Infinity</JSContainerWrapperForObjects>;
}

function NodeNegativeInfinity() {
  return <JSContainerWrapperForObjects>-Infinity</JSContainerWrapperForObjects>;
}

function NodeNumber({ value }: { value: number }) {
  return (
    <JSContainerWrapperForObjects>
      {value.toString()}
    </JSContainerWrapperForObjects>
  );
}

function NodeNull() {
  return <JSContainerWrapperForObjects>null</JSContainerWrapperForObjects>;
}

function NodeUndefined() {
  return <JSContainerWrapperForObjects>undefined</JSContainerWrapperForObjects>;
}

function NodeString({ value }: { value: string }) {
  const isInsideProps = useContext(PropsContext);

  if (isInsideProps === undefined) {
    throw new Error('PropsContext must be used within a PropsContext.Provider');
  }

  const needsSpecialHandling =
    value.includes('\\') ||
    value.includes('{') ||
    value.includes('}') ||
    value.includes('<') ||
    value.includes('>') ||
    value.includes('(') ||
    value.includes(')') ||
    value.includes('`');

  const formattedString = value
    .replaceAll(`"`, `&#92;"`)
    .replaceAll(`\``, '\\`');

  if (!isInsideProps) {
    return (
      <div className="inline flex-col gap-2">
        {needsSpecialHandling ? (
          <>
            <Blue>
              <LeftCurlyBrace />
            </Blue>
            <Yellow>
              `
              <span
                className=""
                dangerouslySetInnerHTML={{ __html: formattedString }}
              />
              `
            </Yellow>
            <Blue>
              <RightCurlyBrace />
            </Blue>
          </>
        ) : (
          <span
            className=""
            dangerouslySetInnerHTML={{ __html: formattedString }}
          />
        )}
      </div>
    );
  }

  return (
    <div className="inline flex-col gap-2">
      <Yellow>
        &quot;
        <span dangerouslySetInnerHTML={{ __html: formattedString }} />
        &quot;
      </Yellow>
    </div>
  );
}

function NodeObject({ value }: { value: object }) {
  return (
    <ObjectContext.Provider value={true}>
      <JSObjectValue value={value} />
    </ObjectContext.Provider>
  );
}

function NodeUnknown({ value }: { value: unknown }) {
  return (
    <JSContainerWrapperForObjects>
      {JSON.stringify(value, null, 2)}
    </JSContainerWrapperForObjects>
  );
}

function JSContainer({ children }: { children: ReactNode }) {
  const isInsideProps = useContext(PropsContext);

  if (isInsideProps === undefined) {
    throw new Error('PropsContext must be used within a PropsContext.Provider');
  }

  return (
    <span>
      {isInsideProps ? null : (
        <Blue>
          <LeftCurlyBrace />
        </Blue>
      )}

      <code className="whitespace-break-spaces break-all">{children}</code>

      {isInsideProps ? null : (
        <Blue>
          <RightCurlyBrace />
        </Blue>
      )}
    </span>
  );
}

const ObjectContext = createContext(false);

function isLetter(letter: string) {
  // @ts-expect-error TODO: Fix this
  return RegExp(/^\p{L}/, 'u').test(letter);
}

function JSObjectValue({ value }: { value: object }) {
  const isInsideProps = useContext(PropsContext);

  if (isInsideProps === undefined) {
    throw new Error('PropsContext must be used within a PropsContext.Provider');
  }

  return (
    <JSContainer>
      {isInsideProps ? (
        <Blue>
          <LeftCurlyBrace />
        </Blue>
      ) : null}

      <div className="flex flex-col pl-[2ch]">
        {Object.entries(value).map(([entryKey, entryValue], i) => {
          const needsDoubleQuotes = !isLetter(entryKey[0]);
          return (
            <span key={entryKey}>
              <span>
                {needsDoubleQuotes ? `"` : null}
                {entryKey}
                {needsDoubleQuotes ? `"` : null}:{' '}
              </span>
              <Node value={entryValue} />
              {i !== Object.keys(value).length - 1 ? <>,</> : null}
            </span>
          );
        })}
      </div>

      {isInsideProps ? (
        <Blue>
          <RightCurlyBrace />
        </Blue>
      ) : null}
    </JSContainer>
  );
}

function JSContainerWrapperForObjects({ children }: { children: ReactNode }) {
  const isInsideObject = useContext(ObjectContext);

  if (isInsideObject === undefined) {
    throw new Error(
      'ObjectContext must be used within a ObjectContext.Provider',
    );
  }

  if (isInsideObject) {
    return <JSContainer>{children}</JSContainer>;
  }

  return children;
}

function Purple({ children }: { children: ReactNode }) {
  return <span className="text-purple-500">{children}</span>;
}

function Pink({ children }: { children: ReactNode }) {
  return <span className="text-pink-700 dark:text-pink-500">{children}</span>;
}

function Yellow({ children }: { children: ReactNode }) {
  return (
    <span className="text-yellow-600 dark:text-yellow-300">{children}</span>
  );
}

function Blue({ children }: { children: ReactNode }) {
  return <span className="text-blue-500">{children}</span>;
}

function Green({ children }: { children: ReactNode }) {
  return <span className="text-green-700 dark:text-green-400">{children}</span>;
}

function LeftCurlyBrace() {
  return <>&#123;</>;
}

function RightCurlyBrace() {
  return <>&#125;</>;
}

function LeftSquareBracket() {
  return <>&#91;</>;
}

function RightSquareBracket() {
  return <>&#93;</>;
}

function LeftArrow() {
  return <>&lt;</>;
}

function RightArrow() {
  return <>&gt;</>;
}


================================================
FILE: packages/core/src/components/FlightResponseChunkModule.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { FlightResponseChunkModule } from './FlightResponseChunkModule';

const meta: Meta<typeof FlightResponseChunkModule> = {
  component: FlightResponseChunkModule,
};

export default meta;
type Story = StoryObj<typeof FlightResponseChunkModule>;

export const UnknownName: Story = {
  args: {
    data: [
      '77095',
      [
        '7095',
        'static/chunks/7095-0d20f1be707836ac.js',
        '3517',
        'static/chunks/app/(main)/blog/page-22a0214c471ccc51.js',
      ],
      '',
    ],
  },
};

export const WithName: Story = {
  args: {
    data: [
      '82538',
      [
        '7095',
        'static/chunks/7095-0d20f1be707836ac.js',
        '3034',
        'static/chunks/3034-d8cf0f8567d59e6e.js',
        '3503',
        'static/chunks/3503-24b01b8fcb22492f.js',
        '2480',
        'static/chunks/app/(main)/projects/page-805143d67355c092.js',
      ],
      'NextSanityImage',
    ],
  },
};


================================================
FILE: packages/core/src/components/FlightResponseChunkModule.tsx
================================================
import React from 'react';
import { ModuleChunk } from '@rsc-parser/react-client';

function groupChunks(array: (string | number)[]) {
  const newArray = [];
  for (let i = 0; i < array.length; i += 2) {
    newArray.push({ name: array[i], path: array[i + 1] });
  }
  return newArray;
}

export function FlightResponseChunkModule({
  data,
}: {
  data: ModuleChunk['value'];
}) {
  return (
    <div className="flex flex-col gap-4">
      <h3 className="text-xl font-semibold">
        Import {data[2] == '' ? 'unknown' : data[2]}
      </h3>
      <p>Id: {data[0]}</p>
      <div>
        <h4 className="font-medium">Chunks</h4>
        <ul className="list-inside list-disc">
          {groupChunks(data[1]).map((item) => {
            return (
              <li key={item.name}>
                {item.name} - {item.path}
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}


================================================
FILE: packages/core/src/components/FlightResponseChunkText.tsx
================================================
import React from 'react';
import { TextChunk } from '@rsc-parser/react-client';

export function FlightResponseChunkText({
  data,
}: {
  data: TextChunk['value'];
}) {
  return <p>{data}</p>;
}


================================================
FILE: packages/core/src/components/FlightResponseChunkUnknown.tsx
================================================
import React from 'react';
import { Chunk } from '@rsc-parser/react-client';

export function FlightResponseChunkUnknown({ chunk }: { chunk: Chunk }) {
  return (
    <p>
      Encountered chunk type `{chunk.type}`. Rendering hasn't been implemented
      for this type yet.
    </p>
  );
}


================================================
FILE: packages/core/src/components/FlightResponseIcons.tsx
================================================
import React from 'react';

export function DownArrowIcon() {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      width={18}
      className="inline text-slate-900 dark:text-slate-100"
    >
      <title>Down arrow</title>
      <path d="M12 16L6 10H18L12 16Z" fill="currentColor"></path>
    </svg>
  );
}

export function RightArrowIcon() {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      width={18}
      className="inline text-slate-900 dark:text-slate-100"
    >
      <title>Right arrow</title>
      <path d="M16 12L10 18V6L16 12Z" fill="currentColor"></path>
    </svg>
  );
}


================================================
FILE: packages/core/src/components/GenericErrorBoundaryFallback.tsx
================================================
import React from 'react';

export function GenericErrorBoundaryFallback({ error }: { error: Error }) {
  return (
    <div role="alert" className="rounded-lg bg-red-100 p-4">
      <p className="dark:text-red-400">Something went wrong:</p>
      <pre className="text-red-600 dark:text-red-500">{error.message}</pre>
    </div>
  );
}


================================================
FILE: packages/core/src/components/IconButton.tsx
================================================
import { ElementType, ReactNode } from 'react';

export function IconButton({
  children,
  onClick,
  as = 'button',
}: {
  children: ReactNode;
  onClick: () => void;
  as?: ElementType;
}) {
  const Component = as;

  return (
    <Component
      onClick={onClick}
      className="rounded-full bg-slate-300 p-1 text-black  dark:bg-slate-700 dark:text-white"
    >
      <div className="size-6">{children}</div>
    </Component>
  );
}


================================================
FILE: packages/core/src/components/Logo.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { Logo } from './Logo';

const meta: Meta<typeof Logo> = {
  component: Logo,
};

export default meta;
type Story = StoryObj<typeof Logo>;

export const small: Story = {
  name: 'small',
  args: {
    variant: 'small',
  },
};

export const wide: Story = {
  name: 'wide',
  args: {
    variant: 'wide',
  },
};


================================================
FILE: packages/core/src/components/Logo.tsx
================================================
import React from 'react';

export function Logo({ variant }: { variant: 'small' | 'wide' }) {
  if (variant === 'small') {
    return <p className="dark:text-white">RSC</p>;
  }

  return <p className="dark:text-white">RSC Devtools</p>;
}


================================================
FILE: packages/core/src/components/OverflowButton.tsx
================================================
import React, { ReactNode } from 'react';
import { MenuProvider, MenuButton, Menu } from '@ariakit/react';
import { IconButton } from './IconButton';

export function OverflowButton({ menuItems }: { menuItems: ReactNode }) {
  return (
    <MenuProvider>
      <IconButton onClick={() => {}} as={MenuButton}>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="currentColor"
          viewBox="0 0 16 16"
          className="size-full"
        >
          <title>Three dots</title>
          <path d="M8,6.5A1.5,1.5,0,1,1,6.5,8,1.5,1.5,0,0,1,8,6.5ZM.5,8A1.5,1.5,0,1,0,2,6.5,1.5,1.5,0,0,0,.5,8Zm12,0A1.5,1.5,0,1,0,14,6.5,1.5,1.5,0,0,0,12.5,8Z" />
        </svg>
      </IconButton>
      <Menu
        gutter={8}
        className="flex flex-col items-start gap-2 rounded-lg bg-slate-600 px-4 py-2 text-white shadow-lg dark:bg-slate-200 dark:text-black"
      >
        {menuItems}
      </Menu>
    </MenuProvider>
  );
}


================================================
FILE: packages/core/src/components/PanelLayout.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { PanelLayout } from './PanelLayout';

const meta: Meta<typeof PanelLayout> = {
  component: PanelLayout,
};

export default meta;
type Story = StoryObj<typeof PanelLayout>;

export const small: Story = {
  name: 'small',
  args: {
    header: 'Header',
    buttons: 'Buttons go here',
    children: 'Content',
  },
};


================================================
FILE: packages/core/src/components/PanelLayout.tsx
================================================
import React, { ReactNode } from 'react';

export function PanelLayout({
  header,
  buttons,
  children,
}: {
  header: ReactNode;
  buttons?: ReactNode;
  children: ReactNode;
}) {
  return (
    <div className="flex min-h-full flex-col text-sm">
      <div className="sticky top-0 z-30 flex flex-row justify-between gap-4 bg-slate-100 p-3 dark:bg-slate-900">
        <div className="flex flex-row items-center gap-4">{header}</div>
        <div className="flex flex-row items-center gap-2">{buttons}</div>
      </div>
      <div className="grow px-3">
        <div className="pb-3">{children}</div>
      </div>
    </div>
  );
}


================================================
FILE: packages/core/src/components/RecordButton.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { RecordButton } from './RecordButton';

const meta: Meta<typeof RecordButton> = {
  component: RecordButton,
};

export default meta;
type Story = StoryObj<typeof RecordButton>;

export const notRecording: Story = {
  name: 'not recording',
  args: {
    isRecording: false,
  },
};

export const recording: Story = {
  name: 'recording',
  args: {
    isRecording: true,
  },
};


================================================
FILE: packages/core/src/components/RecordButton.tsx
================================================
import React from 'react';

export function RecordButton({
  isRecording,
  onClickRecord,
}: {
  isRecording: boolean;
  onClickRecord: () => void;
}) {
  if (isRecording) {
    return (
      <div className="flex w-fit flex-row items-center gap-2 rounded-md bg-red-200 px-2 py-0.5 font-medium dark:bg-red-700">
        <div className="size-3 animate-pulse rounded-full bg-red-500 dark:bg-red-400" />
        <span className="text-red-600 dark:text-red-100">Recording...</span>
      </div>
    );
  }

  return (
    <button
      className="rounded-md bg-slate-600 px-2 py-0.5 text-white dark:bg-slate-300 dark:text-black"
      onClick={async () => {
        onClickRecord();
      }}
    >
      Start recording
    </button>
  );
}


================================================
FILE: packages/core/src/components/RequestDetail.tsx
================================================
import React from 'react';
import { TabList, Tab, TabPanel, TabProvider } from '@ariakit/react';
import { RscEvent, isRscRequestEvent, isRscResponseEvent } from '../events';
import { GenericErrorBoundaryFallback } from './GenericErrorBoundaryFallback';
import { ErrorBoundary } from 'react-error-boundary';
import { RequestDetailTabRawPayload } from './RequestDetailTabRawPayload';
import { RequestDetailTabParsedPayload } from './RequestDetailTabParsedPayload';
import { RequestDetailTabNetwork } from './RequestDetailTabNetwork';
import { RequestDetailTabHeaders } from './RequestDetailTabHeaders';
import { useTabStoreWithTransitions } from './useTabStoreWithTransitions';
import { RequestDetailTabTimings } from './RequestDetailTabTimings';

export function RequestDetail({ events }: { events: RscEvent[] }) {
  const { currentTab, isPending, tabStore } = useTabStoreWithTransitions({
    defaultSelectedId: 'parsedPayload',
  });

  return (
    <TabProvider store={tabStore}>
      <div className="flex w-full flex-col gap-4">
        <TabList
          aria-label="Render modes"
          className="flex flex-row flex-wrap gap-2"
        >
          <Tab
            id="headers"
            className="rounded-md bg-slate-200 px-2 py-0.5 aria-disabled:opacity-50 aria-selected:bg-slate-300 dark:bg-slate-700 dark:aria-selected:text-black"
            disabled={
              !events.some(
                (event) =>
                  isRscRequestEvent(event) || isRscResponseEvent(event),
              )
            }
          >
            Headers
          </Tab>
          <Tab
            id="parsedPayload"
            className="rounded-md bg-slate-200 px-2 py-0.5 aria-disabled:opacity-50 aria-selected:bg-slate-300 dark:bg-slate-700 dark:aria-selected:text-black"
          >
            Parsed payload
          </Tab>
          <Tab
            id="rawPayload"
            className="rounded-md bg-slate-200 px-2 py-0.5 aria-disabled:opacity-50 aria-selected:bg-slate-300 dark:bg-slate-700 dark:aria-selected:text-black"
          >
            Raw payload
          </Tab>
          <Tab
            id="timings"
            className="rounded-md bg-slate-200 px-2 py-0.5 aria-disabled:opacity-50 aria-selected:bg-slate-300 dark:bg-slate-700 dark:aria-selected:text-black"
            disabled={
              !events.some(
                (event) =>
                  isRscRequestEvent(event) || isRscResponseEvent(event),
              )
            }
          >
            Timings
          </Tab>
          <Tab
            id="network"
            className="text-nowrap rounded-md bg-slate-200 px-2 py-0.5 aria-disabled:opacity-50 aria-selected:bg-slate-300 dark:bg-slate-700 dark:aria-selected:text-black"
          >
            Network (Beta)
          </Tab>
        </TabList>

        <TabPanel
          tabId={currentTab}
          className={`flex min-w-0 grow flex-col gap-4 transition-opacity delay-75 duration-100 ${
            isPending ? 'opacity-60' : ''
          }`}
          aria-busy={isPending}
          alwaysVisible={true}
        >
          {currentTab === 'headers' ? (
            <ErrorBoundary FallbackComponent={GenericErrorBoundaryFallback}>
              <RequestDetailTabHeaders events={events} />
            </ErrorBoundary>
          ) : null}
          {currentTab === 'parsedPayload' ? (
            <ErrorBoundary FallbackComponent={GenericErrorBoundaryFallback}>
              <RequestDetailTabParsedPayload events={events} />
            </ErrorBoundary>
          ) : null}
          {currentTab === 'rawPayload' ? (
            <ErrorBoundary FallbackComponent={GenericErrorBoundaryFallback}>
              <RequestDetailTabRawPayload events={events} />
            </ErrorBoundary>
          ) : null}
          {currentTab === 'timings' ? (
            <ErrorBoundary FallbackComponent={GenericErrorBoundaryFallback}>
              <RequestDetailTabTimings events={events} />
            </ErrorBoundary>
          ) : null}
          {currentTab === 'network' ? (
            <ErrorBoundary FallbackComponent={GenericErrorBoundaryFallback}>
              <RequestDetailTabNetwork events={events} />
            </ErrorBoundary>
          ) : null}
        </TabPanel>
      </div>
    </TabProvider>
  );
}


================================================
FILE: packages/core/src/components/RequestDetailTabEmptyState.tsx
================================================
export function RequestDetailTabEmptyState() {
  return 'No data found for the current time frame.';
}


================================================
FILE: packages/core/src/components/RequestDetailTabHeaders.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { RequestDetailTabHeaders } from './RequestDetailTabHeaders';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof RequestDetailTabHeaders> = {
  component: RequestDetailTabHeaders,
};

export default meta;
type Story = StoryObj<typeof RequestDetailTabHeaders>;

export const alvarDev: Story = {
  name: 'alvar.dev',
  render: () => {
    return <RequestDetailTabHeaders events={alvarDevExampleData} />;
  },
};

export const ghFredKissDev: Story = {
  name: 'gh.fredkiss.dev',
  render: () => {
    return <RequestDetailTabHeaders events={ghFredkissDevExampleData} />;
  },
};

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  render: () => {
    return <RequestDetailTabHeaders events={nextjsOrgExampleData} />;
  },
};


================================================
FILE: packages/core/src/components/RequestDetailTabHeaders.tsx
================================================
import React from 'react';
import { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';
import { RscEvent, isRscRequestEvent, isRscResponseEvent } from '../events';
import { useEndTime } from './EndTimeContext';
import { RequestDetailTabEmptyState } from './RequestDetailTabEmptyState';

export function RequestDetailTabHeaders({ events }: { events: RscEvent[] }) {
  const { endTime } = useEndTime();

  const timeFilteredEvents = eventsFilterByMaxTimestamp(events, endTime);

  if (timeFilteredEvents.length === 0) {
    return <RequestDetailTabEmptyState />;
  }

  const requestEvent = timeFilteredEvents.filter(isRscRequestEvent)[0];
  const responseEvent = timeFilteredEvents.filter(isRscResponseEvent)[0];

  return (
    <div className="flex flex-col gap-6">
      {requestEvent ? (
        <Table
          header="General Information"
          data={{
            'Request URL': requestEvent.data.url,
            'Request Method': requestEvent.data.method,
            ...(responseEvent
              ? { 'Status Code': responseEvent.data.status }
              : {}),
          }}
        />
      ) : (
        'No general information'
      )}

      {requestEvent ? (
        <Table header="Request Headers" data={requestEvent.data.headers} />
      ) : (
        'No response headers'
      )}

      {responseEvent ? (
        <Table header="Response Headers" data={responseEvent.data.headers} />
      ) : (
        'No response headers'
      )}
    </div>
  );
}

function Table({
  header,
  data,
}: {
  header: string;
  data: Record<string, string | number>;
}) {
  return (
    <details open className="max-w-2xl">
      <summary className="font-semibold">{header}</summary>
      <table className="ml-3 mt-1 w-full table-auto dark:text-white">
        <tbody>
          {Object.entries(data).map(([key, value]) => (
            <tr
              key={key}
              className="block border-y border-slate-300 py-1 first:border-t-0 last:border-b-0 dark:border-slate-600"
            >
              <td className="min-w-32 border-slate-500">{key}</td>
              <td className="whitespace-pre-wrap break-all">{value}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </details>
  );
}


================================================
FILE: packages/core/src/components/RequestDetailTabNetwork.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { RequestDetailTabNetwork } from './RequestDetailTabNetwork';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof RequestDetailTabNetwork> = {
  component: RequestDetailTabNetwork,
};

export default meta;
type Story = StoryObj<typeof RequestDetailTabNetwork>;

export const alvarDev: Story = {
  name: 'alvar.dev',
  render: () => {
    return <RequestDetailTabNetwork events={alvarDevExampleData} />;
  },
};

export const ghFredKissDev: Story = {
  name: 'gh.fredkiss.dev',
  render: () => {
    return <RequestDetailTabNetwork events={ghFredkissDevExampleData} />;
  },
};

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  render: () => {
    return <RequestDetailTabNetwork events={nextjsOrgExampleData} />;
  },
};


================================================
FILE: packages/core/src/components/RequestDetailTabNetwork.tsx
================================================
import {
  Chunk,
  Reference,
  createFlightResponse,
  isReference,
  processBinaryChunk,
} from '@rsc-parser/react-client';
import React, { Fragment, memo, useEffect, useRef, useState } from 'react';
import * as d3 from 'd3';
import { useEndTime } from './EndTimeContext';
import { RscEvent, isRscChunkEvent } from '../events';
import { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';
import { RequestDetailTabEmptyState } from './RequestDetailTabEmptyState';
import { isDev } from './isDev';

interface Node extends d3.SimulationNodeDatum {
  chunk: Chunk;
  isInTimeRange: boolean;
}

interface Link extends d3.SimulationLinkDatum<Node> {
  source: string;
  target: string;
  text: string;
}

export type Data = {
  nodes: Node[];
  links: Link[];
};

export const WIDTH = 120;
export const HEIGHT = 50;

function findReferencesInChunk(chunk: Chunk) {
  if (chunk.type !== 'model') {
    return [];
  }

  const references: Reference[] = [];

  function walk(data: unknown) {
    if (isReference(data)) {
      references.push(data);
      return;
    }

    if (Array.isArray(data)) {
      for (const value of data) {
        walk(value);
      }
      return;
    }

    if (typeof data == 'object' && data !== null) {
      for (const value of Object.values(data)) {
        walk(value);
      }
    }
  }

  walk(chunk.value);

  return references;
}

function getChunkById(chunks: Chunk[], id: string) {
  return chunks.find((chunk) => chunk.id === id);
}

function getLinks(chunks: Chunk[]) {
  const links: Link[] = [];

  for (const chunk of chunks) {
    if (!chunk) {
      return links;
    }

    const references = findReferencesInChunk(chunk);
    for (const reference of references) {
      // Get the time between the source and target
      const sourceChunk = getChunkById(chunks, chunk.id);
      const targetChunk = getChunkById(chunks, reference.id);

      const timeDiff = getTimeDifferenceBetweenChunks(sourceChunk, targetChunk);

      links.push({
        source: chunk.id,
        target: reference.id,
        text: timeDiff ? `${reference.type} ${timeDiff}ms` : reference.type,
      });
    }
  }

  // Only return links for which there are nodes
  return links.filter((link) =>
    chunks.find((chunk) => chunk.id === link.target),
  );
}

function getTimeDifferenceBetweenChunks(
  chunk1: Chunk | undefined,
  chunk2: Chunk | undefined,
) {
  if (!chunk1 || !chunk2) {
    return 0;
  }
  return chunk2.timestamp - chunk1.timestamp;
}

export function RequestDetailTabNetwork({ events }: { events: RscEvent[] }) {
  const { endTime } = useEndTime();

  if (
    eventsFilterByMaxTimestamp(events, endTime).filter(isRscChunkEvent)
      .length === 0
  ) {
    return <RequestDetailTabEmptyState />;
  }

  const flightResponse = createFlightResponse(isDev(events));
  for (const event of events.filter(isRscChunkEvent)) {
    flightResponse._currentTimestamp = event.data.timestamp;
    processBinaryChunk(flightResponse, Uint8Array.from(event.data.chunkValue));
  }

  const nodes: Node[] = flightResponse._chunks.map((chunk) => {
    return {
      chunk,
      isInTimeRange: chunk.timestamp <= endTime,
    };
  });

  const links = getLinks(flightResponse._chunks);

  const svgRef = useRef<SVGSVGElement>(null);
  const [svgWidth, setSvgWidth] = useState(0);
  const [svgHeight, setSvgHeight] = useState(0);
  useEffect(() => {
    if (!svgRef.current) {
      return;
    }
    const resizeObserver = new ResizeObserver(() => {
      if (!svgRef.current) {
        return;
      }
      setSvgWidth(svgRef.current.clientWidth);
      setSvgHeight(svgRef.current.clientHeight);
      simulation.current = undefined;
    });
    resizeObserver.observe(svgRef.current);
    return () => resizeObserver.disconnect();
  }, []);

  const [linksState, setLinksState] = useState<Link[]>([]);
  const [nodesState, setNodesState] = useState<Node[]>([]);

  const simulation = useRef<d3.Simulation<Node, Link>>(undefined);

  useEffect(() => {
    if (simulation.current) {
      return;
    }

    if (!svgRef.current) {
      return;
    }

    simulation.current = d3
      .forceSimulation(nodes)

      // list of forces we apply to get node positions
      .force(
        'link',
        d3.forceLink<Node, Link>(links).id((d) => d.chunk.id),
      )
      .force('collide', d3.forceCollide().radius((WIDTH + HEIGHT) / 2))
      .force('charge', d3.forceManyBody())
      .force('center', d3.forceCenter(svgWidth / 2, svgHeight / 2))
      .on('tick', () => {
        setLinksState([...links]);
        setNodesState([...nodes]);
      });
  }, [svgWidth, svgHeight, nodes, links]);

  const svgGroupRef = useRef<SVGGElement>(null);

  useEffect(() => {
    if (!svgRef.current || !svgGroupRef.current || !svgWidth || !svgHeight) {
      return;
    }

    const selection = d3.select(svgRef.current);
    selection.call(
      d3
        .zoom<SVGSVGElement, unknown>()
        .extent([
          [0, 0],
          [svgWidth, svgHeight],
        ])
        .scaleExtent([0.3, 8])
        .on('zoom', ({ transform }) => {
          if (!svgGroupRef.current) {
            return;
          }
          svgGroupRef.current.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
        }),
    );
  }, [svgRef.current, svgGroupRef.current, svgWidth, svgHeight]);

  return (
    <div className="h-[calc(100vh-250px)] w-full">
      <svg
        ref={svgRef}
        viewBox={`0 0 ${svgWidth} ${svgHeight}`}
        xmlns="http://www.w3.org/2000/svg"
        className="size-full select-none rounded-sm bg-slate-200 text-slate-400 dark:bg-slate-800 dark:text-slate-600"
      >
        <g cursor="grab" ref={svgGroupRef}>
          {linksState.map((link) => {
            // @ts-expect-error - d3 types are wrong
            const midX = (link.source.x + link.target.x) / 2;
            // @ts-expect-error - d3 types are wrong
            const midY = (link.source.y + link.target.y) / 2;

            return (
              <Fragment
                // @ts-expect-error - d3 types are wrong
                key={`link-${link.index}-${link.source.chunk.id}-${link.target.chunk.id}`}
              >
                <line
                  // @ts-expect-error - d3 types are wrong
                  x1={link.source.x}
                  // @ts-expect-error - d3 types are wrong
                  y1={link.source.y}
                  // @ts-expect-error - d3 types are wrong
                  x2={link.target.x}
                  // @ts-expect-error - d3 types are wrong
                  y2={link.target.y}
                  stroke="currentColor"
                />
                <foreignObject
                  x={midX - WIDTH / 2}
                  y={midY - HEIGHT / 4}
                  width={WIDTH}
                  height={HEIGHT / 2}
                >
                  <p className="text-center text-black dark:text-white">
                    {link.text}
                  </p>
                </foreignObject>
              </Fragment>
            );
          })}
          {nodesState.map((node) => (
            <foreignObject
              key={`node-${node.chunk.id}${JSON.stringify(node.chunk.originalValue)}`}
              // @ts-expect-error - d3 types are wrong
              x={node.x - WIDTH / 2}
              // @ts-expect-error - d3 types are wrong
              y={node.y - HEIGHT / 2}
              width={WIDTH}
              height={HEIGHT * 2}
            >
              <MemoizedChunk chunk={node.chunk} />
            </foreignObject>
          ))}
        </g>
      </svg>
    </div>
  );
}

const MemoizedChunk = memo(function Chunk({ chunk }: { chunk: Chunk }) {
  return (
    <div className="rounded-full bg-slate-300 px-2.5 py-1.5 text-center text-black dark:bg-slate-700 dark:text-white">
      <span className="font-semibold">{chunk.id}</span> {chunk.type}
    </div>
  );
});


================================================
FILE: packages/core/src/components/RequestDetailTabParsedPayload.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { RequestDetailTabParsedPayload } from './RequestDetailTabParsedPayload';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof RequestDetailTabParsedPayload> = {
  component: RequestDetailTabParsedPayload,
};

export default meta;
type Story = StoryObj<typeof RequestDetailTabParsedPayload>;

export const alvarDev: Story = {
  name: 'alvar.dev',
  render: () => {
    return <RequestDetailTabParsedPayload events={alvarDevExampleData} />;
  },
};

export const ghFredKissDev: Story = {
  name: 'gh.fredkiss.dev',
  render: () => {
    return <RequestDetailTabParsedPayload events={ghFredkissDevExampleData} />;
  },
};

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  render: () => {
    return <RequestDetailTabParsedPayload events={nextjsOrgExampleData} />;
  },
};


================================================
FILE: packages/core/src/components/RequestDetailTabParsedPayload.tsx
================================================
import React, { useState, useTransition } from 'react';
import {
  Disclosure,
  DisclosureContent,
  useDisclosureStore,
  TabList,
  Tab,
  TabPanel,
  useTabStore,
} from '@ariakit/react';
import { ErrorBoundary } from 'react-error-boundary';
import { GenericErrorBoundaryFallback } from './GenericErrorBoundaryFallback';
import {
  Chunk,
  createFlightResponse,
  processBinaryChunk,
} from '@rsc-parser/react-client';
import { FlightResponseChunkModule } from './FlightResponseChunkModule';
import { FlightResponseChunkHint } from './FlightResponseChunkHint';
import { FlightResponseChunkModel } from './FlightResponseChunkModel';
import { DownArrowIcon, RightArrowIcon } from './FlightResponseIcons';
import { FlightResponseChunkDebugInfo } from './FlightResponseChunkDebugInfo';
import { FlightResponseChunkText } from './FlightResponseChunkText';
import { FlightResponseChunkConsole } from './FlightResponseChunkConsole';
import { FlightResponseChunkUnknown } from './FlightResponseChunkUnknown';
import { useEndTime } from './EndTimeContext';
import { RscEvent, isRscChunkEvent } from '../events';
import { RequestDetailTabEmptyState } from './RequestDetailTabEmptyState';
import { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';
import { isDev } from './isDev';

export function RequestDetailTabParsedPayload({
  events,
}: {
  events: RscEvent[];
}) {
  const { endTime } = useEndTime();

  if (
    eventsFilterByMaxTimestamp(events, endTime).filter(isRscChunkEvent)
      .length === 0
  ) {
    return <RequestDetailTabEmptyState />;
  }

  const flightResponse = createFlightResponse(isDev(events));
  for (const event of events.filter(isRscChunkEvent)) {
    flightResponse._currentTimestamp = event.data.timestamp;
    processBinaryChunk(flightResponse, Uint8Array.from(event.data.chunkValue));
  }

  const [isPending, startTransition] = useTransition();
  const [selectedTab, setSelectedTab] = useState<string | null | undefined>(
    null,
  );
  const [currentTab, setCurrentTab] = useState<string | null | undefined>(null);

  const payloadSize = parseFloat(
    stringToKiloBytes(
      flightResponse._chunks
        .map((chunk) => {
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          const { _response, ...chunkWithoutResponse } = chunk;
          return JSON.stringify(chunkWithoutResponse);
        })
        .join(''),
    ),
  );

  const selectTab = (nextTab: string | null | undefined) => {
    if (nextTab !== selectedTab) {
      setSelectedTab(nextTab);
      startTransition(() => {
        setCurrentTab(nextTab);
      });
    }
  };

  const selectTabByID = (id: string) => {
    for (const chunk of flightResponse._chunks) {
      if (id === chunk.id) {
        // TODO: Don't hard-code this
        window.scrollTo(0, 680);
        tab.setSelectedId(String(chunk.id));
      }
    }
  };

  const tab = useTabStore({
    selectedId: selectedTab,
    setSelectedId: selectTab,
  });

  const timeFilteredChunks = flightResponse._chunks.filter(
    (chunk) => chunk.timestamp <= endTime,
  );

  return (
    <div className="divide-y-1 dark:divide-slate-600">
      {timeFilteredChunks.length === 0 ? null : (
        <>
          <div className="flex flex-col gap-2 pb-3">
            <div>Total size: {payloadSize} KB (uncompressed)</div>

            <TabList
              store={tab}
              className="flex flex-row flex-wrap gap-2 md:pb-0"
            >
              {timeFilteredChunks.map((chunk) => (
                <Tab
                  className="group rounded-md border-none text-left outline outline-2 outline-offset-2 outline-transparent transition-all duration-200"
                  key={`${chunk.id}-${JSON.stringify(chunk.originalValue)}`}
                  id={String(chunk.id)}
                >
                  <ErrorBoundary
                    fallbackRender={({ error }) => (
                      <ChunkTabFallback
                        error={error}
                        chunk={chunk}
                        payloadSize={payloadSize}
                      />
                    )}
                  >
                    <ChunkTab chunk={chunk} payloadSize={payloadSize} />
                  </ErrorBoundary>
                </Tab>
              ))}
            </TabList>
          </div>

          <TabPanel
            store={tab}
            tabId={currentTab}
            alwaysVisible={true}
            className="pt-3 delay-100 duration-200 flex flex-col gap-8"
            aria-label="Chunks"
            aria-busy={isPending}
            style={{
              opacity: isPending ? '0.6' : '1',
            }}
          >
            {timeFilteredChunks.length === 0 ? (
              <p>Please enter a payload to see results</p>
            ) : selectedTab === null || selectTab === undefined ? (
              <p>Please select a chunk</p>
            ) : null}

            {timeFilteredChunks
              .filter((chunk) => String(chunk.id) == currentTab)
              .map((chunk) => (
                <ErrorBoundary
                  FallbackComponent={GenericErrorBoundaryFallback}
                  key={`tab-panel-${chunk.id}-${JSON.stringify(chunk.originalValue)}`}
                >
                  <ChunkTabPanel
                    chunk={chunk}
                    payloadSize={payloadSize}
                    selectTabByID={selectTabByID}
                  />
                </ErrorBoundary>
              ))}
          </TabPanel>
        </>
      )}
    </div>
  );
}

function ChunkTab({
  chunk,
  payloadSize,
}: {
  chunk: Chunk;
  payloadSize: number;
}) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { _response, ...chunkWithoutResponse } = chunk;
  const chunkSize = parseFloat(
    stringToKiloBytes(JSON.stringify(chunkWithoutResponse)),
  );

  return (
    <div className="flex flex-row gap-1.5 rounded-md border-2 border-transparent bg-slate-200 px-2 py-0.5 transition-all duration-100 group-aria-selected:border-slate-400 dark:bg-slate-800 dark:group-aria-selected:border-slate-500">
      <div className="-mt-px text-xl font-semibold">{chunk.id}</div>
      <div className="flex flex-col items-start">
        <div className="whitespace-nowrap">{chunk.type}</div>
        <Meter fraction={chunkSize / payloadSize} />
      </div>
    </div>
  );
}

function ChunkTabFallback({
  error,
  chunk,
  payloadSize,
}: {
  error: Error;
  chunk: Chunk;
  payloadSize: number;
}) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { _response, ...chunkWithoutResponse } = chunk;
  const chunkSize = parseFloat(
    stringToKiloBytes(JSON.stringify(chunkWithoutResponse)),
  );

  if (error instanceof Error) {
    return (
      <div className="flex h-full flex-col rounded-md border-2 border-transparent bg-red-200 px-2 py-0.5 transition-all duration-200 group-aria-selected:border-red-600 group-aria-selected:text-white">
        <div>Error</div>
        <Meter fraction={chunkSize / payloadSize} />
      </div>
    );
  }

  return <span>Error</span>;
}

export function ChunkTabPanel({
  chunk,
  payloadSize,
  selectTabByID,
}: {
  chunk: Chunk;
  payloadSize: number;
  selectTabByID: (id: string) => void;
}) {
  return (
    <div className="flex flex-col divide-y-1 dark:divide-slate-600">
      <div className="flex flex-row justify-between pb-3">
        <ErrorBoundary
          FallbackComponent={GenericErrorBoundaryFallback}
          key={`meta-${chunk.id}`}
        >
          <ChunkTabPanelMeta chunk={chunk} />
        </ErrorBoundary>
        <ErrorBoundary
          FallbackComponent={GenericErrorBoundaryFallback}
          key={`size-${chunk.id}`}
        >
          <ChunkTabPanelSize chunk={chunk} payloadSize={payloadSize} />
        </ErrorBoundary>
      </div>

      <div className="py-3">
        <ErrorBoundary
          FallbackComponent={GenericErrorBoundaryFallback}
          key={`chunk-${chunk.id}`}
        >
          <ChunkTabPanelExplorer chunk={chunk} selectTabByID={selectTabByID} />
        </ErrorBoundary>
      </div>

      <div className="pt-3">
        <ErrorBoundary
          FallbackComponent={GenericErrorBoundaryFallback}
          key={`tree-${chunk.id}`}
        >
          <ChunkTabPanelGenericData chunk={chunk} />
        </ErrorBoundary>
      </div>
    </div>
  );
}

function ChunkTabPanelMeta({ chunk }: { chunk: Chunk }) {
  return (
    <div className="flex flex-col gap-1">
      <h3 className="inline-block rounded-md text-xl font-bold">{chunk.id}</h3>
      <h4 className="font-medium">{chunk.type}</h4>
    </div>
  );
}

function ChunkTabPanelSize({
  chunk,
  payloadSize,
}: {
  chunk: Chunk;
  payloadSize: number;
}) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { _response, ...chunkWithoutResponse } = chunk;
  const chunkSize = parseFloat(
    stringToKiloBytes(JSON.stringify(chunkWithoutResponse)),
  );

  return (
    <div className="text-right ">
      <div className="whitespace-nowrap">
        {chunkSize} KB chunk (uncompressed)
      </div>
      <div>{((chunkSize / payloadSize) * 100).toFixed(2)}% of total</div>
      <Meter fraction={chunkSize / payloadSize} />
    </div>
  );
}

function ChunkTabPanelExplorer({
  chunk,
  selectTabByID,
}: {
  chunk: Chunk;
  selectTabByID: (id: string) => void;
}) {
  switch (chunk.type) {
    case 'model': {
      return (
        <FlightResponseChunkModel
          data={chunk.value}
          onClickID={(id) => {
            selectTabByID(id);
          }}
        />
      );
    }
    case 'module': {
      return <FlightResponseChunkModule data={chunk.value} />;
    }
    case 'hint': {
      return <FlightResponseChunkHint data={chunk.value} />;
    }
    case 'text':
      return <FlightResponseChunkText data={chunk.value} />;
    case 'debugInfo':
      return (
        <FlightResponseChunkDebugInfo
          data={chunk.value}
          onClickID={(id) => {
            selectTabByID(id);
          }}
        />
      );
    case 'console': {
      return (
        <FlightResponseChunkConsole
          data={chunk.value}
          onClickID={(id) => {
            selectTabByID(id);
          }}
        />
      );
    }
    default: {
      return <FlightResponseChunkUnknown chunk={chunk} />;
    }
  }
}

function ChunkTabPanelGenericData({ chunk }: { chunk: Chunk }) {
  const [isOpen, setIsOpen] = useState(false);
  const [isPending, startTransition] = useTransition();
  const disclosure = useDisclosureStore({
    open: isOpen,
    setOpen: (open) => {
      startTransition(() => {
        setIsOpen(open);
      });
    },
  });

  return (
    <div className="flex flex-col gap-2">
      <Disclosure
        store={disclosure}
        style={{ opacity: isPending ? 0.7 : 1 }}
        className="flex cursor-pointer items-center gap-1"
      >
        {isOpen ? <DownArrowIcon /> : <RightArrowIcon />}
        Raw data
      </Disclosure>
      <DisclosureContent store={disclosure}>
        {isOpen ? <ChunkTabRawJson chunk={chunk} /> : null}
      </DisclosureContent>
    </div>
  );
}

function ChunkTabRawJson({ chunk }: { chunk: Chunk }) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { _response, ...chunkWithoutResponse } = chunk;

  return (
    <pre className="overflow-hidden whitespace-break-spaces break-all">
      {JSON.stringify(chunkWithoutResponse, null, 1)}
    </pre>
  );
}

function stringToKiloBytes(data: string) {
  return ((encodeURI(data).split(/%..|./).length - 1) / 1024).toFixed(2);
}

function Meter({ fraction }: { fraction: number }) {
  const percent = (fraction * 100).toFixed(2);

  return (
    <div
      className="h-2 w-14 bg-slate-300 dark:bg-slate-600 rounded-lg overflow-hidden"
      role="meter"
      aria-valuemin={0}
      aria-valuenow={Number.parseInt(percent)}
      aria-valuemax={100}
    >
      <div
        className="bg-slate-600 dark:bg-white h-full w-4"
        style={{
          width: `${percent}%`,
        }}
      />
      <span className="sr-only">{percent}%</span>
    </div>
  );
}


================================================
FILE: packages/core/src/components/RequestDetailTabRawPayload.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { RequestDetailTabRawPayload } from './RequestDetailTabRawPayload';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof RequestDetailTabRawPayload> = {
  component: RequestDetailTabRawPayload,
};

export default meta;
type Story = StoryObj<typeof RequestDetailTabRawPayload>;

export const alvarDev: Story = {
  name: 'alvar.dev',
  render: () => {
    return <RequestDetailTabRawPayload events={alvarDevExampleData} />;
  },
};

export const ghFredkissDev: Story = {
  name: 'gh.fredkiss.dev',
  render: () => {
    return <RequestDetailTabRawPayload events={ghFredkissDevExampleData} />;
  },
};

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  render: () => {
    return <RequestDetailTabRawPayload events={nextjsOrgExampleData} />;
  },
};


================================================
FILE: packages/core/src/components/RequestDetailTabRawPayload.tsx
================================================
import React from 'react';
import { useEndTime } from './EndTimeContext';
import { RscEvent, isRscChunkEvent } from '../events';
import { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';
import { RequestDetailTabEmptyState } from './RequestDetailTabEmptyState';

const textDecoder = new TextDecoder();

export function RequestDetailTabRawPayload({ events }: { events: RscEvent[] }) {
  const { endTime } = useEndTime();

  const filteredEvents = eventsFilterByMaxTimestamp(events, endTime).filter(
    isRscChunkEvent,
  );
  if (filteredEvents.length === 0) {
    return <RequestDetailTabEmptyState />;
  }

  return (
    <ul className="flex flex-col gap-4 font-code">
      {filteredEvents.map((event) => {
        const text = textDecoder.decode(Uint8Array.from(event.data.chunkValue));

        return (
          <li
            key={
              event.data.requestId +
              event.data.timestamp +
              text.substring(0, 10)
            }
          >
            <pre className="w-full whitespace-break-spaces break-all">
              {text}
            </pre>
          </li>
        );
      })}
    </ul>
  );
}


================================================
FILE: packages/core/src/components/RequestDetailTabTimings.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { RequestDetailTabTimings } from './RequestDetailTabTimings';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof RequestDetailTabTimings> = {
  component: RequestDetailTabTimings,
};

export default meta;
type Story = StoryObj<typeof RequestDetailTabTimings>;

export const alvarDev: Story = {
  name: 'alvar.dev',
  render: () => {
    return <RequestDetailTabTimings events={alvarDevExampleData} />;
  },
};

export const ghFredKissDev: Story = {
  name: 'gh.fredkiss.dev',
  render: () => {
    return <RequestDetailTabTimings events={ghFredkissDevExampleData} />;
  },
};

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  render: () => {
    return <RequestDetailTabTimings events={nextjsOrgExampleData} />;
  },
};


================================================
FILE: packages/core/src/components/RequestDetailTabTimings.tsx
================================================
import React from 'react';
import { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';
import {
  RscEvent,
  isRscChunkEvent,
  isRscRequestEvent,
  isRscResponseEvent,
} from '../events';
import { useEndTime } from './EndTimeContext';
import { RequestDetailTabEmptyState } from './RequestDetailTabEmptyState';
import { getColorForFetch } from '../color';

export function RequestDetailTabTimings({ events }: { events: RscEvent[] }) {
  const { endTime } = useEndTime();

  const timeFilteredEvents = eventsFilterByMaxTimestamp(events, endTime);

  if (timeFilteredEvents.length === 0) {
    return <RequestDetailTabEmptyState />;
  }

  const requestEvent = timeFilteredEvents.filter(isRscRequestEvent)[0];
  const responseEvent = timeFilteredEvents.filter(isRscResponseEvent)[0];

  if (!requestEvent) {
    return <RequestDetailTabEmptyState />;
  }

  const startTimeStamp = requestEvent.data.timestamp;
  const endTimestamp = events.at(-1)?.data.timestamp;

  if (!endTimestamp) {
    return <RequestDetailTabEmptyState />;
  }

  const timings: {
    name: string;
    offset: number | null;
    color: string;
  }[] = [];

  timings.push({
    name: 'Request start',
    offset: startTimeStamp - startTimeStamp,
    color: getColorForFetch('1'),
  });

  if (responseEvent) {
    timings.push({
      name: 'Response start',
      offset: responseEvent.data.timestamp - startTimeStamp,
      color: getColorForFetch('20'),
    });

    timeFilteredEvents.filter(isRscChunkEvent).forEach((event, index) => {
      timings.push({
        name: `Chunk ${index}`,
        offset: event.data.timestamp - startTimeStamp,
        color: getColorForFetch('30'),
      });
    });

    if (
      timeFilteredEvents.find((event) => event.data.timestamp === endTimestamp)
    ) {
      timings.push({
        name: 'Response end',
        offset: endTimestamp ? endTimestamp - startTimeStamp : null,
        color: getColorForFetch('20'),
      });
    }
  }

  const maxOffset = (endTimestamp ?? 500) - startTimeStamp;

  return (
    <ul className="flex max-w-lg flex-col gap-3">
      {timings.map((timing) => {
        return (
          <li key={timing.name} className="flex flex-col gap-0">
            <p>{timing.name}</p>
            <div className="flex flex-row items-center gap-2">
              <div
                className="h-3 rounded-full"
                style={{
                  background: timing.color,
                  width: getWidthByOffset(timing.offset, maxOffset),
                }}
              />
              {timing.offset !== null ? <p>{timing.offset}ms</p> : <p>-</p>}
            </div>
          </li>
        );
      })}
    </ul>
  );
}

function getWidthByOffset(offset: number | null, biggestOffset: number) {
  if (offset === null) {
    return '100%';
  }

  if (offset === 0) {
    return '3px';
  }

  return `${(offset / biggestOffset) * 100}%`;
}


================================================
FILE: packages/core/src/components/TimeScrubber.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { TimeScrubber } from './TimeScrubber';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';
import { EndTimeProvider } from './EndTimeContext';

const meta: Meta<typeof TimeScrubber> = {
  component: TimeScrubber,
};

export default meta;
type Story = StoryObj<typeof TimeScrubber>;

export const alvarDev: Story = {
  name: 'alvar.dev',
  render: () => {
    return (
      <EndTimeProvider maxEndTime={Infinity}>
        <TimeScrubber
          minStartTime={0}
          maxEndTime={Infinity}
          events={alvarDevExampleData}
        />
        ;
      </EndTimeProvider>
    );
  },
};

export const ghFredKissDev: Story = {
  name: 'gh.fredkiss.dev',
  render: () => {
    return (
      <EndTimeProvider maxEndTime={Infinity}>
        <TimeScrubber
          minStartTime={0}
          maxEndTime={Infinity}
          events={ghFredkissDevExampleData}
        />
        ;
      </EndTimeProvider>
    );
  },
};

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  render: () => {
    return (
      <EndTimeProvider maxEndTime={Infinity}>
        <TimeScrubber
          minStartTime={0}
          maxEndTime={Infinity}
          events={nextjsOrgExampleData}
        />
        ;
      </EndTimeProvider>
    );
  },
};


================================================
FILE: packages/core/src/components/TimeScrubber.tsx
================================================
import React, { useMemo } from 'react';
import { RscEvent } from '../events';
import { getColorForFetch } from '../color';
import { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';
import { useEndTime } from './EndTimeContext';

function useTracks(events: RscEvent[]) {
  return useMemo(() => {
    const tracks: Array<Array<RscEvent>> = [[]];

    for (const event of events) {
      // Find a track that doesn't overlap with the current event
      const track = tracks.find((track) => {
        const lastEvent = track[track.length - 1];

        if (!lastEvent) {
          return true;
        }

        if (lastEvent.data.requestId === event.data.requestId) {
          return true;
        }

        const lastEventWithSameRequestId = events
          .filter((m) => m.data.requestId === lastEvent.data.requestId)
          .at(-1);

        if (
          lastEvent.data.requestId !== event.data.requestId &&
          typeof lastEventWithSameRequestId !== 'undefined' &&
          lastEventWithSameRequestId.data.timestamp < event.data.timestamp
        ) {
          return true;
        }

        return false;
      });

      if (track) {
        track.push(event);
      } else {
        tracks.push([event]);
      }
    }

    return tracks;
  }, [events]);
}

export function TimeScrubber({
  events,
  minStartTime,
  maxEndTime,
}: {
  events: RscEvent[];
  minStartTime: number;
  maxEndTime: number;
}) {
  const { endTime, visibleEndTime, changeEndTime, isPending } = useEndTime();

  const filteredEvents = eventsFilterByMaxTimestamp(events, endTime);
  const tracks = useTracks(events);

  const eventHeight = 12;
  const trackSpacing = 4;
  const trackPadding = 8;

  return (
    <div className="flex w-full flex-col gap-1.5 rounded-md bg-slate-200 p-1.5 dark:bg-slate-800">
      <div className="relative flex flex-row items-center transition-opacity delay-75 duration-100">
        <input
          type="range"
          className={[
            'absolute h-full w-full rounded-sm z-20',
            'appearance-none',
            'bg-transparent bg-linear-to-r from-blue-100/25 to-blue-100/25 dark:from-blue-100/10 dark:to-blue-100/10 bg-no-repeat',
            '[&::-webkit-slider-runnable-track]:bg-transparent',
            '[&::-webkit-slider-runnable-track]:h-full',
            '[&::-webkit-slider-thumb]:h-[calc(100%-6px)]',
            '[&::-webkit-slider-thumb]:mt-[calc(6px/2)]',
            '[&::-webkit-slider-thumb]:w-1',
            '[&::-webkit-slider-thumb]:appearance-none',
            '[&::-webkit-slider-thumb]:rounded-md',
            '[&::-webkit-slider-thumb]:transition-colors',
            '[&::-webkit-slider-thumb]:delay-75',
            '[&::-webkit-slider-thumb]:duration-100',
            isPending
              ? '[&::-webkit-slider-thumb]:bg-blue-300'
              : '[&::-webkit-slider-thumb]:bg-blue-500',
            '[&::-webkit-slider-thumb]:z-20',
          ].join(' ')}
          min={minStartTime}
          max={maxEndTime}
          value={visibleEndTime}
          style={{
            backgroundSize:
              ((visibleEndTime - minStartTime) * 100) /
                (maxEndTime - minStartTime) +
              '% 100%',
          }}
          onChange={(event) => {
            const numberValue = Number.parseFloat(event.target.value);
            changeEndTime(numberValue);
          }}
        ></input>

        <svg
          xmlns="http://www.w3.org/2000/svg"
          version="1.1"
          width="100%"
          height={`${
            tracks.length *
              (eventHeight + (tracks.length > 1 ? trackSpacing : 0)) +
            trackPadding * 2
          }px`}
          className="pointer-events-none z-10 rounded-sm bg-white fill-slate-500 dark:bg-slate-700"
        >
          {tracks.map((track, idx) => {
            const sections: {
              requestId: string;
              startTime: number;
              endTime: number;
            }[] = [];

            if (track.length === 0) {
              return null;
            }

            let currentRequestId: string | undefined = undefined;
            for (const event of track) {
              if (currentRequestId === event.data.requestId) {
                continue;
              }

              currentRequestId = event.data.requestId;

              const eventsWithSameRequestId = track.filter(
                (event) => event.data.requestId === currentRequestId,
              );
              const lastEvent = eventsWithSameRequestId.at(-1);

              if (!lastEvent) {
                return null;
              }

              sections.push({
                requestId: currentRequestId,
                startTime: event.data.timestamp,
                endTime: lastEvent.data.timestamp,
              });
            }

            return sections.map((section) => {
              const x =
                ((section.startTime - minStartTime) /
                  (maxEndTime - minStartTime)) *
                100;

              const y = idx * (eventHeight + trackSpacing) + trackPadding;

              const width =
                ((section.endTime - section.startTime) /
                  (maxEndTime - minStartTime)) *
                100;

              return (
                <rect
                  key={section.requestId}
                  x={`${x * 0.98 + 1}%`}
                  y={`${y}px`}
                  width={width > 0.2 ? `${width}%` : '0.2%'}
                  height={eventHeight}
                  fill={getColorForFetch(section.requestId)}
                  rx="1"
                />
              );
            });
          })}
        </svg>
      </div>

      <div className="flex flex-row justify-between px-1">
        <div className="tabular-nums text-slate-700 dark:text-slate-300">
          {new Date(visibleEndTime).toLocaleTimeString()} /{' '}
          {new Date(maxEndTime).toLocaleTimeString()}
        </div>

        <div className="whitespace-nowrap tabular-nums text-slate-700 dark:text-slate-300">
          {String(filteredEvents.length).padStart(
            String(events.length).length,
            '0',
          )}{' '}
          / {events.length}
        </div>
      </div>
    </div>
  );
}


================================================
FILE: packages/core/src/components/ViewerPayload.stories.tsx
================================================
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import { ViewerPayload } from './ViewerPayload';
import { RscEvent, isRscChunkEvent, isRscRequestEvent } from '../events';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof ViewerPayload> = {
  component: ViewerPayload,
};

export default meta;
type Story = StoryObj<typeof ViewerPayload>;

function getUrlsAndRequestIds(events: RscEvent[]) {
  return Array.from(
    new Set(
      events.filter(isRscRequestEvent).map((event) => ({
        url: event.data.url,
        requestId: event.data.requestId,
      })),
    ),
  );
}

function getPayloadByRequestId(events: RscEvent[], requestId: string) {
  return events
    .filter(isRscChunkEvent)
    .filter((event) => event.data.requestId === requestId)
    .map((event) =>
      new TextDecoder().decode(Uint8Array.from(event.data.chunkValue)),
    )
    .join('');
}

export const alvarDev: Story = {
  name: 'alvar.dev',
  argTypes: {
    defaultPayload: {
      options: getUrlsAndRequestIds(alvarDevExampleData).map(({ url }) => url),
      mapping: getUrlsAndRequestIds(alvarDevExampleData).reduce(
        (acc, { url, requestId }) => {
          acc[url] = getPayloadByRequestId(alvarDevExampleData, requestId);
          return acc;
        },
        {} as Record<string, string>,
      ),
      control: {
        type: 'select',
      },
    },
  },
  args: {
    defaultPayload: getPayloadByRequestId(
      alvarDevExampleData,
      getUrlsAndRequestIds(alvarDevExampleData)[0].requestId,
    ),
  },
  render: ({ defaultPayload }) => (
    <ViewerPayload defaultPayload={defaultPayload} key={defaultPayload} />
  ),
};

export const ghFredkissDev: Story = {
  name: 'gh.fredkiss.dev',
  argTypes: {
    defaultPayload: {
      options: getUrlsAndRequestIds(ghFredkissDevExampleData).map(
        ({ url }) => url,
      ),
      mapping: getUrlsAndRequestIds(ghFredkissDevExampleData).reduce(
        (acc, { url, requestId }) => {
          acc[url] = getPayloadByRequestId(ghFredkissDevExampleData, requestId);
          return acc;
        },
        {} as Record<string, string>,
      ),
      control: {
        type: 'select',
      },
    },
  },
  args: {
    defaultPayload: getPayloadByRequestId(
      ghFredkissDevExampleData,
      getUrlsAndRequestIds(ghFredkissDevExampleData)[0].requestId,
    ),
  },
  render: ({ defaultPayload }) => (
    <ViewerPayload defaultPayload={defaultPayload} key={defaultPayload} />
  ),
};

export const nextJsOrg: Story = {
  name: 'nextjs.org',
  argTypes: {
    defaultPayload: {
      options: getUrlsAndRequestIds(nextjsOrgExampleData).map(({ url }) => url),
      mapping: getUrlsAndRequestIds(nextjsOrgExampleData).reduce(
        (acc, { url, requestId }) => {
          acc[url] = getPayloadByRequestId(nextjsOrgExampleData, requestId);
          return acc;
        },
        {} as Record<string, string>,
      ),
      control: {
        type: 'select',
      },
    },
  },
  args: {
    defaultPayload: getPayloadByRequestId(
      nextjsOrgExampleData,
      getUrlsAndRequestIds(nextjsOrgExampleData)[0].requestId,
    ),
  },
  render: ({ defaultPayload }) => (
    <ViewerPayload defaultPayload={defaultPayload} key={defaultPayload} />
  ),
};


================================================
FILE: packages/core/src/components/ViewerPayload.tsx
================================================
import React, { ChangeEvent, useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

import { GenericErrorBoundaryFallback } from './GenericErrorBoundaryFallback';
import { RscChunkEvent } from '../events';
import { EndTimeProvider } from './EndTimeContext';
import { RequestDetail } from './RequestDetail';

export function ViewerPayload({ defaultPayload }: { defaultPayload: string }) {
  const [payload, setPayload] = useState(defaultPayload);

  useEffect(() => {
    const previous = localStorage.getItem('payload');
    setPayload(previous ?? defaultPayload);
  }, []);

  return (
    <div className="flex flex-col items-center gap-6">
      <form className="flex w-full flex-col gap-2">
        <label htmlFor="payload" className="font-medium">
          Payload
        </label>

        <textarea
          name="payload"
          placeholder="RCS payload"
          className="resize-none rounded-md bg-slate-200 p-3 dark:bg-slate-800 dark:text-slate-200"
          rows={12}
          value={payload}
          onChange={(event: ChangeEvent<HTMLTextAreaElement>) => {
            setPayload(event.target.value);
            localStorage.setItem('payload', event.target.value);
          }}
          spellCheck="false"
        />
      </form>
      <div className="w-full">
        <ErrorBoundary
          FallbackComponent={GenericErrorBoundaryFallback}
          key={payload}
        >
          <Viewer payload={payload} />
        </ErrorBoundary>
      </div>
    </div>
  );
}

export function Viewer({ payload }: { payload: string }) {
  const events = [
    {
      type: 'RSC_CHUNK',
      data: {
        tabId: 0,
        requestId: '0',
        timestamp: 0,
        chunkValue: Array.from(new TextEncoder().encode(payload)),
      },
    } satisfies RscChunkEvent,
  ];

  return (
    <EndTimeProvider maxEndTime={Infinity}>
      <RequestDetail events={events} />
    </EndTimeProvider>
  );
}


================================================
FILE: packages/core/src/components/ViewerStreams.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { ViewerStreams } from './ViewerStreams';
import { alvarDevExampleData } from '../example-data/alvar-dev';
import { ghFredkissDevExampleData } from '../example-data/gh-fredkiss-dev';
import { nextjsOrgExampleData } from '../example-data/nextjs-org';

const meta: Meta<typeof ViewerStreams> = {
  component: ViewerStreams,
};

export default meta;
type Story = StoryObj<typeof ViewerStreams>;

export const nextjsOrg: Story = {
  name: 'nextjs.org',
  args: {
    events: nextjsOrgExampleData,
  },
};

export const ghFredkissDEv: Story = {
  name: 'gh.fredkiss.dev',
  args: {
    events: ghFredkissDevExampleData,
  },
};

export const alvarDev: Story = {
  name: 'alvar.dev',
  args: {
    events: alvarDevExampleData,
  },
};


================================================
FILE: packages/core/src/components/ViewerStreams.tsx
================================================
import React from 'react';
import { TabList, Tab, TabPanel } from '@ariakit/react';
import { RscEvent, isRscRequestEvent, isRscResponseEvent } from '../events';
import { TimeScrubber } from './TimeScrubber';
import { EndTimeProvider, useEndTime } from './EndTimeContext';
import {
  eventsFilterByMaxTimestamp,
  eventsFilterByRequestId,
  eventsGetMinMaxTimestamps,
  eventsSortByTimestamp,
  eventsUniqueRequestIds,
} from '../eventArrayHelpers';
import { useTabStoreWithTransitions } from './useTabStoreWithTransitions';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { getColorForFetch } from '../color';
import { RequestDetail } from './RequestDetail';

export function ViewerStreams({ events }: { events: RscEvent[] }) {
  const { minStartTime, maxEndTime } = eventsGetMinMaxTimestamps(events);

  return (
    <EndTimeProvider maxEndTime={maxEndTime}>
      <div className="flex flex-col gap-4 dark:text-white">
        <TimeScrubber
          events={events}
          minStartTime={minStartTime}
          maxEndTime={maxEndTime}
        />

        <Requests events={events} />
      </div>
    </EndTimeProvider>
  );
}

function Requests({ events }: { events: RscEvent[] }) {
  const { endTime } = useEndTime();
  const { currentTab, isPending, tabStore } =
    useTabStoreWithTransitions(undefined);

  const sortedEvents = eventsSortByTimestamp(events);
  const timeFilteredEvents = eventsFilterByMaxTimestamp(sortedEvents, endTime);
  const tabs = eventsUniqueRequestIds(events);

  return (
    <PanelGroup direction="horizontal">
      <Panel id="sidebar" minSize={20} order={1} defaultSize={35}>
        <TabList store={tabStore} className="flex flex-col gap-2 pr-3">
          {tabs.map((tab) => {
            return (
              <Tab key={tab} id={tab} store={tabStore} className="group">
                <RequestTab
                  events={eventsFilterByRequestId(timeFilteredEvents, tab)}
                />
              </Tab>
            );
          })}
        </TabList>
      </Panel>

      <PanelResizeHandle className="w-1 rounded-sm bg-slate-200 dark:bg-slate-800" />

      <Panel order={2} minSize={20} className="">
        <TabPanel
          store={tabStore}
          tabId={currentTab}
          alwaysVisible={true}
          className={`flex min-w-0 grow pl-3 transition-opacity delay-75 duration-100 ${
            isPending ? 'opacity-60' : ''
          }`}
          aria-label="Paths"
          aria-busy={isPending}
        >
          {!currentTab ? (
            <span>Please select a url</span>
          ) : (
            <RequestDetail
              key={currentTab}
              events={eventsFilterByRequestId(sortedEvents, currentTab)}
            />
          )}
        </TabPanel>
      </Panel>
    </PanelGroup>
  );
}

function RequestTab({ events }: { events: RscEvent[] }) {
  const [requestEvent] = events.filter(isRscRequestEvent);
  const [responseEvent] = events.filter(isRscResponseEvent);

  if (!requestEvent) {
    return null;
  }

  const { method, url } = requestEvent.data;
  const { status } = responseEvent?.data ?? {};

  return (
    <div className="flex w-full flex-row items-center gap-2 rounded-md border-none px-1.5 py-1 text-left group-aria-selected:bg-slate-200 dark:group-aria-selected:bg-slate-700">
      <div
        className="h-5 min-h-5 w-1 min-w-1 rounded-md"
        style={{
          background: getColorForFetch(events[0].data.requestId),
        }}
      ></div>
      <div className="flex flex-row gap-1">
        <span className="inline-block min-w-[4ch] font-medium text-slate-500 dark:text-slate-400">
          {method} {status !== 200 && status !== undefined ? `(${status})` : ''}
        </span>
        <div>
          <span className="text-slate-900 dark:text-white">
            {new URL(url).pathname}
          </span>
          <span className="text-slate-500 dark:text-slate-400">
            {new URL(url).search}
          </span>
        </div>
      </div>
    </div>
  );
}


================================================
FILE: packages/core/src/components/ViewerStreamsEmptyState.stories.tsx
================================================
import type { Meta, StoryObj } from '@storybook/react-vite';

import { ViewerStreamsEmptyState } from './ViewerStreamsEmptyState';

const meta: Meta<typeof ViewerStreamsEmptyState> = {
  component: ViewerStreamsEmptyState,
};

export default meta;
type Story = StoryObj<typeof ViewerStreamsEmptyState>;

export const example: Story = {
  name: 'example',
};


================================================
FILE: packages/core/src/components/ViewerStreamsEmptyState.tsx
================================================
import React from 'react';

export function ViewerStreamsEmptyState() {
  return (
    <p className="dark:text-white">
      No data yet. Please start recording and navigate the page.
    </p>
  );
}


================================================
FILE: packages/core/src/components/isDev.ts
================================================
import { RscEvent } from '../events';

export function isDev(events: RscEvent[]) {
  return events.some(
    (event) =>
      event.type === 'RSC_REQUEST' &&
      (event.data.url.includes('localhost') ||
        event.data.url.includes('127.0.0.1')),
  );
}


================================================
FILE: packages/core/src/components/useTabStoreWithTransitions.ts
================================================
import { useTabStore } from '@ariakit/react';
import { useState, useTransition } from 'react';

export function useTabStoreWithTransitions(
  useTabStoreArgs: Parameters<typeof useTabStore>[0],
) {
  const [isPending, startTransition] = useTransition();
  const [selectedTab, setSelectedTab] = useState<string | null | undefined>(
    useTabStoreArgs?.defaultSelectedId ?? null,
  );
  const [currentTab, setCurrentTab] = useState<string | null | undefined>(
    useTabStoreArgs?.defaultSelectedId ?? null,
  );

  const selectTab = (nextTab: string | null | undefined) => {
    if (nextTab !== selectedTab) {
      setSelectedTab(nextTab);
      startTransition(() => {
        setCurrentTab(nextTab);
      });
    }
  };

  const tabStore = useTabStore({
    selectedId: selectedTab,
    setSelectedId: selectTab,
    ...useTabStoreArgs,
  });

  return {
    isPending,
    currentTab,
    tabStore,
  };
}


================================================
FILE: packages/core/src/copyEventsToClipboard.ts
================================================
import { RscEvent } from './events';

export function copyEventsToClipboard({ events }: { events: RscEvent[] }) {
  const stingifiedEvents = JSON.stringify(events);
  console.log(stingifiedEvents);
  const input = document.createElement('input');
  input.style = 'position: absolute; left: -1000px; top: -1000px';
  input.value = stingifiedEvents;
  document.body.appendChild(input);
  input.select();
  document.execCommand('copy');
  document.body.removeChild(input);
  alert('Copied events to clipboard');
}


================================================
FILE: packages/core/src/eventArrayHelpers.ts
================================================
import { RscEvent } from './events';

export function eventsSortByTimestamp(events: RscEvent[]) {
  return events.sort((a, b) => a.data.timestamp - b.data.timestamp);
}

export function eventsFilterByMaxTimestamp(
  events: RscEvent[],
  endTime: number,
) {
  return events.filter((event) => event.data.timestamp <= endTime);
}

export function eventsGetMinMaxTimestamps(events: RscEvent[]) {
  let minStartTime = Number.MAX_SAFE_INTEGER;
  let maxEndTime = 0;

  for (const event of events) {
    minStartTime = Math.min(minStartTime, event.data.timestamp);
    maxEndTime = Math.max(maxEndTime, event.data.timestamp);
  }

  return {
    minStartTime,
    maxEndTime,
  };
}

export function eventsUniqueRequestIds(events: RscEvent[]) {
  const requestIds: string[] = [];

  for (const event of events) {
    if (requestIds.includes(event.data.requestId)) {
      continue;
    }

    requestIds.push(event.data.requestId);
  }

  return requestIds;
}

export function eventsFilterByRequestId(events: RscEvent[], requestId: string) {
  return events.filter((event) => event.data.requestId === requestId);
}


================================================
FILE: packages/core/src/events.ts
================================================
export type StartRecordingEvent = {
  type: 'START_RECORDING';
  data: {
    tabId: number;
  };
};

export function isStartRecordingEvent(
  event: unknown,
): event is StartRecordingEvent {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'START_RECORDING'
  );
}

export type StopRecordingEvent = {
  type: 'STOP_RECORDING';
  data: {
    tabId: number;
  };
};

export function isStopRecordingEvent(
  event: unknown,
): event is StopRecordingEvent {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'STOP_RECORDING'
  );
}

export type ReadNextJsScriptTagsEvent = {
  type: 'READ_NEXT_JS_SCRIPT_TAGS';
  data: {
    tabId: number;
  };
};

export function isReadNextJsScriptTagsEvent(
  event: unknown,
): event is ReadNextJsScriptTagsEvent {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'READ_NEXT_JS_SCRIPT_TAGS'
  );
}

type RscEventSharedData = {
  data: {
    tabId: number;
    requestId: string;
    timestamp: number;
  };
};

export type RscRequestEvent = RscEventSharedData & {
  type: 'RSC_REQUEST';
  data: {
    url: string;
    method: string;
    headers: Record<string, string>;
  };
};

export function isRscRequestEvent(event: unknown): event is RscRequestEvent {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'RSC_REQUEST'
  );
}

export type RscResponseEvent = RscEventSharedData & {
  type: 'RSC_RESPONSE';
  data: {
    status: number;
    headers: Record<string, string>;
  };
};

export function isRscResponseEvent(event: unknown): event is RscResponseEvent {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'RSC_RESPONSE'
  );
}

export type RscChunkEvent = RscEventSharedData & {
  type: 'RSC_CHUNK';
  data: {
    chunkValue: number[];
  };
};

export function isRscChunkEvent(event: unknown): event is RscChunkEvent {
  return (
    typeof event === 'object' &&
    event !== null &&
    'type' in event &&
    event.type === 'RSC_CHUNK'
  );
}

export type RscEvent = RscRequestEvent | RscResponseEvent | RscChunkEvent;

export function isRscEvent(event: unknown): event is RscEvent {
  return (
    isRscRequestEvent(event) ||
    isRscResponseEvent(event) ||
    isRscChunkEvent(event)
  );
}

export type Event = StartRecordingEvent | StopRecordingEvent | RscEvent;

export function isEvent(event: unknown): event is Event {
  return (
    isStartRecordingEvent(event) ||
    isStopRecordingEvent(event) ||
    isRscEvent(event) ||
    isReadNextJsScriptTagsEvent(event)
  );
}


================================================
FILE: packages/core/src/example-data/alvar-dev.ts
================================================
import { RscEvent } from '../events';

export const alvarDevExampleData: RscEvent[] = [
  {
    type: 'RSC_REQUEST',
    data: {
      requestId: '1717949834954.4883',
      tabId: 1717949833446,
      timestamp: 1717949834954,
      url: 'https://www.alvar.dev/about?_rsc=1mv7y',
      method: 'GET',
      headers: {
        RSC: '1',
        'Next-Router-State-Tree':
          '%5B%22%22%2C%7B%22children%22%3A%5B%22(main)%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2C%22%2F%22%2C%22refresh%22%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D',
        'Next-Url': '/',
      },
    },
  },
  {
    type: 'RSC_RESPONSE',
    data: {
      requestId: '1717949834954.4883',
      tabId: 1717949833446,
      timestamp: 1717949835187,
      status: 200,
      headers: {
        age: '0',
        'cache-control':
          'private, no-cache, no-store, max-age=0, must-revalidate',
        'content-encoding': 'gzip',
        'content-type': 'text/x-component',
        date: 'Sun, 09 Jun 2024 16:17:15 GMT',
        server: 'Vercel',
        'strict-transport-security': 'max-age=63072000',
        vary: 'RSC, Next-Router-State-Tree, Next-Router-Prefetch',
        'x-matched-path': '/about.rsc',
        'x-vercel-cache': 'MISS',
        'x-vercel-id': 'arn1::fra1::wm5t8-1717949835046-6a231fae1f9b',
      },
    },
  },
  {
    type: 'RSC_CHUNK',
    data: {
      requestId: '1717949834954.4883',
      tabId: 1717949833446,
      timestamp: 1717949835188,
      chunkValue: [
        50, 58, 73, 91, 53, 52, 48, 54, 44, 91, 34, 56, 54, 51, 48, 34, 44, 34,
        115, 116, 97, 116, 105, 99, 47, 99, 104, 117, 110, 107, 115, 47, 56, 54,
        51, 48, 45, 54, 57, 102, 56, 102, 97, 54, 51, 48, 55, 51, 52, 98, 49,
        49, 97, 46, 106, 115, 63, 100, 112, 108, 61, 100, 112, 108, 95, 68, 122,
        67, 97, 117, 88, 85, 114, 101, 101, 111, 121, 52, 82, 100, 115, 81, 76,
        103, 67, 90, 112, 89, 65, 81, 84, 50, 70, 34, 44, 34, 53, 54, 54, 34,
        44, 34, 115, 116, 97, 116, 105, 99, 47, 99, 104, 117, 110, 107, 115, 47,
        53, 54, 54, 45, 100, 99, 48, 55, 56, 48, 56, 50, 98, 48, 99, 51, 48, 97,
        52, 100, 46, 106, 115, 63, 100, 112, 108, 61, 100, 112, 108, 95, 68,
        122, 67, 97, 117, 88, 85, 114, 101, 101, 111, 121, 52, 82, 100, 115, 81,
        76, 103, 67, 90, 112, 89, 65, 81, 84, 50, 70, 34, 44, 34, 54, 49, 48,
        55, 34, 44, 34, 115, 116, 97, 116, 105, 99, 47, 99, 104, 117, 110, 107,
        115, 47, 54, 49, 48, 55, 45, 98, 49, 55, 49, 98, 102, 53, 102, 50, 101,
        102, 52, 56, 48, 98, 102, 46, 106, 115, 63, 100, 112, 108, 61, 100, 112,
        108, 95, 68, 122, 67, 97, 117, 88, 85, 114, 101, 101, 111, 121, 52, 82,
        100, 115, 81, 76, 103, 67, 90, 112, 89, 65, 81, 84, 50, 70, 34, 44, 34,
        50, 50, 49, 52, 34, 44, 34, 115, 116, 97, 116, 105, 99, 47, 99, 104,
        117, 110, 107, 115, 47, 97, 112, 112, 47, 40, 109, 97, 105, 110, 41, 47,
        97, 98, 111, 117, 116, 47, 112, 97, 103, 101, 45, 51, 54, 51, 101, 51,
        57, 98, 51, 57, 101, 102, 48, 100, 55, 51, 56, 46, 106, 115, 63, 100,
        112, 108, 61, 100, 112, 108, 95, 68, 122, 67, 97, 117, 88, 85, 114, 101,
        101, 111, 121, 52, 82, 100, 115, 81, 76, 103, 67, 90, 112, 89, 65, 81,
        84, 50, 70, 34, 93, 44, 34, 73, 109, 97, 103, 101, 34, 93, 10, 53, 58,
        73, 91, 52, 53, 55, 53, 57, 44, 91, 93, 44, 34, 34, 93, 10, 54, 58, 73,
        91, 53, 56, 50, 48, 49, 44, 91, 93, 44, 34, 34, 93, 10, 48, 58, 91, 34,
        78, 53, 70, 107, 48, 103, 56, 120, 52, 103, 104, 101, 89, 84, 99, 110,
        69, 75, 99, 80, 100, 34, 44, 91, 91, 34, 99, 104, 105, 108, 100, 114,
        101, 110, 34, 44, 34, 40, 109, 97, 105, 1
Download .txt
gitextract_pei3ls9p/

├── .changeset/
│   ├── README.md
│   ├── config.json
│   └── lucky-numbers-roll.md
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── apply-version.sh
├── examples/
│   └── embedded-example/
│       ├── .gitignore
│       ├── .prettierignore
│       ├── CHANGELOG.md
│       ├── LICENSE
│       ├── README.md
│       ├── app/
│       │   ├── action/
│       │   │   └── page.tsx
│       │   ├── error/
│       │   │   └── [name]/
│       │   │       └── page.tsx
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── other/
│       │   │   └── page.tsx
│       │   ├── page.tsx
│       │   └── suspense/
│       │       └── page.tsx
│       ├── eslint.config.js
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.cjs
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       ├── turbo.json
│       └── vercel.json
├── package.json
├── packages/
│   ├── chrome-extension/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── CHANGELOG.md
│   │   ├── LICENSE
│   │   ├── devtoolsPanel.html
│   │   ├── eslint.config.js
│   │   ├── package.json
│   │   ├── public/
│   │   │   ├── devtoolsPage.html
│   │   │   └── manifest.json
│   │   ├── src/
│   │   │   ├── RscDevtoolsExtension.tsx
│   │   │   ├── assets/
│   │   │   │   ├── contentScript.ts
│   │   │   │   ├── devtoolsPage.ts
│   │   │   │   ├── fetchPatcherInjector.ts
│   │   │   │   └── readNextJsScriptTagsInjector.ts
│   │   │   ├── dev/
│   │   │   │   └── reactPreamble.ts
│   │   │   ├── main.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── core/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── CHANGELOG.md
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── eslint.config.js
│   │   ├── jest.config.cjs
│   │   ├── jest.setup.js
│   │   ├── package.json
│   │   ├── postcss.config.cjs
│   │   ├── src/
│   │   │   ├── color.ts
│   │   │   ├── components/
│   │   │   │   ├── BottomPanel.stories.tsx
│   │   │   │   ├── BottomPanel.tsx
│   │   │   │   ├── EndTimeContext.tsx
│   │   │   │   ├── FlightResponseChunkConsole.stories.tsx
│   │   │   │   ├── FlightResponseChunkConsole.tsx
│   │   │   │   ├── FlightResponseChunkDebugInfo.stories.tsx
│   │   │   │   ├── FlightResponseChunkDebugInfo.tsx
│   │   │   │   ├── FlightResponseChunkHint.stories.tsx
│   │   │   │   ├── FlightResponseChunkHint.tsx
│   │   │   │   ├── FlightResponseChunkModel.stories.tsx
│   │   │   │   ├── FlightResponseChunkModel.tsx
│   │   │   │   ├── FlightResponseChunkModule.stories.tsx
│   │   │   │   ├── FlightResponseChunkModule.tsx
│   │   │   │   ├── FlightResponseChunkText.tsx
│   │   │   │   ├── FlightResponseChunkUnknown.tsx
│   │   │   │   ├── FlightResponseIcons.tsx
│   │   │   │   ├── GenericErrorBoundaryFallback.tsx
│   │   │   │   ├── IconButton.tsx
│   │   │   │   ├── Logo.stories.tsx
│   │   │   │   ├── Logo.tsx
│   │   │   │   ├── OverflowButton.tsx
│   │   │   │   ├── PanelLayout.stories.tsx
│   │   │   │   ├── PanelLayout.tsx
│   │   │   │   ├── RecordButton.stories.tsx
│   │   │   │   ├── RecordButton.tsx
│   │   │   │   ├── RequestDetail.tsx
│   │   │   │   ├── RequestDetailTabEmptyState.tsx
│   │   │   │   ├── RequestDetailTabHeaders.stories.tsx
│   │   │   │   ├── RequestDetailTabHeaders.tsx
│   │   │   │   ├── RequestDetailTabNetwork.stories.tsx
│   │   │   │   ├── RequestDetailTabNetwork.tsx
│   │   │   │   ├── RequestDetailTabParsedPayload.stories.tsx
│   │   │   │   ├── RequestDetailTabParsedPayload.tsx
│   │   │   │   ├── RequestDetailTabRawPayload.stories.tsx
│   │   │   │   ├── RequestDetailTabRawPayload.tsx
│   │   │   │   ├── RequestDetailTabTimings.stories.tsx
│   │   │   │   ├── RequestDetailTabTimings.tsx
│   │   │   │   ├── TimeScrubber.stories.tsx
│   │   │   │   ├── TimeScrubber.tsx
│   │   │   │   ├── ViewerPayload.stories.tsx
│   │   │   │   ├── ViewerPayload.tsx
│   │   │   │   ├── ViewerStreams.stories.tsx
│   │   │   │   ├── ViewerStreams.tsx
│   │   │   │   ├── ViewerStreamsEmptyState.stories.tsx
│   │   │   │   ├── ViewerStreamsEmptyState.tsx
│   │   │   │   ├── isDev.ts
│   │   │   │   └── useTabStoreWithTransitions.ts
│   │   │   ├── copyEventsToClipboard.ts
│   │   │   ├── eventArrayHelpers.ts
│   │   │   ├── events.ts
│   │   │   ├── example-data/
│   │   │   │   ├── alvar-dev.ts
│   │   │   │   ├── gh-fredkiss-dev.ts
│   │   │   │   └── nextjs-org.ts
│   │   │   ├── fetchPatcher.ts
│   │   │   ├── main.ts
│   │   │   ├── readNextJsScriptTags.ts
│   │   │   └── tailwind.css
│   │   ├── tailwind.config.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── embedded/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── CHANGELOG.md
│   │   ├── LICENSE
│   │   ├── RscDevtoolsPanel.tsx
│   │   ├── eslint.config.js
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── react-client/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc
│   │   ├── CHANGELOG.md
│   │   ├── eslint.config.js
│   │   ├── flight.ts
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── ReactDOMTypes.ts
│   │   │   ├── ReactFlightClient.ts
│   │   │   ├── ReactFlightClientConfigBrowser.ts
│   │   │   ├── ReactFlightClientConfigBundlerWebpack.ts
│   │   │   ├── ReactFlightImportMetadata.ts
│   │   │   ├── ReactFlightServerConfigDOM.ts
│   │   │   ├── ReactSymbols.ts
│   │   │   ├── ReactTypes.ts
│   │   │   └── crossOriginStrings.ts
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vite.config.ts
│   ├── storybook/
│   │   ├── .gitignore
│   │   ├── .storybook/
│   │   │   ├── main.ts
│   │   │   ├── preview-body.html
│   │   │   └── preview.ts
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── turbo.json
│   │   └── vercel.json
│   └── website/
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── LICENSE
│       ├── app/
│       │   ├── ViewerPayloadClientWrapper.tsx
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   └── page.tsx
│       ├── eslint.config.js
│       ├── jest.config.js
│       ├── jest.setup.js
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.cjs
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       ├── turbo.json
│       └── vercel.json
├── renovate.json
└── turbo.json
Download .txt
SYMBOL INDEX (286 symbols across 77 files)

FILE: examples/embedded-example/app/action/page.tsx
  function ActionPage (line 3) | function ActionPage() {

FILE: examples/embedded-example/app/error/[name]/page.tsx
  function Pokemon (line 1) | function Pokemon({

FILE: examples/embedded-example/app/layout.tsx
  function RootLayout (line 13) | function RootLayout({

FILE: examples/embedded-example/app/other/page.tsx
  function Other (line 3) | function Other() {

FILE: examples/embedded-example/app/page.tsx
  function Home (line 3) | function Home() {

FILE: examples/embedded-example/app/suspense/page.tsx
  function SuspensePage (line 4) | function SuspensePage() {
  function SlowComponent (line 15) | async function SlowComponent() {

FILE: packages/chrome-extension/src/RscDevtoolsExtension.tsx
  function RscDevtoolsExtension (line 29) | function RscDevtoolsExtension() {
  function useRscEvents (line 86) | function useRscEvents() {
  function arraysEqual (line 178) | function arraysEqual(a: unknown[], b: unknown[]) {

FILE: packages/chrome-extension/src/assets/contentScript.ts
  function isStartRecordingEvent (line 13) | function isStartRecordingEvent(event: unknown) {
  function isRscRequestEvent (line 21) | function isRscRequestEvent(event: unknown) {
  function isRscResponseEvent (line 29) | function isRscResponseEvent(event: unknown) {
  function isRscChunkEvent (line 37) | function isRscChunkEvent(event: unknown) {
  function isReadNextJsScriptTagsEvent (line 45) | function isReadNextJsScriptTagsEvent(event: unknown) {
  function isRscEvent (line 53) | function isRscEvent(event: unknown) {
  function injectScript (line 68) | function injectScript(file_path: string, tag: string) {

FILE: packages/chrome-extension/src/assets/devtoolsPage.ts
  function handleShown (line 1) | function handleShown() {
  function handleHidden (line 5) | function handleHidden() {

FILE: packages/core/src/color.ts
  function random (line 1) | function random(seed: number) {
  function getColorForFetch (line 6) | function getColorForFetch(requestId: string) {

FILE: packages/core/src/components/BottomPanel.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof BottomPanel>;

FILE: packages/core/src/components/BottomPanel.tsx
  function BottomPanel (line 6) | function BottomPanel({
  function BottomPanelCloseButton (line 50) | function BottomPanelCloseButton({
  function BottomPanelOpenButton (line 71) | function BottomPanelOpenButton({
  function BottomPanelPositionSwitchButton (line 86) | function BottomPanelPositionSwitchButton({

FILE: packages/core/src/components/EndTimeContext.tsx
  function EndTimeProvider (line 23) | function EndTimeProvider({
  function useEndTime (line 61) | function useEndTime() {

FILE: packages/core/src/components/FlightResponseChunkConsole.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof FlightResponseChunkConsole>;

FILE: packages/core/src/components/FlightResponseChunkConsole.tsx
  function FlightResponseChunkConsole (line 5) | function FlightResponseChunkConsole({

FILE: packages/core/src/components/FlightResponseChunkDebugInfo.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof FlightResponseChunkDebugInfo>;

FILE: packages/core/src/components/FlightResponseChunkDebugInfo.tsx
  function FlightResponseChunkDebugInfo (line 5) | function FlightResponseChunkDebugInfo({

FILE: packages/core/src/components/FlightResponseChunkHint.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof FlightResponseChunkHint>;

FILE: packages/core/src/components/FlightResponseChunkHint.tsx
  function FlightResponseChunkHint (line 4) | function FlightResponseChunkHint({

FILE: packages/core/src/components/FlightResponseChunkModel.stories.tsx
  type Story (line 12) | type Story = StoryObj<typeof FlightResponseChunkModel>;

FILE: packages/core/src/components/FlightResponseChunkModel.tsx
  function FlightResponseChunkModel (line 30) | function FlightResponseChunkModel({
  function Node (line 46) | function Node({ value }: { value: unknown }) {
  function NodeSwitch (line 57) | function NodeSwitch({ value }: { value: unknown }) {
  function removeChildren (line 126) | function removeChildren(props: Record<string, unknown>) {
  function NodeElement (line 135) | function NodeElement({
  function Prop (line 282) | function Prop({ propKey, value }: { propKey: string; value: unknown }) {
  function Props (line 308) | function Props({ props }: { props: { [key: string]: unknown } }) {
  function NodeArray (line 342) | function NodeArray({ value }: { value: unknown[] }) {
  function NodeReference (line 391) | function NodeReference({ value }: { value: Reference }) {
  function TabJumpButton (line 402) | function TabJumpButton({
  function NodeSet (line 446) | function NodeSet({ value }: { value: Set<unknown> }) {
  function NodeSymbol (line 454) | function NodeSymbol({ value }: { value: symbol }) {
  function NodeDate (line 462) | function NodeDate({ value }: { value: Date }) {
  function NodeNaN (line 470) | function NodeNaN() {
  function NodeBigInt (line 474) | function NodeBigInt({ value }: { value: bigint }) {
  function NodePositiveInfinity (line 482) | function NodePositiveInfinity() {
  function NodeNegativeInfinity (line 486) | function NodeNegativeInfinity() {
  function NodeNumber (line 490) | function NodeNumber({ value }: { value: number }) {
  function NodeNull (line 498) | function NodeNull() {
  function NodeUndefined (line 502) | function NodeUndefined() {
  function NodeString (line 506) | function NodeString({ value }: { value: string }) {
  function NodeObject (line 568) | function NodeObject({ value }: { value: object }) {
  function NodeUnknown (line 576) | function NodeUnknown({ value }: { value: unknown }) {
  function JSContainer (line 584) | function JSContainer({ children }: { children: ReactNode }) {
  function isLetter (line 612) | function isLetter(letter: string) {
  function JSObjectValue (line 617) | function JSObjectValue({ value }: { value: object }) {
  function JSContainerWrapperForObjects (line 658) | function JSContainerWrapperForObjects({ children }: { children: ReactNod...
  function Purple (line 674) | function Purple({ children }: { children: ReactNode }) {
  function Pink (line 678) | function Pink({ children }: { children: ReactNode }) {
  function Yellow (line 682) | function Yellow({ children }: { children: ReactNode }) {
  function Blue (line 688) | function Blue({ children }: { children: ReactNode }) {
  function Green (line 692) | function Green({ children }: { children: ReactNode }) {
  function LeftCurlyBrace (line 696) | function LeftCurlyBrace() {
  function RightCurlyBrace (line 700) | function RightCurlyBrace() {
  function LeftSquareBracket (line 704) | function LeftSquareBracket() {
  function RightSquareBracket (line 708) | function RightSquareBracket() {
  function LeftArrow (line 712) | function LeftArrow() {
  function RightArrow (line 716) | function RightArrow() {

FILE: packages/core/src/components/FlightResponseChunkModule.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof FlightResponseChunkModule>;

FILE: packages/core/src/components/FlightResponseChunkModule.tsx
  function groupChunks (line 4) | function groupChunks(array: (string | number)[]) {
  function FlightResponseChunkModule (line 12) | function FlightResponseChunkModule({

FILE: packages/core/src/components/FlightResponseChunkText.tsx
  function FlightResponseChunkText (line 4) | function FlightResponseChunkText({

FILE: packages/core/src/components/FlightResponseChunkUnknown.tsx
  function FlightResponseChunkUnknown (line 4) | function FlightResponseChunkUnknown({ chunk }: { chunk: Chunk }) {

FILE: packages/core/src/components/FlightResponseIcons.tsx
  function DownArrowIcon (line 3) | function DownArrowIcon() {
  function RightArrowIcon (line 17) | function RightArrowIcon() {

FILE: packages/core/src/components/GenericErrorBoundaryFallback.tsx
  function GenericErrorBoundaryFallback (line 3) | function GenericErrorBoundaryFallback({ error }: { error: Error }) {

FILE: packages/core/src/components/IconButton.tsx
  function IconButton (line 3) | function IconButton({

FILE: packages/core/src/components/Logo.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof Logo>;

FILE: packages/core/src/components/Logo.tsx
  function Logo (line 3) | function Logo({ variant }: { variant: 'small' | 'wide' }) {

FILE: packages/core/src/components/OverflowButton.tsx
  function OverflowButton (line 5) | function OverflowButton({ menuItems }: { menuItems: ReactNode }) {

FILE: packages/core/src/components/PanelLayout.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof PanelLayout>;

FILE: packages/core/src/components/PanelLayout.tsx
  function PanelLayout (line 3) | function PanelLayout({

FILE: packages/core/src/components/RecordButton.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof RecordButton>;

FILE: packages/core/src/components/RecordButton.tsx
  function RecordButton (line 3) | function RecordButton({

FILE: packages/core/src/components/RequestDetail.tsx
  function RequestDetail (line 13) | function RequestDetail({ events }: { events: RscEvent[] }) {

FILE: packages/core/src/components/RequestDetailTabEmptyState.tsx
  function RequestDetailTabEmptyState (line 1) | function RequestDetailTabEmptyState() {

FILE: packages/core/src/components/RequestDetailTabHeaders.stories.tsx
  type Story (line 14) | type Story = StoryObj<typeof RequestDetailTabHeaders>;

FILE: packages/core/src/components/RequestDetailTabHeaders.tsx
  function RequestDetailTabHeaders (line 7) | function RequestDetailTabHeaders({ events }: { events: RscEvent[] }) {
  function Table (line 51) | function Table({

FILE: packages/core/src/components/RequestDetailTabNetwork.stories.tsx
  type Story (line 14) | type Story = StoryObj<typeof RequestDetailTabNetwork>;

FILE: packages/core/src/components/RequestDetailTabNetwork.tsx
  type Node (line 16) | interface Node extends d3.SimulationNodeDatum {
  type Link (line 21) | interface Link extends d3.SimulationLinkDatum<Node> {
  type Data (line 27) | type Data = {
  constant WIDTH (line 32) | const WIDTH = 120;
  constant HEIGHT (line 33) | const HEIGHT = 50;
  function findReferencesInChunk (line 35) | function findReferencesInChunk(chunk: Chunk) {
  function getChunkById (line 67) | function getChunkById(chunks: Chunk[], id: string) {
  function getLinks (line 71) | function getLinks(chunks: Chunk[]) {
  function getTimeDifferenceBetweenChunks (line 101) | function getTimeDifferenceBetweenChunks(
  function RequestDetailTabNetwork (line 111) | function RequestDetailTabNetwork({ events }: { events: RscEvent[] }) {

FILE: packages/core/src/components/RequestDetailTabParsedPayload.stories.tsx
  type Story (line 14) | type Story = StoryObj<typeof RequestDetailTabParsedPayload>;

FILE: packages/core/src/components/RequestDetailTabParsedPayload.tsx
  function RequestDetailTabParsedPayload (line 32) | function RequestDetailTabParsedPayload({
  function ChunkTab (line 169) | function ChunkTab({
  function ChunkTabFallback (line 193) | function ChunkTabFallback({
  function ChunkTabPanel (line 220) | function ChunkTabPanel({
  function ChunkTabPanelMeta (line 267) | function ChunkTabPanelMeta({ chunk }: { chunk: Chunk }) {
  function ChunkTabPanelSize (line 276) | function ChunkTabPanelSize({
  function ChunkTabPanelExplorer (line 300) | function ChunkTabPanelExplorer({
  function ChunkTabPanelGenericData (line 351) | function ChunkTabPanelGenericData({ chunk }: { chunk: Chunk }) {
  function ChunkTabRawJson (line 380) | function ChunkTabRawJson({ chunk }: { chunk: Chunk }) {
  function stringToKiloBytes (line 391) | function stringToKiloBytes(data: string) {
  function Meter (line 395) | function Meter({ fraction }: { fraction: number }) {

FILE: packages/core/src/components/RequestDetailTabRawPayload.stories.tsx
  type Story (line 14) | type Story = StoryObj<typeof RequestDetailTabRawPayload>;

FILE: packages/core/src/components/RequestDetailTabRawPayload.tsx
  function RequestDetailTabRawPayload (line 9) | function RequestDetailTabRawPayload({ events }: { events: RscEvent[] }) {

FILE: packages/core/src/components/RequestDetailTabTimings.stories.tsx
  type Story (line 14) | type Story = StoryObj<typeof RequestDetailTabTimings>;

FILE: packages/core/src/components/RequestDetailTabTimings.tsx
  function RequestDetailTabTimings (line 13) | function RequestDetailTabTimings({ events }: { events: RscEvent[] }) {
  function getWidthByOffset (line 99) | function getWidthByOffset(offset: number | null, biggestOffset: number) {

FILE: packages/core/src/components/TimeScrubber.stories.tsx
  type Story (line 15) | type Story = StoryObj<typeof TimeScrubber>;

FILE: packages/core/src/components/TimeScrubber.tsx
  function useTracks (line 7) | function useTracks(events: RscEvent[]) {
  function TimeScrubber (line 50) | function TimeScrubber({

FILE: packages/core/src/components/ViewerPayload.stories.tsx
  type Story (line 15) | type Story = StoryObj<typeof ViewerPayload>;
  function getUrlsAndRequestIds (line 17) | function getUrlsAndRequestIds(events: RscEvent[]) {
  function getPayloadByRequestId (line 28) | function getPayloadByRequestId(events: RscEvent[], requestId: string) {

FILE: packages/core/src/components/ViewerPayload.tsx
  function ViewerPayload (line 9) | function ViewerPayload({ defaultPayload }: { defaultPayload: string }) {
  function Viewer (line 49) | function Viewer({ payload }: { payload: string }) {

FILE: packages/core/src/components/ViewerStreams.stories.tsx
  type Story (line 13) | type Story = StoryObj<typeof ViewerStreams>;

FILE: packages/core/src/components/ViewerStreams.tsx
  function ViewerStreams (line 18) | function ViewerStreams({ events }: { events: RscEvent[] }) {
  function Requests (line 36) | function Requests({ events }: { events: RscEvent[] }) {
  function RequestTab (line 88) | function RequestTab({ events }: { events: RscEvent[] }) {

FILE: packages/core/src/components/ViewerStreamsEmptyState.stories.tsx
  type Story (line 10) | type Story = StoryObj<typeof ViewerStreamsEmptyState>;

FILE: packages/core/src/components/ViewerStreamsEmptyState.tsx
  function ViewerStreamsEmptyState (line 3) | function ViewerStreamsEmptyState() {

FILE: packages/core/src/components/isDev.ts
  function isDev (line 3) | function isDev(events: RscEvent[]) {

FILE: packages/core/src/components/useTabStoreWithTransitions.ts
  function useTabStoreWithTransitions (line 4) | function useTabStoreWithTransitions(

FILE: packages/core/src/copyEventsToClipboard.ts
  function copyEventsToClipboard (line 3) | function copyEventsToClipboard({ events }: { events: RscEvent[] }) {

FILE: packages/core/src/eventArrayHelpers.ts
  function eventsSortByTimestamp (line 3) | function eventsSortByTimestamp(events: RscEvent[]) {
  function eventsFilterByMaxTimestamp (line 7) | function eventsFilterByMaxTimestamp(
  function eventsGetMinMaxTimestamps (line 14) | function eventsGetMinMaxTimestamps(events: RscEvent[]) {
  function eventsUniqueRequestIds (line 29) | function eventsUniqueRequestIds(events: RscEvent[]) {
  function eventsFilterByRequestId (line 43) | function eventsFilterByRequestId(events: RscEvent[], requestId: string) {

FILE: packages/core/src/events.ts
  type StartRecordingEvent (line 1) | type StartRecordingEvent = {
  function isStartRecordingEvent (line 8) | function isStartRecordingEvent(
  type StopRecordingEvent (line 19) | type StopRecordingEvent = {
  function isStopRecordingEvent (line 26) | function isStopRecordingEvent(
  type ReadNextJsScriptTagsEvent (line 37) | type ReadNextJsScriptTagsEvent = {
  function isReadNextJsScriptTagsEvent (line 44) | function isReadNextJsScriptTagsEvent(
  type RscEventSharedData (line 55) | type RscEventSharedData = {
  type RscRequestEvent (line 63) | type RscRequestEvent = RscEventSharedData & {
  function isRscRequestEvent (line 72) | function isRscRequestEvent(event: unknown): event is RscRequestEvent {
  type RscResponseEvent (line 81) | type RscResponseEvent = RscEventSharedData & {
  function isRscResponseEvent (line 89) | function isRscResponseEvent(event: unknown): event is RscResponseEvent {
  type RscChunkEvent (line 98) | type RscChunkEvent = RscEventSharedData & {
  function isRscChunkEvent (line 105) | function isRscChunkEvent(event: unknown): event is RscChunkEvent {
  type RscEvent (line 114) | type RscEvent = RscRequestEvent | RscResponseEvent | RscChunkEvent;
  function isRscEvent (line 116) | function isRscEvent(event: unknown): event is RscEvent {
  type Event (line 124) | type Event = StartRecordingEvent | StopRecordingEvent | RscEvent;
  function isEvent (line 126) | function isEvent(event: unknown): event is Event {

FILE: packages/core/src/fetchPatcher.ts
  function isRscResponse (line 3) | function isRscResponse(response: Response): boolean {
  function getFetchUrl (line 7) | function getFetchUrl(args: Parameters<typeof fetch>): string {
  function getFetchMethod (line 17) | function getFetchMethod(args: Parameters<typeof fetch>): 'GET' | 'POST' {
  function headersInitToSerializableObject (line 34) | function headersInitToSerializableObject(
  function getFetchHeaders (line 53) | function getFetchHeaders(
  function convertLocalUrlToAbsolute (line 71) | function convertLocalUrlToAbsolute(url: string): string {
  function fetchPatcher (line 78) | function fetchPatcher({

FILE: packages/core/src/readNextJsScriptTags.ts
  function readNextJsScriptTags (line 3) | function readNextJsScriptTags(): RscEvent[] | undefined {

FILE: packages/embedded/RscDevtoolsPanel.tsx
  function RscDevtoolsPanel (line 30) | function RscDevtoolsPanel({
  function ApplyStylingOnClient (line 124) | function ApplyStylingOnClient({ children }: { children: ReactNode }) {
  function useRscEvents (line 161) | function useRscEvents() {
  function arraysEqual (line 222) | function arraysEqual(a: unknown[], b: unknown[]) {

FILE: packages/react-client/src/ReactDOMTypes.ts
  type PrefetchDNSOptions (line 11) | type PrefetchDNSOptions = Record<string, unknown>;
  type PreconnectOptions (line 12) | type PreconnectOptions = { crossOrigin?: string };
  type PreloadOptions (line 13) | type PreloadOptions = {
  type PreloadModuleOptions (line 24) | type PreloadModuleOptions = {
  type PreinitOptions (line 30) | type PreinitOptions = {
  type PreinitModuleOptions (line 38) | type PreinitModuleOptions = {
  type CrossOriginEnum (line 45) | type CrossOriginEnum = '' | 'use-credentials' | CrossOriginString;
  type FetchPriorityEnum (line 46) | type FetchPriorityEnum = 'high' | 'low' | 'auto';
  type PreloadImplOptions (line 48) | type PreloadImplOptions = {
  type PreloadModuleImplOptions (line 59) | type PreloadModuleImplOptions = {
  type PreinitStyleOptions (line 65) | type PreinitStyleOptions = {
  type PreinitScriptOptions (line 70) | type PreinitScriptOptions = {
  type PreinitModuleScriptOptions (line 76) | type PreinitModuleScriptOptions = {
  type HostDispatcher (line 82) | type HostDispatcher = {
  type ImportMap (line 109) | type ImportMap = {

FILE: packages/react-client/src/ReactFlightClient.ts
  type TextChunk (line 32) | type TextChunk = {
  type ModuleChunk (line 41) | type ModuleChunk = {
  type ModelChunk (line 50) | type ModelChunk = {
  type HintChunk (line 59) | type HintChunk = {
  type ErrorDevChunk (line 69) | type ErrorDevChunk = {
  type ErrorProdChunk (line 78) | type ErrorProdChunk = {
  type PostponeDevChunk (line 87) | type PostponeDevChunk = {
  type PostponeProdChunk (line 96) | type PostponeProdChunk = {
  type BufferChunk (line 105) | type BufferChunk = {
  type DebugInfoChunk (line 114) | type DebugInfoChunk = {
  type ConsoleChunk (line 123) | type ConsoleChunk = {
  type StartAsyncIterableChunk (line 138) | type StartAsyncIterableChunk = {
  type StartReadableStreamChunk (line 149) | type StartReadableStreamChunk = {
  type StopStreamChunk (line 160) | type StopStreamChunk = {
  type Chunk (line 169) | type Chunk =
  type RowParserState (line 185) | type RowParserState = 0 | 1 | 2 | 3 | 4;
  type UninitializedModel (line 187) | type UninitializedModel = string;
  type JSONValue (line 189) | type JSONValue =
  type FlightResponse (line 197) | type FlightResponse = {
  type Reference (line 212) | type Reference = {
  function isReference (line 219) | function isReference(x: unknown): x is Reference {
  function createServerReferenceProxy (line 228) | function createServerReferenceProxy /*<A: Iterable<any>, T>*/(
  function getOutlinedModel (line 261) | function getOutlinedModel<T>(
  function createMap (line 349) | function createMap(
  function createSet (line 361) | function createSet(
  function createBlob (line 373) | function createBlob(
  function createFormData (line 385) | function createFormData(
  function extractIterator (line 401) | function extractIterator(
  function createModel (line 414) | function createModel(
  function parseModelString (line 426) | function parseModelString(
  function parseModelTuple (line 628) | function parseModelTuple(
  function ResponseInstance (line 649) | function ResponseInstance(__DEV__: boolean) {
  function createFlightResponse (line 669) | function createFlightResponse(__DEV__: boolean): FlightResponse {
  function createElement (line 673) | function createElement(type: unknown, key: unknown, props: unknown) {
  function isElement (line 716) | function isElement(x: unknown): x is ReturnType<typeof createElement> {
  function resolveModel (line 725) | function resolveModel(
  function resolveText (line 744) | function resolveText(response: FlightResponse, id: number, text: string)...
  function resolveBuffer (line 759) | function resolveBuffer(
  function resolveModule (line 777) | function resolveModule(
  function startReadableStream (line 825) | function startReadableStream<T>(
  function startAsyncIterable (line 942) | function startAsyncIterable<T>(
  function stopStream (line 1081) | function stopStream(
  type ErrorWithDigest (line 1107) | type ErrorWithDigest = Error & { digest?: string };
  function resolveErrorProd (line 1108) | function resolveErrorProd(response: FlightResponse, id: number): void {
  function resolveErrorDev (line 1135) | function resolveErrorDev(
  class Postpone (line 1169) | class Postpone extends Error {
  constant REACT_POSTPONE_TYPE (line 1173) | const REACT_POSTPONE_TYPE = Symbol.for('react.postpone');
  function resolvePostponeProd (line 1175) | function resolvePostponeProd(response: FlightResponse, id: number): void {
  function resolvePostponeDev (line 1202) | function resolvePostponeDev(
  function resolveHint (line 1232) | function resolveHint<Code extends HintCode>(
  function resolveDebugInfo (line 1253) | function resolveDebugInfo(
  function resolveConsoleEntry (line 1285) | function resolveConsoleEntry(
  function mergeBuffer (line 1355) | function mergeBuffer(
  function resolveTypedArray (line 1378) | function resolveTypedArray(
  function processFullBinaryRow (line 1419) | function processFullBinaryRow(
  function processFullStringRow (line 1485) | function processFullStringRow(
  constant ROW_ID (line 1605) | const ROW_ID = 0;
  constant ROW_TAG (line 1606) | const ROW_TAG = 1;
  constant ROW_LENGTH (line 1607) | const ROW_LENGTH = 2;
  constant ROW_CHUNK_BY_NEWLINE (line 1608) | const ROW_CHUNK_BY_NEWLINE = 3;
  constant ROW_CHUNK_BY_LENGTH (line 1609) | const ROW_CHUNK_BY_LENGTH = 4;
  function processBinaryChunk (line 1611) | function processBinaryChunk(
  function processStringChunk (line 1732) | function processStringChunk(response: FlightResponse, chunk: string): vo...
  function parseModel (line 1877) | function parseModel<T>(response: FlightResponse, json: UninitializedMode...
  function createFromJSONCallback (line 1881) | function createFromJSONCallback(response: FlightResponse) {

FILE: packages/react-client/src/ReactFlightClientConfigBrowser.ts
  type StringDecoder (line 9) | type StringDecoder = TextDecoder;
  function createStringDecoder (line 11) | function createStringDecoder(): StringDecoder {
  function readPartialStringChunk (line 17) | function readPartialStringChunk(
  function readFinalStringChunk (line 24) | function readFinalStringChunk(

FILE: packages/react-client/src/ReactFlightClientConfigBundlerWebpack.ts
  type ClientReferenceMetadata (line 11) | type ClientReferenceMetadata = ImportMetadata;

FILE: packages/react-client/src/ReactFlightImportMetadata.ts
  type ImportMetadata (line 11) | type ImportMetadata =
  constant CHUNKS (line 25) | const CHUNKS = 1;
  constant NAME (line 26) | const NAME = 2;
  function isAsyncImport (line 33) | function isAsyncImport(metadata: ImportMetadata): boolean {

FILE: packages/react-client/src/ReactFlightServerConfigDOM.ts
  type UnspecifiedPrecedence (line 22) | type UnspecifiedPrecedence = 0;
  type TypeMap (line 25) | type TypeMap = {
  type HintCode (line 51) | type HintCode = keyof TypeMap;
  type HintModel (line 52) | type HintModel<T extends HintCode> = TypeMap[T];
  type Hints (line 54) | type Hints = Set<string>;

FILE: packages/react-client/src/ReactSymbols.ts
  constant REACT_ELEMENT_TYPE (line 9) | const REACT_ELEMENT_TYPE: symbol = Symbol.for('react.element');
  constant REACT_LAZY_TYPE (line 10) | const REACT_LAZY_TYPE: symbol = Symbol.for('react.lazy');
  constant REACT_POSTPONE_TYPE (line 11) | const REACT_POSTPONE_TYPE: symbol = Symbol.for('react.postpone');

FILE: packages/react-client/src/ReactTypes.ts
  type ConsoleTask (line 10) | type ConsoleTask = any;
  type ReactCallSite (line 12) | type ReactCallSite = [
  type ReactStackTrace (line 19) | type ReactStackTrace = Array<ReactCallSite>;
  type ReactComponentInfo (line 21) | type ReactComponentInfo = {
  type ReactEnvironmentInfo (line 29) | type ReactEnvironmentInfo = {
  type ReactErrorInfoProd (line 33) | type ReactErrorInfoProd = {
  type ReactErrorInfoDev (line 37) | type ReactErrorInfoDev = {
  type ReactErrorInfo (line 45) | type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;
  type ReactAsyncInfo (line 47) | type ReactAsyncInfo = {
  type ReactDebugInfo (line 53) | type ReactDebugInfo = Array<ReactComponentInfo | ReactAsyncInfo>;

FILE: packages/react-client/src/crossOriginStrings.ts
  type CrossOriginString (line 9) | type CrossOriginString = string;

FILE: packages/storybook/.storybook/main.ts
  function getAbsolutePath (line 23) | function getAbsolutePath(value: string): any {

FILE: packages/website/app/ViewerPayloadClientWrapper.tsx
  function PayloadViewerClientWrapper (line 20) | function PayloadViewerClientWrapper() {

FILE: packages/website/app/layout.tsx
  function RootLayout (line 18) | function RootLayout({

FILE: packages/website/app/page.tsx
  function Home (line 10) | function Home() {

FILE: packages/website/next.config.ts
  method rewrites (line 4) | async rewrites() {
Condensed preview — 183 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,561K chars).
[
  {
    "path": ".changeset/README.md",
    "chars": 510,
    "preview": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that wo"
  },
  {
    "path": ".changeset/config.json",
    "chars": 442,
    "preview": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@2.3.1/schema.json\",\n  \"changelog\": \"@changesets/cli/changelog\",\n  \""
  },
  {
    "path": ".changeset/lucky-numbers-roll.md",
    "chars": 271,
    "preview": "---\n'@rsc-parser/chrome-extension': patch\n'@rsc-parser/embedded-example': patch\n'@rsc-parser/core': patch\n'@rsc-parser/e"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 702,
    "preview": "name: CI\n\non:\n  pull_request_target:\n    # Types must be filtered to prevent getting stuck when a\n    # bot with GITHUB_"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1366,
    "preview": "name: Release\n\non:\n  push:\n    branches:\n      - main\n\nconcurrency: ${{ github.workflow }}-${{ github.ref }}\n\njobs:\n  re"
  },
  {
    "path": ".gitignore",
    "chars": 336,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# t"
  },
  {
    "path": ".prettierignore",
    "chars": 5,
    "preview": ".yarn"
  },
  {
    "path": ".prettierrc",
    "chars": 52,
    "preview": "{\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 94,
    "preview": "{\n  \"css.lint.unknownAtRules\": \"ignore\",\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Alvar Lagerlöf\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 2384,
    "preview": "# RSC Parser\n\nThis is a parser for React Server Components (RSC) when sent over the network. React uses a format to repr"
  },
  {
    "path": "apply-version.sh",
    "chars": 533,
    "preview": "#!/bin/bash\nset -e\n\n# Step 1: Extract newVersion from ./packages/core/package.json\nnew_version=$(jq -r '.version' ./pack"
  },
  {
    "path": "examples/embedded-example/.gitignore",
    "chars": 391,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "examples/embedded-example/.prettierignore",
    "chars": 12,
    "preview": ".next\n.turbo"
  },
  {
    "path": "examples/embedded-example/CHANGELOG.md",
    "chars": 6716,
    "preview": "# @rsc-parser/embedded-example\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n- Upd"
  },
  {
    "path": "examples/embedded-example/LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Alvar Lagerlöf\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "examples/embedded-example/README.md",
    "chars": 1383,
    "preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
  },
  {
    "path": "examples/embedded-example/app/action/page.tsx",
    "chars": 481,
    "preview": "import Link from 'next/link';\n\nexport default function ActionPage() {\n  return (\n    <>\n      <Link href=\"/\">Go to home "
  },
  {
    "path": "examples/embedded-example/app/error/[name]/page.tsx",
    "chars": 211,
    "preview": "export default function Pokemon({\n  params,\n}: {\n  params: Promise<{ name: string }>;\n}) {\n  // @ts-expect-error Incorre"
  },
  {
    "path": "examples/embedded-example/app/globals.css",
    "chars": 56,
    "preview": "@import 'tailwindcss';\n@config '../tailwind.config.ts';\n"
  },
  {
    "path": "examples/embedded-example/app/layout.tsx",
    "chars": 606,
    "preview": "import type { Metadata } from 'next';\nimport { Inter } from 'next/font/google';\nimport './globals.css';\nimport { RscDevt"
  },
  {
    "path": "examples/embedded-example/app/other/page.tsx",
    "chars": 166,
    "preview": "import Link from 'next/link';\n\nexport default function Other() {\n  return (\n    <>\n      <p>Other page</p>\n      <Link h"
  },
  {
    "path": "examples/embedded-example/app/page.tsx",
    "chars": 371,
    "preview": "import Link from 'next/link';\n\nexport default function Home() {\n  return (\n    <>\n      <p>Home page</p>\n      <Link hre"
  },
  {
    "path": "examples/embedded-example/app/suspense/page.tsx",
    "chars": 399,
    "preview": "import Link from 'next/link';\nimport { Suspense } from 'react';\n\nexport default function SuspensePage() {\n  return (\n   "
  },
  {
    "path": "examples/embedded-example/eslint.config.js",
    "chars": 467,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default [\n  {\n    ignor"
  },
  {
    "path": "examples/embedded-example/next.config.ts",
    "chars": 99,
    "preview": "import { NextConfig } from 'next';\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n"
  },
  {
    "path": "examples/embedded-example/package.json",
    "chars": 1045,
    "preview": "{\n  \"name\": \"@rsc-parser/embedded-example\",\n  \"packageManager\": \"bun@1.3.3\",\n  \"version\": \"1.1.2\",\n  \"private\": true,\n  "
  },
  {
    "path": "examples/embedded-example/postcss.config.cjs",
    "chars": 72,
    "preview": "module.exports = {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/embedded-example/tailwind.config.ts",
    "chars": 498,
    "preview": "import type { Config } from 'tailwindcss';\n\nconst config: Config = {\n  content: [\n    './pages/**/*.{js,ts,jsx,tsx,mdx}'"
  },
  {
    "path": "examples/embedded-example/tsconfig.json",
    "chars": 671,
    "preview": "{\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n  "
  },
  {
    "path": "examples/embedded-example/turbo.json",
    "chars": 207,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\".n"
  },
  {
    "path": "examples/embedded-example/vercel.json",
    "chars": 78,
    "preview": "{\n  \"github\": {\n    \"silent\": true\n  },\n  \"buildCommand\": \"turbo run build\"\n}\n"
  },
  {
    "path": "package.json",
    "chars": 864,
    "preview": "{\n  \"name\": \"rsc-parser\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"repository\": \"https://github.com/alvarlagerlof/"
  },
  {
    "path": "packages/chrome-extension/.gitignore",
    "chars": 426,
    "preview": "**/*.trace\n**/*.zip\n**/*.tar.gz\n**/*.tgz\n**/*.log\n\npackage-lock.json\n**/*.bun\n\n# See https://help.github.com/articles/ig"
  },
  {
    "path": "packages/chrome-extension/.prettierignore",
    "chars": 4,
    "preview": "dist"
  },
  {
    "path": "packages/chrome-extension/CHANGELOG.md",
    "chars": 9142,
    "preview": "# @rsc-parser/chrome-extension\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n- Upd"
  },
  {
    "path": "packages/chrome-extension/LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Alvar Lagerlöf\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "packages/chrome-extension/devtoolsPanel.html",
    "chars": 724,
    "preview": "<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-sc"
  },
  {
    "path": "packages/chrome-extension/eslint.config.js",
    "chars": 484,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default [\n  {\n    ignor"
  },
  {
    "path": "packages/chrome-extension/package.json",
    "chars": 1277,
    "preview": "{\n  \"name\": \"@rsc-parser/chrome-extension\",\n  \"version\": \"1.1.2\",\n  \"license\": \"MIT\",\n  \"packageManager\": \"bun@1.3.3\",\n "
  },
  {
    "path": "packages/chrome-extension/public/devtoolsPage.html",
    "chars": 64,
    "preview": "<body>\n  <script src=\"assets/devtoolsPage.js\"></script>\n</body>\n"
  },
  {
    "path": "packages/chrome-extension/public/manifest.json",
    "chars": 727,
    "preview": "{\n  \"name\": \"RSC Devtools\",\n  \"version\": \"1.1.2\",\n  \"description\": \"React Server Components network visualizer\",\n  \"cont"
  },
  {
    "path": "packages/chrome-extension/src/RscDevtoolsExtension.tsx",
    "chars": 4615,
    "preview": "import React, {\n  startTransition,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\n\nimport {\n  Viewer"
  },
  {
    "path": "packages/chrome-extension/src/assets/contentScript.ts",
    "chars": 3876,
    "preview": "import {\n  type RscEvent,\n  type StopRecordingEvent,\n} from '@rsc-parser/core/events';\n// import { RscEvent, isRscEvent "
  },
  {
    "path": "packages/chrome-extension/src/assets/devtoolsPage.ts",
    "chars": 388,
    "preview": "function handleShown() {\n  //console.log(\"panel is being shown\");\n}\n\nfunction handleHidden() {\n  //console.log(\"panel is"
  },
  {
    "path": "packages/chrome-extension/src/assets/fetchPatcherInjector.ts",
    "chars": 219,
    "preview": "import { fetchPatcher } from '@rsc-parser/core/fetchPatcher';\n\nfetchPatcher({\n  onRscEvent: (event) => {\n    // Forward "
  },
  {
    "path": "packages/chrome-extension/src/assets/readNextJsScriptTagsInjector.ts",
    "chars": 234,
    "preview": "import { readNextJsScriptTags } from '@rsc-parser/core/readNextJsScriptTags';\n\n(() => {\n  const events = readNextJsScrip"
  },
  {
    "path": "packages/chrome-extension/src/dev/reactPreamble.ts",
    "chars": 400,
    "preview": "// @ts-expect-error Vite special sauce\nimport RefreshRuntime from 'http://localhost:6020/@react-refresh';\nRefreshRuntime"
  },
  {
    "path": "packages/chrome-extension/src/main.tsx",
    "chars": 529,
    "preview": "import 'vite/modulepreload-polyfill';\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { RscD"
  },
  {
    "path": "packages/chrome-extension/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "packages/chrome-extension/tsconfig.json",
    "chars": 576,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"sk"
  },
  {
    "path": "packages/chrome-extension/turbo.json",
    "chars": 296,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"di"
  },
  {
    "path": "packages/chrome-extension/vite.config.ts",
    "chars": 2752,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport htmlPlugin, { Options, ScriptTag }"
  },
  {
    "path": "packages/core/.gitignore",
    "chars": 44,
    "preview": "/node_modules\ndist\nvite.config.ts.timestamp*"
  },
  {
    "path": "packages/core/.prettierignore",
    "chars": 47,
    "preview": "vite.config.ts.timestamp*\nsrc/example-data\ndist"
  },
  {
    "path": "packages/core/CHANGELOG.md",
    "chars": 6692,
    "preview": "# @rsc-parser/core\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n- Updated depende"
  },
  {
    "path": "packages/core/LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Alvar Lagerlöf\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "packages/core/README.md",
    "chars": 277,
    "preview": "# @rsc-parser/core\n\n> [!WARNING]\n> This package is published on npm as `@rsc-parser/core` but its api cannot be consider"
  },
  {
    "path": "packages/core/eslint.config.js",
    "chars": 538,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default [\n  {\n    ignor"
  },
  {
    "path": "packages/core/jest.config.cjs",
    "chars": 347,
    "preview": "/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'jsdom'"
  },
  {
    "path": "packages/core/jest.setup.js",
    "chars": 37,
    "preview": "import('@testing-library/jest-dom');\n"
  },
  {
    "path": "packages/core/package.json",
    "chars": 2496,
    "preview": "{\n  \"name\": \"@rsc-parser/core\",\n  \"version\": \"1.1.2\",\n  \"packageManager\": \"bun@1.3.3\",\n  \"author\": \"Alvar Lagerlöf\",\n  \""
  },
  {
    "path": "packages/core/postcss.config.cjs",
    "chars": 72,
    "preview": "module.exports = {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "packages/core/src/color.ts",
    "chars": 319,
    "preview": "function random(seed: number) {\n  const x = Math.sin(seed++) * 10000;\n  return x - Math.floor(x);\n}\n\nexport function get"
  },
  {
    "path": "packages/core/src/components/BottomPanel.stories.tsx",
    "chars": 390,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { BottomPanel } from './BottomPanel';\n\nconst meta: "
  },
  {
    "path": "packages/core/src/components/BottomPanel.tsx",
    "chars": 3757,
    "preview": "import React, { ReactNode, useState } from 'react';\nimport { Logo } from './Logo';\nimport { Panel, PanelGroup, PanelResi"
  },
  {
    "path": "packages/core/src/components/EndTimeContext.tsx",
    "chars": 1373,
    "preview": "import React from 'react';\nimport {\n  ReactNode,\n  createContext,\n  useContext,\n  useEffect,\n  useState,\n  useTransition"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkConsole.stories.tsx",
    "chars": 925,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { FlightResponseChunkConsole } from './FlightRespon"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkConsole.tsx",
    "chars": 2231,
    "preview": "import React from 'react';\nimport { ConsoleChunk } from '@rsc-parser/react-client';\nimport { FlightResponseChunkModel } "
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkDebugInfo.stories.tsx",
    "chars": 492,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { FlightResponseChunkDebugInfo } from './FlightResp"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkDebugInfo.tsx",
    "chars": 438,
    "preview": "import React from 'react';\nimport { DebugInfoChunk } from '@rsc-parser/react-client';\nimport { FlightResponseChunkModel "
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkHint.stories.tsx",
    "chars": 698,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { FlightResponseChunkHint } from './FlightResponseC"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkHint.tsx",
    "chars": 581,
    "preview": "import React from 'react';\nimport { HintChunk } from '@rsc-parser/react-client';\n\nexport function FlightResponseChunkHin"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkModel.stories.tsx",
    "chars": 19352,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { FlightResponseChunkModel } from './FlightResponse"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkModel.tsx",
    "chars": 17917,
    "preview": "import React, {\n  ReactNode,\n  createContext,\n  useContext,\n  useState,\n  useTransition,\n} from 'react';\nimport {\n  Disc"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkModule.stories.tsx",
    "chars": 989,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { FlightResponseChunkModule } from './FlightRespons"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkModule.tsx",
    "chars": 918,
    "preview": "import React from 'react';\nimport { ModuleChunk } from '@rsc-parser/react-client';\n\nfunction groupChunks(array: (string "
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkText.tsx",
    "chars": 196,
    "preview": "import React from 'react';\nimport { TextChunk } from '@rsc-parser/react-client';\n\nexport function FlightResponseChunkTex"
  },
  {
    "path": "packages/core/src/components/FlightResponseChunkUnknown.tsx",
    "chars": 291,
    "preview": "import React from 'react';\nimport { Chunk } from '@rsc-parser/react-client';\n\nexport function FlightResponseChunkUnknown"
  },
  {
    "path": "packages/core/src/components/FlightResponseIcons.tsx",
    "chars": 671,
    "preview": "import React from 'react';\n\nexport function DownArrowIcon() {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/sv"
  },
  {
    "path": "packages/core/src/components/GenericErrorBoundaryFallback.tsx",
    "chars": 335,
    "preview": "import React from 'react';\n\nexport function GenericErrorBoundaryFallback({ error }: { error: Error }) {\n  return (\n    <"
  },
  {
    "path": "packages/core/src/components/IconButton.tsx",
    "chars": 440,
    "preview": "import { ElementType, ReactNode } from 'react';\n\nexport function IconButton({\n  children,\n  onClick,\n  as = 'button',\n}:"
  },
  {
    "path": "packages/core/src/components/Logo.stories.tsx",
    "chars": 381,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { Logo } from './Logo';\n\nconst meta: Meta<typeof Lo"
  },
  {
    "path": "packages/core/src/components/Logo.tsx",
    "chars": 240,
    "preview": "import React from 'react';\n\nexport function Logo({ variant }: { variant: 'small' | 'wide' }) {\n  if (variant === 'small'"
  },
  {
    "path": "packages/core/src/components/OverflowButton.tsx",
    "chars": 945,
    "preview": "import React, { ReactNode } from 'react';\nimport { MenuProvider, MenuButton, Menu } from '@ariakit/react';\nimport { Icon"
  },
  {
    "path": "packages/core/src/components/PanelLayout.stories.tsx",
    "chars": 388,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { PanelLayout } from './PanelLayout';\n\nconst meta: "
  },
  {
    "path": "packages/core/src/components/PanelLayout.tsx",
    "chars": 634,
    "preview": "import React, { ReactNode } from 'react';\n\nexport function PanelLayout({\n  header,\n  buttons,\n  children,\n}: {\n  header:"
  },
  {
    "path": "packages/core/src/components/RecordButton.stories.tsx",
    "chars": 450,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { RecordButton } from './RecordButton';\n\nconst meta"
  },
  {
    "path": "packages/core/src/components/RecordButton.tsx",
    "chars": 738,
    "preview": "import React from 'react';\n\nexport function RecordButton({\n  isRecording,\n  onClickRecord,\n}: {\n  isRecording: boolean;\n"
  },
  {
    "path": "packages/core/src/components/RequestDetail.tsx",
    "chars": 4286,
    "preview": "import React from 'react';\nimport { TabList, Tab, TabPanel, TabProvider } from '@ariakit/react';\nimport { RscEvent, isRs"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabEmptyState.tsx",
    "chars": 103,
    "preview": "export function RequestDetailTabEmptyState() {\n  return 'No data found for the current time frame.';\n}\n"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabHeaders.stories.tsx",
    "chars": 1007,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { RequestDetailTabHeader"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabHeaders.tsx",
    "chars": 2247,
    "preview": "import React from 'react';\nimport { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';\nimport { RscEvent, isRscRe"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabNetwork.stories.tsx",
    "chars": 1007,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { RequestDetailTabNetwor"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabNetwork.tsx",
    "chars": 7909,
    "preview": "import {\n  Chunk,\n  Reference,\n  createFlightResponse,\n  isReference,\n  processBinaryChunk,\n} from '@rsc-parser/react-cl"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabParsedPayload.stories.tsx",
    "chars": 1055,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { RequestDetailTabParsed"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabParsedPayload.tsx",
    "chars": 12097,
    "preview": "import React, { useState, useTransition } from 'react';\nimport {\n  Disclosure,\n  DisclosureContent,\n  useDisclosureStore"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabRawPayload.stories.tsx",
    "chars": 1031,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { RequestDetailTabRawPay"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabRawPayload.tsx",
    "chars": 1151,
    "preview": "import React from 'react';\nimport { useEndTime } from './EndTimeContext';\nimport { RscEvent, isRscChunkEvent } from '../"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabTimings.stories.tsx",
    "chars": 1007,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { RequestDetailTabTiming"
  },
  {
    "path": "packages/core/src/components/RequestDetailTabTimings.tsx",
    "chars": 2904,
    "preview": "import React from 'react';\nimport { eventsFilterByMaxTimestamp } from '../eventArrayHelpers';\nimport {\n  RscEvent,\n  isR"
  },
  {
    "path": "packages/core/src/components/TimeScrubber.stories.tsx",
    "chars": 1493,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { TimeScrubber } from '."
  },
  {
    "path": "packages/core/src/components/TimeScrubber.tsx",
    "chars": 6252,
    "preview": "import React, { useMemo } from 'react';\nimport { RscEvent } from '../events';\nimport { getColorForFetch } from '../color"
  },
  {
    "path": "packages/core/src/components/ViewerPayload.stories.tsx",
    "chars": 3451,
    "preview": "import React from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { ViewerPayload } from '"
  },
  {
    "path": "packages/core/src/components/ViewerPayload.tsx",
    "chars": 1960,
    "preview": "import React, { ChangeEvent, useEffect, useState } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\n\n"
  },
  {
    "path": "packages/core/src/components/ViewerStreams.stories.tsx",
    "chars": 798,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { ViewerStreams } from './ViewerStreams';\nimport { "
  },
  {
    "path": "packages/core/src/components/ViewerStreams.tsx",
    "chars": 4036,
    "preview": "import React from 'react';\nimport { TabList, Tab, TabPanel } from '@ariakit/react';\nimport { RscEvent, isRscRequestEvent"
  },
  {
    "path": "packages/core/src/components/ViewerStreamsEmptyState.stories.tsx",
    "chars": 358,
    "preview": "import type { Meta, StoryObj } from '@storybook/react-vite';\n\nimport { ViewerStreamsEmptyState } from './ViewerStreamsEm"
  },
  {
    "path": "packages/core/src/components/ViewerStreamsEmptyState.tsx",
    "chars": 200,
    "preview": "import React from 'react';\n\nexport function ViewerStreamsEmptyState() {\n  return (\n    <p className=\"dark:text-white\">\n "
  },
  {
    "path": "packages/core/src/components/isDev.ts",
    "chars": 259,
    "preview": "import { RscEvent } from '../events';\n\nexport function isDev(events: RscEvent[]) {\n  return events.some(\n    (event) =>\n"
  },
  {
    "path": "packages/core/src/components/useTabStoreWithTransitions.ts",
    "chars": 911,
    "preview": "import { useTabStore } from '@ariakit/react';\nimport { useState, useTransition } from 'react';\n\nexport function useTabSt"
  },
  {
    "path": "packages/core/src/copyEventsToClipboard.ts",
    "chars": 511,
    "preview": "import { RscEvent } from './events';\n\nexport function copyEventsToClipboard({ events }: { events: RscEvent[] }) {\n  cons"
  },
  {
    "path": "packages/core/src/eventArrayHelpers.ts",
    "chars": 1110,
    "preview": "import { RscEvent } from './events';\n\nexport function eventsSortByTimestamp(events: RscEvent[]) {\n  return events.sort(("
  },
  {
    "path": "packages/core/src/events.ts",
    "chars": 2718,
    "preview": "export type StartRecordingEvent = {\n  type: 'START_RECORDING';\n  data: {\n    tabId: number;\n  };\n};\n\nexport function isS"
  },
  {
    "path": "packages/core/src/example-data/alvar-dev.ts",
    "chars": 220105,
    "preview": "import { RscEvent } from '../events';\n\nexport const alvarDevExampleData: RscEvent[] = [\n  {\n    type: 'RSC_REQUEST',\n   "
  },
  {
    "path": "packages/core/src/example-data/gh-fredkiss-dev.ts",
    "chars": 3246147,
    "preview": "import { RscEvent } from '../events';\n\nexport const ghFredkissDevExampleData: RscEvent[] = [\n  {\n    type: 'RSC_REQUEST'"
  },
  {
    "path": "packages/core/src/example-data/nextjs-org.ts",
    "chars": 1690054,
    "preview": "import { RscEvent } from '../events';\n\nexport const nextjsOrgExampleData: RscEvent[] = [\n  {\n    type: 'RSC_REQUEST',\n  "
  },
  {
    "path": "packages/core/src/fetchPatcher.ts",
    "chars": 3877,
    "preview": "import { RscEvent } from './events';\n\nfunction isRscResponse(response: Response): boolean {\n  return response.headers.ge"
  },
  {
    "path": "packages/core/src/main.ts",
    "chars": 936,
    "preview": "import {\n  ViewerPayload,\n  Viewer as unstable_Viewer,\n} from './components/ViewerPayload';\nimport { ViewerStreams } fro"
  },
  {
    "path": "packages/core/src/readNextJsScriptTags.ts",
    "chars": 1204,
    "preview": "import { RscEvent } from './events';\n\nexport function readNextJsScriptTags(): RscEvent[] | undefined {\n  try {\n    // @t"
  },
  {
    "path": "packages/core/src/tailwind.css",
    "chars": 56,
    "preview": "@import 'tailwindcss';\n@config '../tailwind.config.ts';\n"
  },
  {
    "path": "packages/core/tailwind.config.ts",
    "chars": 867,
    "preview": "import { Config } from 'tailwindcss';\nimport plugin from 'tailwindcss/plugin';\n\nconst config: Config = {\n  content: ['./"
  },
  {
    "path": "packages/core/tsconfig.json",
    "chars": 491,
    "preview": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \""
  },
  {
    "path": "packages/core/tsconfig.test.json",
    "chars": 86,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\"\n  }\n}\n"
  },
  {
    "path": "packages/core/turbo.json",
    "chars": 296,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"di"
  },
  {
    "path": "packages/core/vite.config.ts",
    "chars": 915,
    "preview": "// vite.config.js\nimport { resolve } from 'path';\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin"
  },
  {
    "path": "packages/embedded/.gitignore",
    "chars": 44,
    "preview": "/node_modules\ndist\nvite.config.ts.timestamp*"
  },
  {
    "path": "packages/embedded/.prettierignore",
    "chars": 4,
    "preview": "dist"
  },
  {
    "path": "packages/embedded/CHANGELOG.md",
    "chars": 4274,
    "preview": "# @rsc-parser/embedded\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n\n## 1.1.1\n\n##"
  },
  {
    "path": "packages/embedded/LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Alvar Lagerlöf\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "packages/embedded/RscDevtoolsPanel.tsx",
    "chars": 5888,
    "preview": "'use client';\n\n// @ts-expect-error Inline styles are not understood by the typescript compiler\nimport styles from '@rsc-"
  },
  {
    "path": "packages/embedded/eslint.config.js",
    "chars": 538,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default [\n  {\n    ignor"
  },
  {
    "path": "packages/embedded/package.json",
    "chars": 1113,
    "preview": "{\n  \"name\": \"@rsc-parser/embedded\",\n  \"packageManager\": \"bun@1.3.3\",\n  \"version\": \"1.1.2\",\n  \"license\": \"MIT\",\n  \"reposi"
  },
  {
    "path": "packages/embedded/tsconfig.json",
    "chars": 492,
    "preview": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \""
  },
  {
    "path": "packages/embedded/turbo.json",
    "chars": 140,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"di"
  },
  {
    "path": "packages/embedded/vite.config.ts",
    "chars": 903,
    "preview": "// vite.config.js\nimport { resolve } from 'path';\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin"
  },
  {
    "path": "packages/react-client/.gitignore",
    "chars": 44,
    "preview": "/node_modules\ndist\nvite.config.ts.timestamp*"
  },
  {
    "path": "packages/react-client/.prettierignore",
    "chars": 4,
    "preview": "dist"
  },
  {
    "path": "packages/react-client/.prettierrc",
    "chars": 26,
    "preview": "{\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": "packages/react-client/CHANGELOG.md",
    "chars": 2087,
    "preview": "# @rsc-parser/react-client\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n\n## 1.1.1"
  },
  {
    "path": "packages/react-client/eslint.config.js",
    "chars": 365,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default [\n  {\n    ignor"
  },
  {
    "path": "packages/react-client/flight.ts",
    "chars": 348,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/package.json",
    "chars": 920,
    "preview": "{\n  \"name\": \"@rsc-parser/react-client\",\n  \"version\": \"1.1.2\",\n  \"license\": \"MIT\",\n  \"packageManager\": \"bun@1.3.3\",\n  \"ty"
  },
  {
    "path": "packages/react-client/src/ReactDOMTypes.ts",
    "chars": 3228,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/ReactFlightClient.ts",
    "chars": 56898,
    "preview": "/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Co"
  },
  {
    "path": "packages/react-client/src/ReactFlightClientConfigBrowser.ts",
    "chars": 650,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/ReactFlightClientConfigBundlerWebpack.ts",
    "chars": 312,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/ReactFlightImportMetadata.ts",
    "chars": 1089,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/ReactFlightServerConfigDOM.ts",
    "chars": 1665,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/ReactSymbols.ts",
    "chars": 404,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/ReactTypes.ts",
    "chars": 1170,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/src/crossOriginStrings.ts",
    "chars": 235,
    "preview": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found"
  },
  {
    "path": "packages/react-client/tsconfig.json",
    "chars": 494,
    "preview": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \""
  },
  {
    "path": "packages/react-client/turbo.json",
    "chars": 216,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"di"
  },
  {
    "path": "packages/react-client/vite.config.ts",
    "chars": 541,
    "preview": "// vite.config.js\nimport { resolve } from 'path';\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin"
  },
  {
    "path": "packages/storybook/.gitignore",
    "chars": 35,
    "preview": "/node_modules\ndist\nstorybook-static"
  },
  {
    "path": "packages/storybook/.storybook/main.ts",
    "chars": 852,
    "preview": "import { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport type { StorybookConfig } "
  },
  {
    "path": "packages/storybook/.storybook/preview-body.html",
    "chars": 183,
    "preview": "<style>\n  :root {\n    --font-code: 'JetBrains Mono';\n  }\n\n  @media (prefers-color-scheme: dark) {\n    body {\n      backg"
  },
  {
    "path": "packages/storybook/.storybook/preview.ts",
    "chars": 326,
    "preview": "import type { Preview } from '@storybook/react-vite';\n\nimport '../../core/dist/style.css';\n\nconst preview: Preview = {\n "
  },
  {
    "path": "packages/storybook/CHANGELOG.md",
    "chars": 4208,
    "preview": "# @rsc-parser/storybook\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n\n## 1.1.1\n\n#"
  },
  {
    "path": "packages/storybook/package.json",
    "chars": 660,
    "preview": "{\n  \"name\": \"@rsc-parser/storybook\",\n  \"private\": true,\n  \"version\": \"1.1.2\",\n  \"license\": \"MIT\",\n  \"packageManager\": \"b"
  },
  {
    "path": "packages/storybook/tsconfig.json",
    "chars": 497,
    "preview": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \""
  },
  {
    "path": "packages/storybook/turbo.json",
    "chars": 215,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"cache\": false,"
  },
  {
    "path": "packages/storybook/vercel.json",
    "chars": 119,
    "preview": "{\n  \"github\": {\n    \"silent\": true\n  },\n  \"buildCommand\": \"turbo run build\",\n  \"outputDirectory\": \"storybook-static\"\n}\n"
  },
  {
    "path": "packages/website/.gitignore",
    "chars": 368,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "packages/website/CHANGELOG.md",
    "chars": 8823,
    "preview": "# @rsc-parser/website\n\n## 1.1.2\n\n### Patch Changes\n\n- c965537: Add \"repository\" to /embedded package.json\n- Updated depe"
  },
  {
    "path": "packages/website/LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Alvar Lagerlöf\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "packages/website/app/ViewerPayloadClientWrapper.tsx",
    "chars": 12030,
    "preview": "'use client';\n\nimport React from 'react';\nimport { ViewerPayload } from '@rsc-parser/core';\n\nimport '@rsc-parser/core/st"
  },
  {
    "path": "packages/website/app/globals.css",
    "chars": 56,
    "preview": "@import 'tailwindcss';\n@config \"../tailwind.config.ts\";\n"
  },
  {
    "path": "packages/website/app/layout.tsx",
    "chars": 893,
    "preview": "import Script from 'next/script';\nimport './globals.css';\nimport { Inter } from 'next/font/google';\nimport { JetBrains_M"
  },
  {
    "path": "packages/website/app/page.tsx",
    "chars": 1278,
    "preview": "import { Metadata } from 'next';\nimport Link from 'next/link';\n\nimport { PayloadViewerClientWrapper } from './ViewerPayl"
  },
  {
    "path": "packages/website/eslint.config.js",
    "chars": 467,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default [\n  {\n    ignor"
  },
  {
    "path": "packages/website/jest.config.js",
    "chars": 347,
    "preview": "/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'jsdom'"
  },
  {
    "path": "packages/website/jest.setup.js",
    "chars": 37,
    "preview": "import('@testing-library/jest-dom');\n"
  },
  {
    "path": "packages/website/next.config.ts",
    "chars": 386,
    "preview": "import { NextConfig } from 'next';\n\nconst nextConfig: NextConfig = {\n  async rewrites() {\n    return [\n      {\n        s"
  },
  {
    "path": "packages/website/package.json",
    "chars": 1127,
    "preview": "{\n  \"name\": \"@rsc-parser/website\",\n  \"version\": \"1.1.2\",\n  \"license\": \"MIT\",\n  \"packageManager\": \"bun@1.3.3\",\n  \"type\": "
  },
  {
    "path": "packages/website/postcss.config.cjs",
    "chars": 72,
    "preview": "module.exports = {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "packages/website/tailwind.config.ts",
    "chars": 607,
    "preview": "import { Config } from 'tailwindcss';\n\nconst config: Config = {\n  content: [\n    './pages/**/*.{js,ts,jsx,tsx,mdx}',\n   "
  },
  {
    "path": "packages/website/tsconfig.json",
    "chars": 715,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"sk"
  },
  {
    "path": "packages/website/turbo.json",
    "chars": 207,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\".n"
  },
  {
    "path": "packages/website/vercel.json",
    "chars": 78,
    "preview": "{\n  \"github\": {\n    \"silent\": true\n  },\n  \"buildCommand\": \"turbo run build\"\n}\n"
  },
  {
    "path": "renovate.json",
    "chars": 798,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"timezone\": \"Europe/Stockholm\",\n  \"rangeStrategy\":"
  },
  {
    "path": "turbo.json",
    "chars": 476,
    "preview": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\n        \"^build\"\n    "
  }
]

About this extraction

This page contains the full source code of the alvarlagerlof/rsc-parser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 183 files (5.2 MB), approximately 1.4M tokens, and a symbol index with 286 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!