development fda491248b56 cached
210 files
234.5 KB
68.1k tokens
200 symbols
1 requests
Download .txt
Showing preview only (278K chars total). Download the full file or copy to clipboard to get everything.
Repository: leonardoventurini/meteor-devtools-evolved
Branch: development
Commit: fda491248b56
Files: 210
Total size: 234.5 KB

Directory structure:
gitextract_md1jik7s/

├── .babelrc
├── .claude/
│   └── settings.json
├── .editorconfig
├── .envrc
├── .eslintrc.js
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .serena/
│   ├── .gitignore
│   ├── memories/
│   │   ├── architecture_patterns.md
│   │   ├── code_style_and_conventions.md
│   │   ├── codebase_structure.md
│   │   ├── project_overview.md
│   │   ├── security_and_auditing.md
│   │   ├── suggested_commands.md
│   │   ├── task_completion_checklist.md
│   │   └── tech_stack.md
│   └── project.yml
├── .yarnrc.yml
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── devapp-2.0.0/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   ├── links.js
│   │   │   └── random.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Hello.jsx
│   │       └── Info.jsx
│   ├── package.json
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── devapp-2.2.0/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   ├── links.js
│   │   │   └── random.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Hello.jsx
│   │       └── Info.jsx
│   ├── package.json
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── devapp-2.2.4/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   ├── links.js
│   │   │   └── random.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Hello.jsx
│   │       └── Info.jsx
│   ├── package.json
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── devapp-3.4/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── .swcrc
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   └── links.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Counter.jsx
│   │       ├── Header.jsx
│   │       ├── Info.jsx
│   │       └── styles.css
│   ├── package.json
│   ├── rspack.config.js
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── eslint.config.mjs
├── extension/
│   ├── devtools-panel.html
│   ├── devtools.html
│   ├── manifest-v2.json
│   ├── manifest-v3.json
│   ├── options.html
│   └── popup.html
├── lint-staged.js
├── package.json
├── postcss.config.js
├── src/
│   ├── Analytics.ts
│   ├── App.tsx
│   ├── AppToaster.jsx
│   ├── Bridge.ts
│   ├── Browser/
│   │   ├── Background.ts
│   │   ├── Content.ts
│   │   ├── DevTools.ts
│   │   ├── Inject.ts
│   │   └── MeteorLibrary.ts
│   ├── Components/
│   │   ├── Button.tsx
│   │   ├── Field.tsx
│   │   ├── PopoverButton.tsx
│   │   ├── Separator.tsx
│   │   ├── StatusBar.tsx
│   │   ├── TabBar.tsx
│   │   └── TextInput.tsx
│   ├── Constants.ts
│   ├── Database/
│   │   └── PanelDatabase.ts
│   ├── Injectors/
│   │   ├── DDPInjector.ts
│   │   ├── MeteorAdapter.ts
│   │   └── MinimongoInjector.ts
│   ├── Log.ts
│   ├── Pages/
│   │   ├── Options.tsx
│   │   ├── Panel/
│   │   │   ├── Bookmarks/
│   │   │   │   ├── Bookmarks.tsx
│   │   │   │   └── BookmarksStatus.tsx
│   │   │   ├── DDP/
│   │   │   │   ├── DDP.tsx
│   │   │   │   ├── DDPContainer.tsx
│   │   │   │   ├── DDPFilterMenu.tsx
│   │   │   │   ├── DDPLog.tsx
│   │   │   │   ├── DDPLogDirection.tsx
│   │   │   │   ├── DDPLogMenu.tsx
│   │   │   │   ├── DDPLogPreview.tsx
│   │   │   │   ├── DDPStatus.tsx
│   │   │   │   └── FilterConstants.ts
│   │   │   ├── DrawerJSON.tsx
│   │   │   ├── DrawerStackTrace.tsx
│   │   │   ├── HelpDrawer.tsx
│   │   │   ├── Minimongo/
│   │   │   │   ├── Minimongo.tsx
│   │   │   │   ├── MinimongoContainer.tsx
│   │   │   │   ├── MinimongoNavigator.tsx
│   │   │   │   ├── MinimongoRow.tsx
│   │   │   │   └── MinimongoStatus.tsx
│   │   │   ├── Navigation.tsx
│   │   │   ├── PartnersGrid.tsx
│   │   │   ├── Performance/
│   │   │   │   └── Performance.tsx
│   │   │   └── Subscriptions/
│   │   │       └── Subscriptions.tsx
│   │   ├── Panel.tsx
│   │   └── Popup.tsx
│   ├── Stores/
│   │   ├── Common/
│   │   │   └── Searchable.ts
│   │   ├── Panel/
│   │   │   ├── BookmarkStore.ts
│   │   │   ├── DDPStore.ts
│   │   │   ├── MinimongoStore/
│   │   │   │   ├── CollectionStore.ts
│   │   │   │   └── index.ts
│   │   │   ├── PerformanceStore.ts
│   │   │   ├── SettingStore.ts
│   │   │   └── SubscriptionStore.ts
│   │   └── PanelStore.tsx
│   ├── Styles/
│   │   ├── App.scss
│   │   ├── Breakpoints.ts
│   │   ├── Constants.ts
│   │   ├── Mixins.ts
│   │   ├── Tailwind.css
│   │   └── _Utils.scss
│   ├── Utils/
│   │   ├── BackgroundEvents.ts
│   │   ├── Hideable.tsx
│   │   ├── Hooks/
│   │   │   ├── useAnalytics.ts
│   │   │   ├── useBreakpoints.ts
│   │   │   ├── useDimensions.ts
│   │   │   ├── useInterval.ts
│   │   │   └── useResize.ts
│   │   ├── JSONUtils.ts
│   │   ├── MessageFormatter.ts
│   │   ├── Numbers.ts
│   │   ├── ObjectTreerinator/
│   │   │   ├── ArrayNodeRenderer.tsx
│   │   │   ├── ArrayRenderer.tsx
│   │   │   ├── BooleanRenderer.tsx
│   │   │   ├── Collapsible.tsx
│   │   │   ├── NullRenderer.tsx
│   │   │   ├── NumberRenderer.tsx
│   │   │   ├── ObjectRenderer.tsx
│   │   │   ├── StringRenderer.tsx
│   │   │   └── index.tsx
│   │   ├── Objects.ts
│   │   ├── Pagination.ts
│   │   ├── StringUtils.ts
│   │   └── index.ts
│   └── index.d.ts
├── tailwind.config.js
├── tsconfig.json
└── webpack/
    ├── base.js
    ├── chrome.dev.js
    ├── chrome.prod.js
    ├── firefox.dev.js
    ├── firefox.prod.js
    └── utils.js

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

================================================
FILE: .babelrc
================================================
{
  "presets": ["@babel/env", "@babel/react"]
}


================================================
FILE: .claude/settings.json
================================================
{
  "enabledPlugins": {
    "frontend-design@claude-plugins-official": true,
    "context7@claude-plugins-official": true,
    "code-review@claude-plugins-official": true,
    "feature-dev@claude-plugins-official": true,
    "code-simplifier@claude-plugins-official": true,
    "typescript-lsp@claude-plugins-official": true,
    "superpowers@claude-plugins-official": true
  },
  "defaultMode": "bypassPermissions",
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Glass.aiff"
          }
        ]
      }
    ],
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Funk.aiff"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "yarn prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" && yarn eslint --fix \"$CLAUDE_TOOL_INPUT_FILE_PATH\" || exit 2"
          }
        ]
      }
    ]
  }
}


================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true

[*.{js, jsx, ts, tsx, groovy}]
indent_size = 2
indent_style = space
ij_visual_guides = 80


================================================
FILE: .envrc
================================================
#!/usr/bin/env bash
export DEVTOOLS_HOME="$(git rev-parse --show-toplevel)"
export MAC_CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

function mpm {
  meteor npm $@
}

function start {
  yarn devapp
}

function develop {
  local BROWSER=${1-"chrome"}
  echo "Starting Development mode for => ${BROWSER}"
  yarn concurrently -n ext,app "webpack --config webpack/${BROWSER}.dev.js" "cd devapp-3.4 && npm start"
}

function watch {
  local BROWSER=${1-"chrome"}
  yarn webpack --config webpack/${BROWSER}.dev.js
}

function setup {
  yarn
  cd devapp-3.4 || exit
  npm install
}

function update-meteor {
  cd devapp-3.4 || exit
  meteor update
  cd ..
}

function package-version {
  grep version <package.json |
    head -1 |
    awk -F: '{ print $2 }' |
    sed 's/[", ]//g'
}

function build-for-browser {
  local BROWSER=$1
  local VERSION=$(package-version)

  mkdir releases

  yarn run build:${BROWSER}

  cd extension/${BROWSER} || exit

  zip -r "../../releases/meteor-devtools-evolved-${VERSION}.${BROWSER}.zip" -- *

  cd - || exit
}

function build {
  build-for-browser chrome
  build-for-browser firefox
}


================================================
FILE: .eslintrc.js
================================================
const { merge } = require('lodash')

module.exports = merge(require('@tstt/eslint-config/index.js'), {
  rules: {
    'global-require': 0,
    '@typescript-eslint/no-var-requires': 0,
    '@typescript-eslint/no-inferrable-types': [
      2,
      {
        ignoreParameters: true,
        ignoreProperties: true,
      },
    ],
  },
  globals: { Meteor: 'readonly', i18n: 'readonly' },
})


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

on:
  push:
    branches:
      - development
  pull_request:
  workflow_dispatch:

jobs:
  lint-and-audit:
    name: Lint & Security Audit
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Enable Corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --immutable

      - name: Run lint
        run: yarn run lint

      - name: Run security audit
        run: yarn run audit


================================================
FILE: .gitignore
================================================
.DS_Store
.idea
node_modules
chrome/build
firefox/build
chrome.pem
firefox.pem
extension/chrome
extension/firefox
mongo-decimal-test-main
.eslintcache
yarn-error.log
.yarn


================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged -s


================================================
FILE: .prettierignore
================================================
node_modules/
extension/
devapp-3.4/
.yarn/
webpack/
*.lock
yarn.lock
package-lock.json


================================================
FILE: .prettierrc.json
================================================
{
  "plugins": ["prettier-plugin-tailwindcss"],
  "semi": false,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "bracketSpacing": true,
  "jsxSingleQuote": true,
  "arrowParens": "avoid"
}


================================================
FILE: .serena/.gitignore
================================================
/cache


================================================
FILE: .serena/memories/architecture_patterns.md
================================================
# Architecture and Design Patterns

## Browser Extension Architecture

### Multi-Context Design

The extension operates in multiple browser contexts:

1. **DevTools Panel** (`src/Pages/Panel`) - Main UI for DDP tracking and Minimongo inspection
2. **Options Page** (`src/Pages/Options`) - Extension settings and configuration
3. **Browser Action Popup** (`src/Pages/Popup`) - Quick access popup from toolbar
4. **Background Scripts** - Browser extension background processes (in `src/Browser/`)
5. **Content Scripts** - Injected into Meteor app pages (in `src/Injectors/`)

### Communication Bridge

- `src/Bridge.ts` handles inter-context communication
- Uses browser extension messaging APIs
- Coordinates between DevTools panel, content scripts, and page context

## State Management with MobX

### Store Pattern

- MobX 6 with `mobx-react-lite` for React integration
- Stores located in `src/Stores/`
- Likely uses observable state with computed values and actions
- React components observe stores using hooks

### State Persistence

- **Dexie (IndexedDB)** for persistent storage (bookmarks)
- Located in `src/Database/`
- Allows saving DDP messages across sessions and browser restarts

## Component Architecture

### UI Framework Hierarchy

1. **Blueprint** - Primary component library (buttons, dialogs, forms, etc.)
2. **Custom Components** - Project-specific components in `src/Components/`
3. **Styled Components** - CSS-in-JS for component-specific styling
4. **SCSS** - Global styles and theming
5. **Tailwind CSS + DaisyUI** - Utility classes for rapid styling

### Component Patterns

- Functional components with React Hooks (React 17)
- MobX observer components using `observer()` HOC or hooks
- Likely uses `react-singleton-hook` for global singletons
- `react-window` for virtualized lists (performance for large DDP message lists)

## Data Visualization

### D3.js Integration

Used for visualizing Meteor/DDP data structures:

- **d3-hierarchy** - Tree structures for Minimongo documents
- **d3-shape** - Shapes for data visualization
- **d3-selection** - DOM manipulation for charts
- **d3-collection** - Data structure utilities

Custom "Object Treerinator" for document visualization (mentioned in README)

## Performance Optimizations

### Virtualization

- `react-window` + `react-window-infinite-loader` for handling thousands of DDP messages
- Virtual scrolling prevents DOM bloat with large datasets

### Memoization & Throttling

- `lodash.memoize` - Cache expensive computations
- `lodash.debounce` - Debounce user input
- `lodash.throttle` - Throttle high-frequency events (DDP message processing)

## Build System

### Webpack Configuration

- **Base config** - Shared settings
- **Dev builds** - Watch mode, source maps, faster builds
- **Prod builds** - Minification, optimization
- **Target-specific** - Different configs for Chrome vs Firefox
  - Chrome uses Manifest V3
  - Firefox may use Manifest V2 or adapted V3

### Asset Handling

- `file-loader` - Images and static assets
- `css-loader` + `style-loader` - CSS module handling
- `sass-loader` - SCSS compilation
- `postcss-loader` - PostCSS transformations (Tailwind)
- `ts-loader` - TypeScript compilation
- `babel-loader` - JavaScript transpilation

### Code Splitting

Likely splits code by extension context (panel, popup, options, background)

## Extension-Specific Patterns

### Webextension Polyfill

- `webextension-polyfill` provides cross-browser compatibility
- Converts callback-based APIs to Promises
- Allows same code to work on Chrome and Firefox

### Injectors Pattern

- Content scripts in `src/Injectors/` inject code into Meteor app pages
- Intercepts DDP messages by hooking into Meteor's DDP client
- Sends captured data to DevTools panel via messaging bridge

### Analytics

- `src/Analytics.ts` likely tracks usage (opt-in)
- Helps understand feature usage and bugs

## Development Workflow

### Hot Module Replacement

- Webpack dev builds watch for changes
- Browser extension reloads on rebuild
- Devapp runs concurrently on port 3000

### Concurrent Development

- `npm-run-all` / `concurrently` - Run multiple processes
- `wait-on` - Synchronize startup (wait for builds and devapp)
- `web-ext run` - Launches browser with extension loaded

## Styling Strategy

### Layered Approach

1. **Normalize.css** - Base reset
2. **Tailwind base** - Utility-first foundation
3. **DaisyUI** - Component classes on top of Tailwind
4. **Blueprint** - Pre-built React components
5. **SCSS modules** - Component-specific styles
6. **Styled Components** - Dynamic, prop-based styling
7. **Polished** - Color manipulation and mixins for styled-components

This multi-layered approach provides flexibility while maintaining consistency.


================================================
FILE: .serena/memories/code_style_and_conventions.md
================================================
# Code Style and Conventions

## ESLint Configuration

The project extends `@tstt/eslint-config` with custom overrides:

- **global-require**: Disabled (allows require() anywhere)
- **@typescript-eslint/no-var-requires**: Disabled (allows const x = require())
- **@typescript-eslint/no-inferrable-types**: Error with ignoreParameters and ignoreProperties
- **Global variables**: `Meteor` and `i18n` are defined as readonly

## Prettier Configuration

Inherits from `@tstt/eslint-config/.prettierrc.js` (exact settings not visible but standard Prettier defaults)

## EditorConfig Settings

- **Indentation**: 2 spaces (for .js, .jsx, .ts, .tsx, .groovy)
- **Line endings**: LF (Unix-style)
- **Charset**: UTF-8
- **Trailing whitespace**: Trimmed
- **Final newline**: Required
- **Visual guides**: 80 characters

## TypeScript Configuration

- **Strict mode**: Disabled (`strict: false`)
- **noImplicitAny**: Disabled
- **Target**: ES6
- **Module**: CommonJS
- **Module resolution**: Node
- **Decorators**: Experimental decorators enabled
- **JSX**: React
- **Source maps**: Enabled
- **ESModuleInterop**: Enabled
- **Path mapping**: `@/*` → `src/*`

## Naming Conventions

Based on code inspection:

- **Components**: PascalCase (e.g., `Panel`, `Options`, `Popup`)
- **Files**: Match component names for React components
- **Constants**: Likely UPPER_SNAKE_CASE (based on Constants.ts)
- **Utilities**: camelCase functions

## Code Organization Patterns

- **React**: Functional components with hooks (React 17)
- **State Management**: MobX stores for global state
- **Styling**: Mix of styled-components, SCSS modules, and Tailwind utilities
- **Type Definitions**: Global types in `src/index.d.ts`
- **Imports**: Use path alias `@/` for src directory imports

## Component Structure

Based on `App.tsx`:

- Import external libraries first (React, Blueprint, etc.)
- Then internal components/pages
- Finally styles (CSS/SCSS)
- Component logic and rendering

## Best Practices (from CONTRIBUTING.md)

1. Code must be linted and properly formatted
2. Features should benefit the Meteor community as a whole
3. Be friendly and supportive in contributions
4. Use appropriate IDE (WebStorm recommended by maintainer)


================================================
FILE: .serena/memories/codebase_structure.md
================================================
# Codebase Structure

## Root Directory

```
meteor-devtools-evolved/
├── src/                    # Main source code
├── devapp/                 # Test Meteor application for development
├── webpack/                # Webpack configuration files
├── extension/              # Built extension output (generated, not in git)
├── releases/               # Release builds
├── .husky/                 # Git hooks
├── .github/                # GitHub Actions and configuration
└── [config files]          # Various configuration files
```

## Source Directory (`src/`)

```
src/
├── Assets/         # Static assets (images, icons, etc.)
├── Browser/        # Browser extension specific code (background, content scripts)
├── Components/     # Reusable React components
├── Database/       # Dexie (IndexedDB) setup and models
├── Injectors/      # Content script injectors for page context
├── Pages/          # Main extension pages
│   ├── Panel       # DevTools panel (main UI)
│   ├── Options     # Extension options page
│   └── Popup       # Browser action popup
├── Stores/         # MobX state stores
├── Styles/         # Global styles (SCSS, CSS, Tailwind)
├── Utils/          # Utility functions and helpers
├── App.tsx         # Main React application entry
├── Bridge.ts       # Communication bridge between contexts
├── Constants.ts    # Global constants
├── Log.ts          # Logging utilities
└── Analytics.ts    # Analytics tracking
```

## Webpack Configuration (`webpack/`)

- `base.js` - Base configuration shared by all builds
- `chrome.dev.js` / `chrome.prod.js` - Chrome-specific builds
- `firefox.dev.js` / `firefox.prod.js` - Firefox-specific builds
- `utils.js` - Webpack utility functions

## Development App (`devapp/`)

A minimal Meteor application used for testing the extension during development:

- Runs on port 3000
- Standard Meteor project structure
- Uses Meteor's module system
- Includes basic client and server code

## Key Entry Points

- `src/App.tsx` - Renders Panel, Options, or Popup based on context
- Extension manifests generated by webpack for Chrome/Firefox
- Path alias: `@/*` maps to `src/*` for imports


================================================
FILE: .serena/memories/project_overview.md
================================================
# Meteor DevTools Evolved - Project Overview

## Purpose

Meteor DevTools Evolved is a browser extension for Google Chrome and Mozilla Firefox that helps developers monitor, debug, and optimize their Meteor.js applications. It provides:

- **DDP (Distributed Data Protocol) Tracking**: Filter and search through thousands of DDP messages to understand what's happening under the hood
- **Bookmarks**: Save DDP messages for later search and retrieval (stored in IndexedDB)
- **Minimongo Inspection**: Search and visualize documents in Minimongo with a custom Object Tree viewer

## Project Context

This project is based on the original Meteor DevTools extension by The Bakery (no longer maintained), hence the "evolved" in the name. It was completely rewritten with modern tooling and technologies.

## Distribution

- Chrome Web Store: https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf
- Firefox Add-ons: https://addons.mozilla.org/en-US/firefox/addon/meteor-devtools-evolved/

## Repository

GitHub: https://github.com/leonardoventurini/meteor-devtools-evolved


================================================
FILE: .serena/memories/security_and_auditing.md
================================================
# Security and Auditing

## Audit Script

The project includes a security audit script that checks for high and critical severity vulnerabilities:

```bash
yarn run audit
```

This runs: `yarn npm audit --all --recursive --severity high`

## Security Resolutions

Due to transitive dependencies, the project uses Yarn resolutions to enforce secure versions of nested dependencies. These are defined in `package.json` under the `"resolutions"` field.

### Current Security Resolutions

The following packages are pinned to secure versions to address known vulnerabilities:

- **@babel/traverse**: ^7.23.2 - Fixes arbitrary code execution vulnerability
- **axios**: ^1.6.0 - Fixes SSRF and credential leakage vulnerability
- **braces**: ^3.0.3 - Fixes uncontrolled resource consumption
- **cross-spawn**: ^7.0.5 - Fixes Regular Expression Denial of Service (ReDoS)
- **fast-json-patch**: ^3.1.1 - Fixes prototype pollution
- **form-data**: ^4.0.0 - Fixes unsafe random function in boundary generation
- **http-cache-semantics**: ^4.1.1 - Fixes ReDoS vulnerability
- **json5**: ^2.2.3 - Fixes prototype pollution via parse method
- **jsonwebtoken**: ^9.0.0 - Fixes unrestricted key type vulnerability
- **jws**: ^4.0.0 - Fixes improper HMAC signature verification
- **loader-utils**: ^3.2.1 - Fixes prototype pollution and ReDoS vulnerabilities
- **node-forge**: ^1.3.2 - Fixes ASN.1 unbounded recursion
- **qs**: ^6.14.1 - Fixes DoS via memory exhaustion
- **semver**: ^7.5.4 - Fixes ReDoS vulnerabilities across multiple version ranges
- **sha.js**: ^2.4.12 - Fixes missing type checks
- **ws**: ^8.17.1 - Fixes DoS when handling requests with many HTTP headers

## Security Update Process

When security vulnerabilities are discovered:

1. Run `yarn run audit` to identify issues
2. Update direct dependencies where possible
3. Add/update resolutions for transitive dependencies
4. Run `yarn install` to apply changes
5. Run `yarn run audit` again to verify fixes
6. Test the application to ensure nothing broke
7. Commit the changes with a clear security-focused message

## Regular Security Maintenance

- Run `yarn run audit` regularly (monthly recommended)
- Keep direct dependencies up to date
- Monitor GitHub security advisories
- Review and update resolutions when new vulnerabilities are disclosed

## Important Notes

- The project uses **Yarn 4.12.0**, not npm, so use `yarn` commands
- Resolutions in package.json override nested dependency versions
- Some older dependencies (like file-loader) may have vulnerabilities that require resolutions because they're no longer maintained
- Always test builds after security updates: `yarn build:chrome` and `yarn build:firefox`


================================================
FILE: .serena/memories/suggested_commands.md
================================================
# Suggested Commands

## Initial Setup

```bash
# Install dependencies for both root and devapp
yarn setup
```

## Development

```bash
# Start development mode for Chrome (default)
yarn dev

# Start development mode specifically for Chrome
yarn dev:chrome

# Start development mode for Firefox
yarn dev:firefox

# Run just the devapp (Meteor test application)
yarn devapp
```

**Note**: The `yarn dev` commands will:

1. Build and watch the extension
2. Run the devapp in parallel
3. Launch a browser instance with the extension installed
4. Auto-reload on code changes

## Building

```bash
# Build Chrome extension for production
yarn build:chrome

# Build Firefox extension for production
yarn build:firefox

# Clean build artifacts
yarn clean
```

## Linting & Formatting

```bash
# Run ESLint on all source files
yarn lint

# Prettier and ESLint run automatically via pre-commit hooks
# Manual formatting is handled by lint-staged
```

## Security Auditing

```bash
# Check for high and critical security vulnerabilities
yarn run audit

# This runs: yarn npm audit --all --recursive --severity high
```

## Git Operations

```bash
# Standard git commands work normally
git status
git add <files>
git commit -m "message"  # Pre-commit hook runs lint-staged automatically
git push
```

## Useful System Commands (macOS/Darwin)

```bash
# List files
ls -la

# Search for files
find . -name "*.tsx"

# Search in files
grep -r "pattern" src/

# View file contents
cat <file>
head -n 20 <file>
tail -n 20 <file>

# Navigate directories
cd <directory>
pwd
```

## Testing Extension Manually

After running `yarn dev:chrome` or `yarn dev:firefox`, the extension will be loaded in a browser instance. Navigate to `http://localhost:2100` to see the devapp and test the extension's DevTools panel.

## Node/Yarn Version Management

The project uses Volta to manage Node and Yarn versions:

- Node.js: 14.19.3
- Yarn: 1.22.18

Volta will automatically use the correct versions if installed.

## Troubleshooting

```bash
# If dependencies are out of sync, reinstall
rm -rf node_modules yarn.lock
yarn install

# If devapp has issues
cd devapp
rm -rf node_modules yarn.lock
yarn install
cd ..

# Reset Meteor (if devapp is broken)
cd devapp
meteor reset
cd ..
```


================================================
FILE: .serena/memories/task_completion_checklist.md
================================================
# Task Completion Checklist

When completing a coding task in this project, follow these steps:

## 1. Pre-Commit Automatic Checks

The project uses Husky + lint-staged for pre-commit hooks. When you commit, the following runs automatically:

- ✓ ESLint on all staged .js, .jsx, .ts, .tsx files
- ✓ TypeScript type checking (tsc --noEmit)
- ✓ Prettier formatting on staged files
- ✓ React Scripts tests (if applicable, with --passWithNoTests)

**If the pre-commit hook fails**, fix the issues before committing:

```bash
# Run lint manually to see all issues
yarn lint

# TypeScript errors need to be fixed in code
# ESLint and Prettier issues are often auto-fixed by lint-staged
```

## 2. Manual Verification

After making changes, verify:

### Linting

```bash
yarn lint
```

Ensure no ESLint errors or warnings.

### Security Audit

```bash
yarn run audit
```

Ensure no high or critical security vulnerabilities are introduced.

### Building

```bash
# Test Chrome build
yarn build:chrome

# Test Firefox build
yarn build:firefox
```

Ensure both builds complete successfully without errors.

### Manual Testing

```bash
# Start dev environment
yarn dev:chrome  # or yarn dev:firefox
```

- Open the browser instance that launches
- Navigate to http://localhost:2100 (devapp)
- Open DevTools and find the "Meteor" panel
- Test your changes manually in the extension UI

## 3. Code Quality Checks

Before considering the task done:

- [ ] TypeScript compiles without errors
- [ ] ESLint shows no errors or warnings
- [ ] Code follows project conventions (2-space indent, LF line endings, etc.)
- [ ] No unused imports or variables
- [ ] MobX stores updated if state changes
- [ ] Components are properly typed
- [ ] Path imports use `@/` alias where appropriate

## 4. Cross-Browser Compatibility

If the change affects browser-specific code:

- [ ] Test in Chrome build (`yarn dev:chrome`)
- [ ] Test in Firefox build (`yarn dev:firefox`)
- [ ] Check for webextension-polyfill usage for cross-browser APIs

## 5. Git Commit

```bash
git add <changed-files>
git commit -m "descriptive message"
# Pre-commit hooks run automatically
```

## 6. Common Issues

### Pre-commit hook fails

- Check `yarn lint` output
- Run prettier manually if needed
- Fix TypeScript errors shown by tsc

### Build fails

- Check webpack output for specific errors
- Verify all imports exist and are correct
- Check for TypeScript compilation errors

### Extension doesn't load

- Check browser console for errors
- Verify manifest.json was generated correctly
- Check extension/chrome or extension/firefox directories exist

## Notes

- The devapp must be running for the extension to work properly
- IndexedDB data persists between sessions (for bookmarks)
- Check Chrome DevTools console in the extension context for runtime errors


================================================
FILE: .serena/memories/tech_stack.md
================================================
# Tech Stack

## Core Technologies

- **TypeScript** 4.4.3 - Main programming language with ES6 target
- **React** 17.0.2 - UI framework (functional components with hooks)
- **MobX** 6.4.0 - State management library
- **Webpack** 5 - Module bundler and build tool

## UI & Styling

- **Blueprint** 4.14.1 - Core UI components library by Palantir
- **Styled Components** 5.3.3 - CSS-in-JS styling
- **SASS/SCSS** - Additional styling with sass-loader
- **Tailwind CSS** 3.0.24 - Utility-first CSS framework
- **DaisyUI** 2.15.2 - Tailwind CSS component library
- **Normalize.css** - CSS reset
- **Heroicons React** - Icon library

## Data & State

- **Dexie** 3.2.2 - IndexedDB wrapper for bookmarks storage
- **mobx-react-lite** 3.3.0 - React bindings for MobX

## Utilities

- **Luxon** 2.5.2 - Date/time manipulation
- **Lodash** (selective imports: debounce, memoize, sortby, throttle)
- **D3** (collection, hierarchy, selection, shape) - Data visualization
- **pretty-bytes** - Byte formatting
- **uuid** - Unique ID generation

## Build Tools & Dev Environment

- **Babel** 7 - JavaScript transpiler
- **ts-loader** - TypeScript loader for Webpack
- **PostCSS** - CSS processing
- **Terser** - JavaScript minification
- **web-ext** - Browser extension development tool
- **concurrently** / **npm-run-all** - Run multiple commands
- **wait-on** - Wait for resources before starting

## Code Quality

- **ESLint** - JavaScript/TypeScript linter (extends @tstt/eslint-config)
- **Prettier** - Code formatter (from @tstt/eslint-config)
- **Husky** - Git hooks
- **lint-staged** - Run linters on staged files

## Runtime Environment

- **Node.js** 14.19.3 (managed by Volta)
- **Yarn** 1.22.18 (managed by Volta)

## Browser APIs

- **@types/chrome** - Chrome extension API types
- **webextension-polyfill** - Cross-browser extension API


================================================
FILE: .serena/project.yml
================================================
# the name by which the project can be referenced within Serena
project_name: 'meteor-devtools-evolved'

# list of languages for which language servers are started; choose from:
#   al                  bash                clojure             cpp                 csharp
#   csharp_omnisharp    dart                elixir              elm                 erlang
#   fortran             fsharp              go                  groovy              haskell
#   java                julia               kotlin              lua                 markdown
#   matlab              nix                 pascal              perl                php
#   powershell          python              python_jedi         r                   rego
#   ruby                ruby_solargraph     rust                scala               swift
#   terraform           toml                typescript          typescript_vts      vue
#   yaml                zig
#   (This list may be outdated. For the current list, see values of Language enum here:
#   https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
#   For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
#   - For C, use cpp
#   - For JavaScript, use typescript
#   - For Free Pascal/Lazarus, use pascal
# Special requirements:
#   Some languages require additional setup/installations.
#   See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
  - typescript

# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: 'utf-8'

# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true

# list of additional paths to ignore in all projects
# same syntax as gitignore, so you can use * and **
ignored_paths: []

# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false

# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
#  * `activate_project`: Activates a project by name.
#  * `check_onboarding_performed`: Checks whether project onboarding was already performed.
#  * `create_text_file`: Creates/overwrites a file in the project directory.
#  * `delete_lines`: Deletes a range of lines within a file.
#  * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
#  * `execute_shell_command`: Executes a shell command.
#  * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
#  * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
#  * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
#  * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
#  * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
#  * `initial_instructions`: Gets the initial instructions for the current project.
#     Should only be used in settings where the system prompt cannot be set,
#     e.g. in clients you have no control over, like Claude Desktop.
#  * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
#  * `insert_at_line`: Inserts content at a given line in a file.
#  * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
#  * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
#  * `list_memories`: Lists memories in Serena's project-specific memory store.
#  * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
#  * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
#  * `read_file`: Reads a file within the project directory.
#  * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
#  * `remove_project`: Removes a project from the Serena configuration.
#  * `replace_lines`: Replaces a range of lines within a file with new content.
#  * `replace_symbol_body`: Replaces the full definition of a symbol.
#  * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
#  * `search_for_pattern`: Performs a search for a pattern in the project.
#  * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
#  * `switch_modes`: Activates modes by providing a list of their names
#  * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
#  * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
#  * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
#  * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []

# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
included_optional_tools: []

# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []

# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:

# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:

# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ''


================================================
FILE: .yarnrc.yml
================================================
nodeLinker: node-modules


================================================
FILE: CHANGELOG.md
================================================
# Change Log

All notable changes to this project will be documented in this file.

> The dates refer to when it was made available in the Chrome platform.

## [1.8] - 2023-01-17

## Fixed

- Fixed a bug where the extension crashes on custom Minimongo types specifically on MongoDecimal type.
- Issue where the lodash package would break the extension randomly on some pages and reduced bundle size.

## Added

- Help drawer containing contact information for the author and for partners. Moved the content from the About drawer to the Help drawer.
- Added sponsor button so users can support the project.

## [1.7] - 2022-07-05

## Added

- The Firefox browser is now supported. Thanks to Niloy.

## Changed (development only)

- Now we can build extensions for both Chrome and Firefox using the same codebase.
- New commands added to support the workflow.

## [1.6] - 2022-06-08

## Added

- Now logs are intercepted and stored in the background and loaded when you open the devtools panel.
- Add Meteor emoji to devtool tab.
- Add emojis to some actions in the top bar.
- Add Galaxy Sponsor content.

## Removed

- Removed CRC32 hashes, they did not have much use besides looking cool. Improves performance a bit.

## [1.5] - 2022-04-14

## Added

- Google Analytics for improving the extension.
- The browser action of the extension opens Galaxy.
- Add subscription duration, so we know how long specific subscriptions take.
- Performance tab which measures Minimongo calls.

## Changed

- Upgrade dependencies to latest.
- Add copy JSON button to minimongo drawer.
- Improved minimongo tracking performance and responsiveness.
- By default JSON documents are expanded up to 5 levels now.
- Remove Iosevka font, the default monospaced font from OS.
- Now the extension is loaded slightly earlier, so we don't miss initial Meteor activity.

## Fixed

- Fix stack trace error issue caused by a third party library affecting some users.

## [1.4] - 2020-07-21

## Changed

- Make stack trace and bookmark buttons more accessible.
- Make right menu more responsive.
- Estimated collection size is always visible for all collections.

## Fixed

- Fix `error-stack-parser` global pollution interacting badly with some websites.

## [1.3] - 2020-06-17

## Added

- Meteor `gitCommitHash` is now shown in the status bar.
- Community Slack button (with VFX!!)
- Added subscription search.
- Estimate Minimongo collection byte size.

## Changed

- Subscriptions are clickable and open the params object viewer.
- Improved naming for the extension global variables to avoid collisions.
- Removed horizontal scroll constraint, but making it more responsive is too much work for now.

## Fixed

- Fixed horizontal scroll showing when resizing.

## [1.2] - 2020-04-29

In this release I am focusing on some quality of life changes and addressing issues reported by the community. I tried to make the design simpler and more efficient as well.

### Added

- Minimongo sidebar navigation ordered alphabetically and with counts.
- Add the Iosevka font as it is more space efficient in certain scenarios.
- Subscriptions tab listing all current subscriptions in real-time-ish.

### Changed

- The DDP log is now a virtualized list with INFINITE scrolling and new logs come at the top.
- Moved extension logs from top frame to background. NO MORE ANNOYING CONSOLE LOGS!!!
- Logs now have their interaction menu as a popover in order to be more space efficient.
- More space-efficient tabs and status bars.

### Fixed

- Small fixes and improvements in Treerinator (JSON Viewer).
- Show subscription name when ready as well.
- Fixed GitHub stats not persisting as they should.

## [1.1] - 2020-04-02

I had to take a small hiatus from development after the initial release, but now I am back with a few quality of life changes and additions. Also, I am attempting to fix an issue where some installations do not initialize and thus don't log the DDP messages, which also happens to be at least a quality of life change. Hope it works, as I could not reproduce the issue, but I added a bunch of logs just in case, I promise.

### Added

- [Issue #1](https://github.com/leonardoventurini/meteor-devtools-evolved/issues/1)
  Added ability to replay methods either from the logs or bookmarks.
- Added document count to collection navigator.
- Added Minimongo active collection clear button.
- Added GitHub buttons to make receiving feedback easier.
- Added long timestamp format on hover for logs which is useful for bookmarks.
- Added setting persistence, which means the filters will persist between sessions.
- Added about page with some basics and license information.

### Changed

- Adjusted the layout, so it is responsive to screens with less horizontal real-estate.
- Collection tags are now clickable.

### Fixed

- [Issue #2](https://github.com/leonardoventurini/meteor-devtools-evolved/issues/2)
  The extension now initializes from the content script, which means that we don't need the devtools panel open for initialization -- but we do need it for DDP logging.

## [1.0] - 2020-03-05

Initial release.

### Added

- Added DDP logging.
- Added DDP bookmarking.
- Added Minimongo browsing.
- Added search and pagination.
- Added a bunch of stuff really.


================================================
FILE: CLAUDE.md
================================================
IMPORTANT: The first thing you ever do is to start the project `meteor-devtools-evolved` in the Serena MCP server


================================================
FILE: CONTRIBUTING.md
================================================
## Setting the Environment Up

1. Install dependencies for `devapp-3.4` & `root` with `yarn`.

```shell
yarn setup
```

> As of now we use Node.js `v14.19.3`.

2. Run the extension locally

```shell
yarn dev # default chrome
```

```shell
yarn dev:chrome # for chrome
```

```shell
yarn dev:firefox # for firefox
```

> This command will build and watch the extension and run the `devapp-3.4` in parallel mode and when they are ready it will launch the chrome/firefox private instance with extension installed

5. Hack away!

   > Open a Pull Request from your fork to our repo once it is done or need a review.

## Environment Commands

If you use Linux you can run `source .envrc` for some useful commands

> -c: for chrome, -f: firefox, (chrome is default)

- Setup extension and test project Dependencies

```shell
setup
```

## Build

- Chrome

```shell
npm run build:chrome
```

- Firefox

```shell
npm run build:firefox
```

## Guidelines & Objectives

1. The code must be linted and properly formatted, that can be easily done with the right IDE -- I use JetBrains WebStorm. Perhaps some git hooks would come in handy in the future.
2. Every feature needs to take into account the Meteor community as a whole and not the interest of a few in detriment of others.
3. Be friendly and supportive, no one is perfect, and we all have limited time, especially in these difficult times.


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2020 Leonardo Venturini

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
================================================
<div align="center">

<img src="https://media.giphy.com/media/Pt2yOXUALOhpB5dpiM/giphy.gif" alt="Meteor Devtool Evolved Gif" />

<p style="font-size: 30px">
Meteor Devtools Extension
</p>
Behold, the evolution of Meteor DevTools.</p>

Meteor Devtools Evolved is currently available for Google Chrome and Mozilla Firefox.

</div>

<p align="center" >
    <a href="https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf">
    <img width="120" src="https://img.shields.io/badge/%20-Chrome-orange?logo=google-chrome&logoColor=white" alt="Download for Chrome" />
    </a>
    <a href="https://addons.mozilla.org/en-US/firefox/addon/meteor-devtools-evolved/">
    <img width="110" src="https://img.shields.io/badge/%20-Firefox-red?logo=mozilla&logoColor=white" alt="Download for Firefox" />
    </a>
</p>

[Harder, Better, Faster, Stronger](https://www.youtube.com/watch?v=gAjR4_CbPpQ) :rocket:

Are you beginning with Meteor? Do you want to get a sense of "what is going on" or even to optimize your Meteor app? This is the tool for you.

:point_right: [Changelog](CHANGELOG.md)

### Distributed Data Protocol (DDP)

Everything you need to track and understand what is going on under the hood of your Meteor application. The extension allows you to filter and search for any DDP message, being able to handle thousands and thousands of messages without a hiccup.

### Bookmarks

The DDP inspection is ephemeral, but you can save as many DDP messages you want for later search and retrieval, from any host. Be careful though, it is saved on IndexedDB.

### Minimongo

You don't know what data belongs to where? You can rapidly search for anything in your Minimongo data and easily visualize the documents with our blazing fast custom-made Object Treerinator.

---

## Development

> DISCLAIMER: This work is based in part on the [Meteor DevTools](https://github.com/bakery/meteor-devtools) extension by The Bakery. Which sadly is not maintained anymore. While it is not necessarily a fork, I did use some useful knowledge and architectural decisions, and some things naturally converged into the same most practical solution. Hence the "evolved".

The extension is almost entirely written in TypeScript, while some Chrome specific code being left out for practical reasons. It uses MobX to manage state, and SASS its styles. We also use components from the [Blueprint](https://github.com/palantir/blueprint) library by Palantir. Everything is glued together with Webpack.

> Anyone is welcome to contribute, more info [here](CONTRIBUTING.md).

## Firefox

The Firefox port of the extension was a contribution made by [@nilooy](https://github.com/nilooy). Thank you!


================================================
FILE: devapp-2.0.0/.gitignore
================================================
node_modules/


================================================
FILE: devapp-2.0.0/.meteor/.finished-upgraders
================================================
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.

notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze


================================================
FILE: devapp-2.0.0/.meteor/.gitignore
================================================
local


================================================
FILE: devapp-2.0.0/.meteor/.id
================================================
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
#   - ensuring you don't accidentally deploy one app on top of another
#   - providing package authors with aggregated statistics

x58f9z1u8cbj.d63i3vx3cjzb


================================================
FILE: devapp-2.0.0/.meteor/packages
================================================
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

meteor-base@1.4.0             # Packages every Meteor app needs to have
mobile-experience@1.1.0       # Packages for a great mobile UX
mongo@1.10.1                   # The database Meteor supports right now
reactive-var@1.0.11            # Reactive variable for tracker

standard-minifier-css@1.7.2   # CSS minifier run for production mode
standard-minifier-js@2.6.0    # JS minifier run for production mode
es5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers
ecmascript@0.15.0              # Enable ECMAScript2015+ syntax in app code
typescript@4.1.2              # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0            # Server-side component of the `meteor shell` command
hot-module-replacement@0.2.0  # Update client in development without reloading the page

insecure@1.0.7                # Allow all DB writes from clients (for prototyping)
static-html             # Define static page content in .html files
react-meteor-data       # React higher-order component for reactively tracking Meteor data


================================================
FILE: devapp-2.0.0/.meteor/platforms
================================================
server
browser


================================================
FILE: devapp-2.0.0/.meteor/release
================================================
METEOR@2.0


================================================
FILE: devapp-2.0.0/.meteor/versions
================================================
allow-deny@1.1.0
autoupdate@1.7.0
babel-compiler@7.6.2
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.3.1
check@1.3.1
ddp@1.4.0
ddp-client@2.4.1
ddp-common@1.4.0
ddp-server@2.3.3
diff-sequence@1.1.1
dynamic-import@0.6.0
ecmascript@0.15.1
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.1
ecmascript-runtime-server@0.10.1
ejson@1.1.1
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
hot-module-replacement@0.2.1
html-tools@1.1.3
htmljs@1.1.1
id-map@1.1.1
insecure@1.0.7
inter-process-messaging@0.1.1
launch-screen@1.2.1
livedata@1.0.18
logging@1.2.0
meteor@1.9.3
meteor-base@1.4.0
minifier-css@1.5.4
minifier-js@2.6.1
minimongo@1.6.2
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.7
modules@0.16.0
modules-runtime@0.12.0
modules-runtime-hot@0.13.0
mongo@1.10.1
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@3.8.1
ordered-dict@1.1.0
promise@0.11.2
random@1.2.0
react-fast-refresh@0.1.1
react-meteor-data@2.5.1
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.0
shell-server@0.5.0
socket-stream-client@0.3.3
spacebars-compiler@1.3.1
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
static-html@1.3.2
templating-tools@1.2.2
tracker@1.2.0
typescript@4.1.2
underscore@1.0.10
webapp@1.10.1
webapp-hashing@1.1.0


================================================
FILE: devapp-2.0.0/client/main.css
================================================
body {
  padding: 10px;
  font-family: sans-serif;
}


================================================
FILE: devapp-2.0.0/client/main.html
================================================
<head>
  <title>devapp</title>
</head>

<body>
  <div id="react-target"></div>
</body>


================================================
FILE: devapp-2.0.0/client/main.jsx
================================================
import React from 'react'
import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import { App } from '../imports/ui/App'

import '../imports/api/links'
import '../imports/api/random'

Meteor.startup(() => {
  render(<App />, document.getElementById('react-target'))
})


================================================
FILE: devapp-2.0.0/imports/api/links.js
================================================
import { Mongo } from 'meteor/mongo'

export const LinksCollection = new Mongo.Collection('links')


================================================
FILE: devapp-2.0.0/imports/api/random.js
================================================
import { Mongo } from 'meteor/mongo'

export const RandomCollection = new Mongo.Collection('random')


================================================
FILE: devapp-2.0.0/imports/ui/App.jsx
================================================
import React, { useEffect, useRef, useState } from 'react'
import { useTracker } from 'meteor/react-meteor-data'
import { RandomCollection } from '../api/random'

export const App = () => {
  const [isSpamming, setSpamming] = useState(false)
  const spammerRef = useRef(0)

  const r1to100 = useTracker(() => {
    const handle = Meteor.subscribe('random1to100')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r101to200 = useTracker(() => {
    const handle = Meteor.subscribe('random101to200')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r201to300 = useTracker(() => {
    const handle = Meteor.subscribe('random201to300')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r301to400 = useTracker(() => {
    const handle = Meteor.subscribe('random301to400')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r401to500 = useTracker(() => {
    const handle = Meteor.subscribe('random401to500')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r501to600 = useTracker(() => {
    const handle = Meteor.subscribe('random501to600')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r601to700 = useTracker(() => {
    const handle = Meteor.subscribe('random601to700')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r701to800 = useTracker(() => {
    const handle = Meteor.subscribe('random701to800')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r801to900 = useTracker(() => {
    const handle = Meteor.subscribe('random801to900')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r901to1000 = useTracker(() => {
    const handle = Meteor.subscribe('random901to1000')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  useEffect(() => {
    if (isSpamming && !spammerRef.current) {
      spammerRef.current = setInterval(() => {
        Meteor.call('echo', 'Echo')
      }, 100)
    } else {
      if (spammerRef.current) {
        clearInterval(spammerRef.current)
        spammerRef.current = 0
      }
    }
  }, [isSpamming])

  return (
    <div>
      <h1>Welcome to Meteor!</h1>

      <button
        onClick={() => {
          setSpamming(!isSpamming)
        }}
      >
        {isSpamming ? 'Spam [On]' : 'Spam [Off]'}
      </button>

      <button
        onClick={() => {
          Meteor.call('echo', 'Echo')
        }}
      >
        String
      </button>

      <button
        onClick={() => {
          Meteor.call('echo', {
            echo: 'Parley gun log poop deck salmagundi gibbet prow chandler gaff boatswain. Loaded to the gunwalls Jack Ketch parrel sheet smartly gabion coffer Admiral of the Black interloper carouser. Rutters booty barque galleon pink gun Barbary Coast run a shot across the bow list marooned.',
          })
        }}
      >
        Object
      </button>
    </div>
  )
}


================================================
FILE: devapp-2.0.0/imports/ui/Hello.jsx
================================================
import React, { useState } from 'react'

export const Hello = () => {
  const [counter, setCounter] = useState(0)

  const increment = () => {
    setCounter(counter + 1)
  }

  return (
    <div>
      <button onClick={increment}>Click Me</button>
      <p>You've pressed the button {counter} times.</p>
    </div>
  )
}


================================================
FILE: devapp-2.0.0/imports/ui/Info.jsx
================================================
import React from 'react'
import { useTracker } from 'meteor/react-meteor-data'
import { LinksCollection } from '../api/links'

export const Info = () => {
  const links = useTracker(() => {
    return LinksCollection.find().fetch()
  })

  return (
    <div>
      <h2>Learn Meteor!</h2>
      <ul>
        {links.map(link => (
          <li key={link._id}>
            <a href={link.url} target='_blank' rel='noreferrer'>
              {link.title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  )
}


================================================
FILE: devapp-2.0.0/package.json
================================================
{
  "name": "devapp-2.0.0",
  "private": true,
  "scripts": {
    "start": "meteor run",
    "test": "meteor test --once --driver-package meteortesting:mocha",
    "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
    "visualize": "meteor --production --extra-packages bundle-visualizer"
  },
  "dependencies": {
    "@babel/runtime": "^7.11.2",
    "meteor-node-stubs": "^1.0.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.jsx",
      "server": "server/main.js"
    },
    "testModule": "tests/main.js"
  }
}


================================================
FILE: devapp-2.0.0/server/main.js
================================================
import { Meteor } from 'meteor/meteor'
import { LinksCollection } from '../imports/api/links'
import { RandomCollection } from '../imports/api/random'

function insertLink(title, url) {
  LinksCollection.insert({ title, url, createdAt: new Date() })
}

Meteor.methods({
  echo(echo) {
    return echo
  },
})

Meteor.startup(() => {
  if (LinksCollection.find().count() === 0) {
    insertLink(
      'Do the Tutorial',
      'https://www.meteor.com/tutorials/react/creating-an-app',
    )

    insertLink('Follow the Guide', 'http://guide.meteor.com')

    insertLink('Read the Docs', 'https://docs.meteor.com')

    insertLink('Discussions', 'https://forums.meteor.com')
  }

  RandomCollection.remove({})

  let counter = 1

  new Array(1000)
    .fill(null)
    .map(() => ({
      name: 'Lorem Ipsum '.concat(String(counter)),
      number: counter++,
    }))
    .forEach(item => {
      RandomCollection.insert(item)
    })
})

Meteor.publish('random1to100', function () {
  return RandomCollection.find({
    number: { $gte: 1, $lte: 100 },
  })
})

Meteor.publish('random101to200', function () {
  return RandomCollection.find({
    number: { $gte: 101, $lte: 200 },
  })
})

Meteor.publish('random201to300', function () {
  return RandomCollection.find({
    number: { $gte: 201, $lte: 300 },
  })
})

Meteor.publish('random301to400', function () {
  return RandomCollection.find({
    number: { $gte: 301, $lte: 400 },
  })
})

Meteor.publish('random401to500', function () {
  return RandomCollection.find({
    number: { $gte: 401, $lte: 500 },
  })
})

Meteor.publish('random501to600', function () {
  return RandomCollection.find({
    number: { $gte: 501, $lte: 600 },
  })
})

Meteor.publish('random601to700', function () {
  return RandomCollection.find({
    number: { $gte: 601, $lte: 700 },
  })
})

Meteor.publish('random701to800', function () {
  return RandomCollection.find({
    number: { $gte: 701, $lte: 800 },
  })
})

Meteor.publish('random801to900', function () {
  return RandomCollection.find({
    number: { $gte: 801, $lte: 900 },
  })
})

Meteor.publish('random901to1000', function () {
  return RandomCollection.find({
    number: { $gte: 901, $lte: 1000 },
  })
})


================================================
FILE: devapp-2.0.0/tests/main.js
================================================
import assert from 'assert'

describe('devapp-2.0.0', function () {
  it('package.json has correct name', async function () {
    const { name } = await import('../package.json')
    assert.strictEqual(name, 'devapp-2.0.0')
  })

  if (Meteor.isClient) {
    it('client is not server', function () {
      assert.strictEqual(Meteor.isServer, false)
    })
  }

  if (Meteor.isServer) {
    it('server is not client', function () {
      assert.strictEqual(Meteor.isClient, false)
    })
  }
})


================================================
FILE: devapp-2.2.0/.gitignore
================================================
node_modules/


================================================
FILE: devapp-2.2.0/.meteor/.finished-upgraders
================================================
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.

notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze


================================================
FILE: devapp-2.2.0/.meteor/.gitignore
================================================
local


================================================
FILE: devapp-2.2.0/.meteor/.id
================================================
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
#   - ensuring you don't accidentally deploy one app on top of another
#   - providing package authors with aggregated statistics

kckjbl9hqpog.ffkb1f09s7ns


================================================
FILE: devapp-2.2.0/.meteor/packages
================================================
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

meteor-base@1.4.0             # Packages every Meteor app needs to have
mobile-experience@1.1.0       # Packages for a great mobile UX
mongo@1.11.0                   # The database Meteor supports right now
reactive-var@1.0.11            # Reactive variable for tracker

standard-minifier-css@1.7.2   # CSS minifier run for production mode
standard-minifier-js@2.6.0    # JS minifier run for production mode
es5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers
ecmascript@0.15.1              # Enable ECMAScript2015+ syntax in app code
typescript@4.2.2              # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0            # Server-side component of the `meteor shell` command
hot-module-replacement@0.2.0  # Update client in development without reloading the page

insecure@1.0.7                # Allow all DB writes from clients (for prototyping)
static-html             # Define static page content in .html files
react-meteor-data       # React higher-order component for reactively tracking Meteor data


================================================
FILE: devapp-2.2.0/.meteor/platforms
================================================
server
browser


================================================
FILE: devapp-2.2.0/.meteor/release
================================================
METEOR@2.2


================================================
FILE: devapp-2.2.0/.meteor/versions
================================================
allow-deny@1.1.0
autoupdate@1.7.0
babel-compiler@7.6.2
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.3.1
check@1.3.1
ddp@1.4.0
ddp-client@2.4.1
ddp-common@1.4.0
ddp-server@2.3.3
diff-sequence@1.1.1
dynamic-import@0.6.0
ecmascript@0.15.1
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.1
ecmascript-runtime-server@0.10.1
ejson@1.1.1
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
hot-module-replacement@0.2.1
html-tools@1.1.3
htmljs@1.1.1
id-map@1.1.1
insecure@1.0.7
inter-process-messaging@0.1.1
launch-screen@1.2.1
livedata@1.0.18
logging@1.2.0
meteor@1.9.3
meteor-base@1.4.0
minifier-css@1.5.4
minifier-js@2.6.1
minimongo@1.6.2
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.7
modules@0.16.0
modules-runtime@0.12.0
modules-runtime-hot@0.13.0
mongo@1.11.1
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@3.9.1
ordered-dict@1.1.0
promise@0.11.2
random@1.2.0
react-fast-refresh@0.1.1
react-meteor-data@2.5.1
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.0
shell-server@0.5.0
socket-stream-client@0.3.3
spacebars-compiler@1.3.1
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
static-html@1.3.2
templating-tools@1.2.2
tracker@1.2.0
typescript@4.2.2
underscore@1.0.10
webapp@1.10.1
webapp-hashing@1.1.0


================================================
FILE: devapp-2.2.0/client/main.css
================================================
body {
  padding: 10px;
  font-family: sans-serif;
}


================================================
FILE: devapp-2.2.0/client/main.html
================================================
<head>
  <title>devapp</title>
</head>

<body>
  <div id="react-target"></div>
</body>


================================================
FILE: devapp-2.2.0/client/main.jsx
================================================
import React from 'react'
import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import { App } from '../imports/ui/App'

import '../imports/api/links'
import '../imports/api/random'

Meteor.startup(() => {
  render(<App />, document.getElementById('react-target'))
})


================================================
FILE: devapp-2.2.0/imports/api/links.js
================================================
import { Mongo } from 'meteor/mongo'

export const LinksCollection = new Mongo.Collection('links')


================================================
FILE: devapp-2.2.0/imports/api/random.js
================================================
import { Mongo } from 'meteor/mongo'

export const RandomCollection = new Mongo.Collection('random')


================================================
FILE: devapp-2.2.0/imports/ui/App.jsx
================================================
import React, { useEffect, useRef, useState } from 'react'
import { useTracker } from 'meteor/react-meteor-data'
import { RandomCollection } from '../api/random'

export const App = () => {
  const [isSpamming, setSpamming] = useState(false)
  const spammerRef = useRef(0)

  const r1to100 = useTracker(() => {
    const handle = Meteor.subscribe('random1to100')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r101to200 = useTracker(() => {
    const handle = Meteor.subscribe('random101to200')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r201to300 = useTracker(() => {
    const handle = Meteor.subscribe('random201to300')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r301to400 = useTracker(() => {
    const handle = Meteor.subscribe('random301to400')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r401to500 = useTracker(() => {
    const handle = Meteor.subscribe('random401to500')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r501to600 = useTracker(() => {
    const handle = Meteor.subscribe('random501to600')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r601to700 = useTracker(() => {
    const handle = Meteor.subscribe('random601to700')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r701to800 = useTracker(() => {
    const handle = Meteor.subscribe('random701to800')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r801to900 = useTracker(() => {
    const handle = Meteor.subscribe('random801to900')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r901to1000 = useTracker(() => {
    const handle = Meteor.subscribe('random901to1000')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  useEffect(() => {
    if (isSpamming && !spammerRef.current) {
      spammerRef.current = setInterval(() => {
        Meteor.call('echo', 'Echo')
      }, 100)
    } else {
      if (spammerRef.current) {
        clearInterval(spammerRef.current)
        spammerRef.current = 0
      }
    }
  }, [isSpamming])

  return (
    <div>
      <h1>Welcome to Meteor!</h1>

      <button
        onClick={() => {
          setSpamming(!isSpamming)
        }}
      >
        {isSpamming ? 'Spam [On]' : 'Spam [Off]'}
      </button>

      <button
        onClick={() => {
          Meteor.call('echo', 'Echo')
        }}
      >
        String
      </button>

      <button
        onClick={() => {
          Meteor.call('echo', {
            echo: 'Parley gun log poop deck salmagundi gibbet prow chandler gaff boatswain. Loaded to the gunwalls Jack Ketch parrel sheet smartly gabion coffer Admiral of the Black interloper carouser. Rutters booty barque galleon pink gun Barbary Coast run a shot across the bow list marooned.',
          })
        }}
      >
        Object
      </button>
    </div>
  )
}


================================================
FILE: devapp-2.2.0/imports/ui/Hello.jsx
================================================
import React, { useState } from 'react'

export const Hello = () => {
  const [counter, setCounter] = useState(0)

  const increment = () => {
    setCounter(counter + 1)
  }

  return (
    <div>
      <button onClick={increment}>Click Me</button>
      <p>You've pressed the button {counter} times.</p>
    </div>
  )
}


================================================
FILE: devapp-2.2.0/imports/ui/Info.jsx
================================================
import React from 'react'
import { useTracker } from 'meteor/react-meteor-data'
import { LinksCollection } from '../api/links'

export const Info = () => {
  const links = useTracker(() => {
    return LinksCollection.find().fetch()
  })

  return (
    <div>
      <h2>Learn Meteor!</h2>
      <ul>
        {links.map(link => (
          <li key={link._id}>
            <a href={link.url} target='_blank'>
              {link.title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  )
}


================================================
FILE: devapp-2.2.0/package.json
================================================
{
  "name": "devapp-2.2.0",
  "private": true,
  "scripts": {
    "start": "meteor run",
    "test": "meteor test --once --driver-package meteortesting:mocha",
    "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
    "visualize": "meteor --production --extra-packages bundle-visualizer"
  },
  "dependencies": {
    "@babel/runtime": "^7.11.2",
    "meteor-node-stubs": "^1.0.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.jsx",
      "server": "server/main.js"
    },
    "testModule": "tests/main.js"
  }
}


================================================
FILE: devapp-2.2.0/server/main.js
================================================
import { Meteor } from 'meteor/meteor'
import { LinksCollection } from '../imports/api/links'
import { RandomCollection } from '../imports/api/random'

function insertLink(title, url) {
  LinksCollection.insert({ title, url, createdAt: new Date() })
}

Meteor.methods({
  echo(echo) {
    return echo
  },
})

Meteor.startup(() => {
  if (LinksCollection.find().count() === 0) {
    insertLink(
      'Do the Tutorial',
      'https://www.meteor.com/tutorials/react/creating-an-app',
    )

    insertLink('Follow the Guide', 'http://guide.meteor.com')

    insertLink('Read the Docs', 'https://docs.meteor.com')

    insertLink('Discussions', 'https://forums.meteor.com')
  }

  RandomCollection.remove({})

  let counter = 1

  new Array(1000)
    .fill(null)
    .map(() => ({
      name: 'Lorem Ipsum '.concat(String(counter)),
      number: counter++,
    }))
    .forEach(item => {
      RandomCollection.insert(item)
    })
})

Meteor.publish('random1to100', function () {
  return RandomCollection.find({
    number: { $gte: 1, $lte: 100 },
  })
})

Meteor.publish('random101to200', function () {
  return RandomCollection.find({
    number: { $gte: 101, $lte: 200 },
  })
})

Meteor.publish('random201to300', function () {
  return RandomCollection.find({
    number: { $gte: 201, $lte: 300 },
  })
})

Meteor.publish('random301to400', function () {
  return RandomCollection.find({
    number: { $gte: 301, $lte: 400 },
  })
})

Meteor.publish('random401to500', function () {
  return RandomCollection.find({
    number: { $gte: 401, $lte: 500 },
  })
})

Meteor.publish('random501to600', function () {
  return RandomCollection.find({
    number: { $gte: 501, $lte: 600 },
  })
})

Meteor.publish('random601to700', function () {
  return RandomCollection.find({
    number: { $gte: 601, $lte: 700 },
  })
})

Meteor.publish('random701to800', function () {
  return RandomCollection.find({
    number: { $gte: 701, $lte: 800 },
  })
})

Meteor.publish('random801to900', function () {
  return RandomCollection.find({
    number: { $gte: 801, $lte: 900 },
  })
})

Meteor.publish('random901to1000', function () {
  return RandomCollection.find({
    number: { $gte: 901, $lte: 1000 },
  })
})


================================================
FILE: devapp-2.2.0/tests/main.js
================================================
import assert from 'assert'

describe('devapp-2.2.0', function () {
  it('package.json has correct name', async function () {
    const { name } = await import('../package.json')
    assert.strictEqual(name, 'devapp-2.2.0')
  })

  if (Meteor.isClient) {
    it('client is not server', function () {
      assert.strictEqual(Meteor.isServer, false)
    })
  }

  if (Meteor.isServer) {
    it('server is not client', function () {
      assert.strictEqual(Meteor.isClient, false)
    })
  }
})


================================================
FILE: devapp-2.2.4/.gitignore
================================================
node_modules/


================================================
FILE: devapp-2.2.4/.meteor/.finished-upgraders
================================================
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.

notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze


================================================
FILE: devapp-2.2.4/.meteor/.gitignore
================================================
local


================================================
FILE: devapp-2.2.4/.meteor/.id
================================================
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
#   - ensuring you don't accidentally deploy one app on top of another
#   - providing package authors with aggregated statistics

azmndnm89g3.mkgtn1ux8hf9


================================================
FILE: devapp-2.2.4/.meteor/packages
================================================
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

meteor-base@1.4.0             # Packages every Meteor app needs to have
mobile-experience@1.1.0       # Packages for a great mobile UX
mongo@1.11.0                   # The database Meteor supports right now
reactive-var@1.0.11            # Reactive variable for tracker

standard-minifier-css@1.7.2   # CSS minifier run for production mode
standard-minifier-js@2.6.0    # JS minifier run for production mode
es5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers
ecmascript@0.15.3              # Enable ECMAScript2015+ syntax in app code
typescript@4.3.5              # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0            # Server-side component of the `meteor shell` command
hot-module-replacement@0.2.0  # Update client in development without reloading the page

insecure@1.0.7                # Allow all DB writes from clients (for prototyping)
static-html             # Define static page content in .html files
react-meteor-data       # React higher-order component for reactively tracking Meteor data


================================================
FILE: devapp-2.2.4/.meteor/platforms
================================================
server
browser


================================================
FILE: devapp-2.2.4/.meteor/release
================================================
METEOR@2.2.4


================================================
FILE: devapp-2.2.4/.meteor/versions
================================================
allow-deny@1.1.0
autoupdate@1.7.0
babel-compiler@7.7.0
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.3.1
check@1.3.1
ddp@1.4.0
ddp-client@2.4.1
ddp-common@1.4.0
ddp-server@2.3.3
diff-sequence@1.1.1
dynamic-import@0.6.0
ecmascript@0.15.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.1
ecmascript-runtime-server@0.10.1
ejson@1.1.1
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
hot-module-replacement@0.2.1
html-tools@1.1.3
htmljs@1.1.1
id-map@1.1.1
insecure@1.0.7
inter-process-messaging@0.1.1
launch-screen@1.2.1
livedata@1.0.18
logging@1.2.0
meteor@1.9.3
meteor-base@1.4.0
minifier-css@1.5.4
minifier-js@2.6.1
minimongo@1.6.2
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.7
modules@0.16.0
modules-runtime@0.12.0
modules-runtime-hot@0.13.0
mongo@1.11.1
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@3.9.1
ordered-dict@1.1.0
promise@0.11.2
random@1.2.0
react-fast-refresh@0.1.1
react-meteor-data@2.5.1
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.0
shell-server@0.5.0
socket-stream-client@0.3.3
spacebars-compiler@1.3.1
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
static-html@1.3.2
templating-tools@1.2.2
tracker@1.2.0
typescript@4.3.5
underscore@1.0.10
webapp@1.10.1
webapp-hashing@1.1.0


================================================
FILE: devapp-2.2.4/client/main.css
================================================
body {
  padding: 10px;
  font-family: sans-serif;
}


================================================
FILE: devapp-2.2.4/client/main.html
================================================
<head>
  <title>devapp</title>
</head>

<body>
  <div id="react-target"></div>
</body>


================================================
FILE: devapp-2.2.4/client/main.jsx
================================================
import React from 'react'
import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import { App } from '../imports/ui/App'

import '../imports/api/links'
import '../imports/api/random'

Meteor.startup(() => {
  render(<App />, document.getElementById('react-target'))
})


================================================
FILE: devapp-2.2.4/imports/api/links.js
================================================
import { Mongo } from 'meteor/mongo'

export const LinksCollection = new Mongo.Collection('links')


================================================
FILE: devapp-2.2.4/imports/api/random.js
================================================
import { Mongo } from 'meteor/mongo'

export const RandomCollection = new Mongo.Collection('random')


================================================
FILE: devapp-2.2.4/imports/ui/App.jsx
================================================
import React, { useEffect, useRef, useState } from 'react'
import { useTracker } from 'meteor/react-meteor-data'
import { RandomCollection } from '../api/random'

export const App = () => {
  const [isSpamming, setSpamming] = useState(false)
  const spammerRef = useRef(0)

  const r1to100 = useTracker(() => {
    const handle = Meteor.subscribe('random1to100')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r101to200 = useTracker(() => {
    const handle = Meteor.subscribe('random101to200')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r201to300 = useTracker(() => {
    const handle = Meteor.subscribe('random201to300')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r301to400 = useTracker(() => {
    const handle = Meteor.subscribe('random301to400')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r401to500 = useTracker(() => {
    const handle = Meteor.subscribe('random401to500')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r501to600 = useTracker(() => {
    const handle = Meteor.subscribe('random501to600')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r601to700 = useTracker(() => {
    const handle = Meteor.subscribe('random601to700')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r701to800 = useTracker(() => {
    const handle = Meteor.subscribe('random701to800')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r801to900 = useTracker(() => {
    const handle = Meteor.subscribe('random801to900')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  const r901to1000 = useTracker(() => {
    const handle = Meteor.subscribe('random901to1000')
    return {
      isLoading: !handle.ready(),
      docs: RandomCollection.find({}).fetch(),
    }
  }, [])

  useEffect(() => {
    if (isSpamming && !spammerRef.current) {
      spammerRef.current = setInterval(() => {
        Meteor.call('echo', 'Echo')
      }, 100)
    } else {
      if (spammerRef.current) {
        clearInterval(spammerRef.current)
        spammerRef.current = 0
      }
    }
  }, [isSpamming])

  return (
    <div>
      <h1>Welcome to Meteor!</h1>

      <button
        onClick={() => {
          setSpamming(!isSpamming)
        }}
      >
        {isSpamming ? 'Spam [On]' : 'Spam [Off]'}
      </button>

      <button
        onClick={() => {
          Meteor.call('echo', 'Echo')
        }}
      >
        String
      </button>

      <button
        onClick={() => {
          Meteor.call('echo', {
            echo: 'Parley gun log poop deck salmagundi gibbet prow chandler gaff boatswain. Loaded to the gunwalls Jack Ketch parrel sheet smartly gabion coffer Admiral of the Black interloper carouser. Rutters booty barque galleon pink gun Barbary Coast run a shot across the bow list marooned.',
          })
        }}
      >
        Object
      </button>
    </div>
  )
}


================================================
FILE: devapp-2.2.4/imports/ui/Hello.jsx
================================================
import React, { useState } from 'react'

export const Hello = () => {
  const [counter, setCounter] = useState(0)

  const increment = () => {
    setCounter(counter + 1)
  }

  return (
    <div>
      <button onClick={increment}>Click Me</button>
      <p>You've pressed the button {counter} times.</p>
    </div>
  )
}


================================================
FILE: devapp-2.2.4/imports/ui/Info.jsx
================================================
import React from 'react'
import { useTracker } from 'meteor/react-meteor-data'
import { LinksCollection } from '../api/links'

export const Info = () => {
  const links = useTracker(() => {
    return LinksCollection.find().fetch()
  })

  return (
    <div>
      <h2>Learn Meteor!</h2>
      <ul>
        {links.map(link => (
          <li key={link._id}>
            <a href={link.url} target='_blank' rel='noreferrer'>
              {link.title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  )
}


================================================
FILE: devapp-2.2.4/package.json
================================================
{
  "name": "devapp-2.2.4",
  "private": true,
  "scripts": {
    "start": "meteor run",
    "test": "meteor test --once --driver-package meteortesting:mocha",
    "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
    "visualize": "meteor --production --extra-packages bundle-visualizer"
  },
  "dependencies": {
    "@babel/runtime": "^7.11.2",
    "meteor-node-stubs": "^1.0.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.jsx",
      "server": "server/main.js"
    },
    "testModule": "tests/main.js"
  }
}


================================================
FILE: devapp-2.2.4/server/main.js
================================================
import { Meteor } from 'meteor/meteor'
import { LinksCollection } from '../imports/api/links'
import { RandomCollection } from '../imports/api/random'

function insertLink(title, url) {
  LinksCollection.insert({ title, url, createdAt: new Date() })
}

Meteor.methods({
  echo(echo) {
    return echo
  },
})

Meteor.startup(() => {
  if (LinksCollection.find().count() === 0) {
    insertLink(
      'Do the Tutorial',
      'https://www.meteor.com/tutorials/react/creating-an-app',
    )

    insertLink('Follow the Guide', 'http://guide.meteor.com')

    insertLink('Read the Docs', 'https://docs.meteor.com')

    insertLink('Discussions', 'https://forums.meteor.com')
  }

  RandomCollection.remove({})

  let counter = 1

  new Array(1000)
    .fill(null)
    .map(() => ({
      name: 'Lorem Ipsum '.concat(String(counter)),
      number: counter++,
    }))
    .forEach(item => {
      RandomCollection.insert(item)
    })
})

Meteor.publish('random1to100', function () {
  return RandomCollection.find({
    number: { $gte: 1, $lte: 100 },
  })
})

Meteor.publish('random101to200', function () {
  return RandomCollection.find({
    number: { $gte: 101, $lte: 200 },
  })
})

Meteor.publish('random201to300', function () {
  return RandomCollection.find({
    number: { $gte: 201, $lte: 300 },
  })
})

Meteor.publish('random301to400', function () {
  return RandomCollection.find({
    number: { $gte: 301, $lte: 400 },
  })
})

Meteor.publish('random401to500', function () {
  return RandomCollection.find({
    number: { $gte: 401, $lte: 500 },
  })
})

Meteor.publish('random501to600', function () {
  return RandomCollection.find({
    number: { $gte: 501, $lte: 600 },
  })
})

Meteor.publish('random601to700', function () {
  return RandomCollection.find({
    number: { $gte: 601, $lte: 700 },
  })
})

Meteor.publish('random701to800', function () {
  return RandomCollection.find({
    number: { $gte: 701, $lte: 800 },
  })
})

Meteor.publish('random801to900', function () {
  return RandomCollection.find({
    number: { $gte: 801, $lte: 900 },
  })
})

Meteor.publish('random901to1000', function () {
  return RandomCollection.find({
    number: { $gte: 901, $lte: 1000 },
  })
})


================================================
FILE: devapp-2.2.4/tests/main.js
================================================
import assert from 'assert'

describe('devapp-2.2.4', function () {
  it('package.json has correct name', async function () {
    const { name } = await import('../package.json')
    assert.strictEqual(name, 'devapp-2.2.4')
  })

  if (Meteor.isClient) {
    it('client is not server', function () {
      assert.strictEqual(Meteor.isServer, false)
    })
  }

  if (Meteor.isServer) {
    it('server is not client', function () {
      assert.strictEqual(Meteor.isClient, false)
    })
  }
})


================================================
FILE: devapp-3.4/.gitignore
================================================
node_modules/

# Meteor Modern-Tools build context directories
_build
*/build-assets
*/build-chunks
.rsdoctor


================================================
FILE: devapp-3.4/.meteor/.finished-upgraders
================================================
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.

notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze


================================================
FILE: devapp-3.4/.meteor/.gitignore
================================================
local


================================================
FILE: devapp-3.4/.meteor/.id
================================================
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
#   - ensuring you don't accidentally deploy one app on top of another
#   - providing package authors with aggregated statistics

hquoz8fwpx2o.w95c0f55ay3


================================================
FILE: devapp-3.4/.meteor/packages
================================================
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

meteor-base@1.5.2             # Packages every Meteor app needs to have
mobile-experience@1.1.2       # Packages for a great mobile UX
mongo@2.2.0                   # The database Meteor supports right now
reactive-var@1.0.13            # Reactive variable for tracker

standard-minifier-css@1.10.0   # CSS minifier run for production mode
standard-minifier-js@3.2.0    # JS minifier run for production mode
es5-shim@4.8.1                # ECMAScript 5 compatibility for older browsers
ecmascript@0.17.0              # Enable ECMAScript2015+ syntax in app code
typescript@5.9.3              # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.7.0            # Server-side component of the `meteor shell` command
hot-module-replacement@0.5.4  # Update client in development without reloading the page


static-html@1.5.0             # Define static page content in .html files
react-meteor-data       # React higher-order component for reactively tracking Meteor data

rspack                  # Integrate Rspack into Meteor for client and server app bundling


================================================
FILE: devapp-3.4/.meteor/platforms
================================================
server
browser


================================================
FILE: devapp-3.4/.meteor/release
================================================
METEOR@3.4


================================================
FILE: devapp-3.4/.meteor/versions
================================================
allow-deny@2.1.0
autoupdate@2.0.1
babel-compiler@7.13.0
babel-runtime@1.5.2
base64@1.0.13
binary-heap@1.0.12
boilerplate-generator@2.1.0
caching-compiler@2.0.1
callback-hook@1.6.1
check@1.5.0
core-runtime@1.0.0
ddp@1.4.2
ddp-client@3.1.1
ddp-common@1.4.4
ddp-server@3.1.2
diff-sequence@1.1.3
dynamic-import@0.7.4
ecmascript@0.17.0
ecmascript-runtime@0.8.3
ecmascript-runtime-client@0.12.3
ecmascript-runtime-server@0.11.1
ejson@1.1.5
es5-shim@4.8.1
facts-base@1.0.2
fetch@0.1.6
geojson-utils@1.0.12
hot-code-push@1.0.5
hot-module-replacement@0.5.4
id-map@1.2.0
inter-process-messaging@0.1.2
launch-screen@2.0.1
logging@1.3.6
meteor@2.2.0
meteor-base@1.5.2
minifier-css@2.0.1
minifier-js@3.1.0
minimongo@2.0.5
mobile-experience@1.1.2
mobile-status-bar@1.1.1
modern-browsers@0.2.3
modules@0.20.3
modules-runtime@0.13.2
modules-runtime-hot@0.14.3
mongo@2.2.0
mongo-decimal@0.2.0
mongo-dev-server@1.1.1
mongo-id@1.0.9
npm-mongo@6.16.1
ordered-dict@1.2.0
promise@1.0.0
random@1.2.2
react-fast-refresh@0.3.0
react-meteor-data@4.0.1
reactive-var@1.0.13
reload@1.3.2
retry@1.1.1
routepolicy@1.1.2
rspack@1.0.0
shell-server@0.7.0
socket-stream-client@0.6.1
standard-minifier-css@1.10.0
standard-minifier-js@3.2.0
static-html@1.5.0
static-html-tools@1.0.0
tools-core@1.0.0
tracker@1.3.4
typescript@5.9.3
webapp@2.1.0
webapp-hashing@1.1.2
zodern:types@1.0.13


================================================
FILE: devapp-3.4/.swcrc
================================================
{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    }
  }
}


================================================
FILE: devapp-3.4/client/main.css
================================================
body {
  padding: 10px;
  font-family: sans-serif;
}


================================================
FILE: devapp-3.4/client/main.html
================================================
<head>
  <title>devapp-3.4</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link
    href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
    rel="stylesheet"
  />
</head>

<body>
  <div id="react-target"></div>
</body>


================================================
FILE: devapp-3.4/client/main.jsx
================================================
import { createRoot } from 'react-dom/client'
import { Meteor } from 'meteor/meteor'
import { App } from '/imports/ui/App'
import '/imports/ui/styles.css'

Meteor.startup(() => {
  const container = document.getElementById('react-target')
  const root = createRoot(container)
  root.render(<App />)
})


================================================
FILE: devapp-3.4/imports/api/links.js
================================================
import { Mongo } from 'meteor/mongo'

export const LinksCollection = new Mongo.Collection('links')


================================================
FILE: devapp-3.4/imports/ui/App.jsx
================================================
import { Counter } from './Counter.jsx'
import { Header } from './Header.jsx'
import { Info } from './Info.jsx'

export const App = () => (
  <div className='page'>
    <Header />
    <main className='main'>
      <Counter />
      <Info />
    </main>
  </div>
)


================================================
FILE: devapp-3.4/imports/ui/Counter.jsx
================================================
import { useState } from 'react'

export const Counter = () => {
  const [counter, setCounter] = useState(0)

  const increment = () => {
    setCounter(counter + 1)
  }

  return (
    <div className='counter-card card'>
      <div className='counter-content'>
        <button className='button' onClick={increment}>
          Click Me
        </button>
        <p className='counter-text'>
          You've pressed the button{' '}
          <span className='counter-value'>{counter}</span>{' '}
          {counter === 1 ? 'time' : 'times'}.
        </p>
      </div>
    </div>
  )
}


================================================
FILE: devapp-3.4/imports/ui/Header.jsx
================================================
import MeteorLogo from './meteor-logo.svg'

export const Header = () => {
  return (
    <div className='header'>
      <nav className='nav container'>
        <div className='logo-container'>
          <MeteorLogo className='logo' />
        </div>
        <h1 className='page-title'>Welcome to Meteor!</h1>
      </nav>
    </div>
  )
}


================================================
FILE: devapp-3.4/imports/ui/Info.jsx
================================================
import { useFind, useSubscribe } from 'meteor/react-meteor-data'
import { LinksCollection } from '../api/links'

export const Info = () => {
  const isLoading = useSubscribe('links')
  const links = useFind(() => LinksCollection.find())

  if (isLoading()) {
    return <div>Loading...</div>
  }

  return (
    <section>
      <h2 className='section-title'>Learn Meteor!</h2>
      <ul className='resources-grid'>
        {links.map(link => (
          <li className='section' key={link._id}>
            <a href={link.url} className='resource-link' target='_blank'>
              <div className='resource-card card'>
                <div className='resource-content'>
                  <span className='resource-title'>{link.title}</span>
                </div>
              </div>
            </a>
          </li>
        ))}
      </ul>
    </section>
  )
}


================================================
FILE: devapp-3.4/imports/ui/styles.css
================================================
/* this file is imported in client/main.jsx */

:root {
  /* Colors */
  --color-background: hsl(210, 20%, 98%);
  --color-foreground: hsl(220, 20%, 15%);
  --color-card: hsl(0, 0%, 100%);
  --color-primary: hsl(4, 70%, 55%);
  --color-primary-hover: hsl(4, 70%, 45%);
  --color-muted: hsl(220, 10%, 50%);
  --color-border: hsl(220, 14%, 90%);

  /* Shadows */
  --shadow-card:
    0 1px 3px 0 hsl(220 20% 15% / 0.04), 0 1px 2px -1px hsl(220 20% 15% / 0.04);
  --shadow-card-hover:
    0 10px 15px -3px hsl(220 20% 15% / 0.08),
    0 4px 6px -4px hsl(220 20% 15% / 0.04);

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;

  /* Border radius */
  --radius: 0.75rem;
  --radius-sm: 0.5rem;

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-normal: 200ms ease;
  --transition-slow: 250ms ease;
}

/* ============ Reset & Base Styles ============ */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family:
    'Inter',
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    sans-serif;
  background-color: var(--color-background);
  color: var(--color-foreground);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  text-decoration: none;
  color: inherit;
}

/* ============ Layout ============ */
.page {
  min-height: 100vh;
  background-color: var(--color-background);
}

.container {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding-left: var(--spacing-md);
  padding-right: var(--spacing-md);
}

@media (min-width: 768px) {
  .container {
    padding-left: var(--spacing-lg);
    padding-right: var(--spacing-lg);
  }
}

/* ============ Header / Navigation ============ */
.header {
  border-bottom: 1px solid var(--color-border);
  background-color: var(--color-card);
  border-radius: var(--radius);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 4rem;
}

.logo-container {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.logo {
  width: 4rem;
  height: 4rem;
}

.logo-text {
  font-weight: 600;
  color: var(--color-foreground);
  display: none;
}

@media (min-width: 640px) {
  .logo-text {
    display: inline;
  }
}

.page-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--color-foreground);
  letter-spacing: -0.025em;
}

@media (min-width: 768px) {
  .page-title {
    font-size: 1.5rem;
  }
}

.nav-link {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-primary);
  transition: opacity var(--transition-fast);
}

.nav-link:hover {
  opacity: 0.8;
}

/* ============ Main Content ============ */
.main {
  padding-top: var(--spacing-xl);
  padding-bottom: var(--spacing-xl);
}

@media (min-width: 768px) {
  .main {
    padding-top: var(--spacing-2xl);
    padding-bottom: var(--spacing-2xl);
  }
}

/* ============ Card Component ============ */
.card {
  background-color: var(--color-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  border: 1px solid var(--color-border);
}

/* ============ Counter Section ============ */
.counter-card {
  padding: var(--spacing-lg);
  margin-bottom: 2.5rem;
}

@media (min-width: 768px) {
  .counter-card {
    padding: var(--spacing-xl);
    margin-bottom: var(--spacing-2xl);
  }
}

.counter-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-md);
}

@media (min-width: 640px) {
  .counter-content {
    flex-direction: row;
  }
}

.counter-text {
  color: var(--color-muted);
  text-align: center;
}

@media (min-width: 640px) {
  .counter-text {
    text-align: left;
  }
}

.counter-value {
  font-weight: 600;
  color: var(--color-foreground);
}

/* ============ Button Component ============ */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  padding: 0.625rem 1.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  font-family: inherit;
  color: white;
  background-color: var(--color-primary);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition:
    background-color var(--transition-normal),
    transform var(--transition-fast);
}

.button:hover {
  background-color: var(--color-primary-hover);
}

.button:active {
  transform: scale(0.98);
}

.button:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* ============ Resources Section ============ */
.section-title {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--color-foreground);
  margin-bottom: var(--spacing-lg);
  letter-spacing: -0.025em;
}

.resources-grid {
  list-style-type: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--spacing-md);
}

@media (min-width: 640px) {
  .resources-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ============ Resource Card ============ */
.resource-link {
  display: block;
}

.resource-card {
  padding: 1.25rem;
  transition:
    box-shadow var(--transition-slow),
    transform var(--transition-slow);
}

.resource-card:hover {
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
}

.resource-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.resource-title {
  font-weight: 500;
  color: var(--color-foreground);
  transition: color var(--transition-fast);
}

.resource-link:hover .resource-title {
  color: var(--color-primary);
}

.resource-icon {
  width: 1rem;
  height: 1rem;
  color: var(--color-muted);
  opacity: 0;
  transition:
    opacity var(--transition-fast),
    color var(--transition-fast);
}

.resource-link:hover .resource-icon {
  opacity: 1;
  color: var(--color-primary);
}


================================================
FILE: devapp-3.4/package.json
================================================
{
  "name": "devapp-3.4",
  "private": true,
  "scripts": {
    "start": "meteor run",
    "test": "meteor test --once --driver-package meteortesting:mocha",
    "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
    "visualize": "meteor --production --extra-packages bundle-visualizer"
  },
  "dependencies": {
    "@babel/runtime": "^7.23.5",
    "@swc/helpers": "^0.5.17",
    "meteor-node-stubs": "^1.2.12",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@meteorjs/rspack": "^1.0.0",
    "@rsdoctor/rspack-plugin": "^1.2.3",
    "@rspack/cli": "^1.7.1",
    "@rspack/core": "^1.7.1",
    "@rspack/plugin-react-refresh": "^1.4.3",
    "@svgr/webpack": "^8.1.0",
    "react-refresh": "^0.17.0"
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.jsx",
      "server": "server/main.js"
    },
    "testModule": "tests/main.js",
    "modern": true
  }
}


================================================
FILE: devapp-3.4/rspack.config.js
================================================
const { defineConfig } = require('@meteorjs/rspack')

/**
 * Rspack configuration for Meteor projects.
 *
 * Provides typed flags on the `Meteor` object, such as:
 * - `Meteor.isClient` / `Meteor.isServer`
 * - `Meteor.isDevelopment` / `Meteor.isProduction`
 * - …and other flags available
 *
 * Use these flags to adjust your build settings based on environment.
 */
module.exports = defineConfig(Meteor => {
  return {
    module: {
      rules: [
        // Add support for importing SVGs as React components
        {
          test: /\.svg$/i,
          issuer: /\.[jt]sx?$/,
          use: ['@svgr/webpack'],
        },
      ],
    },
  }
})


================================================
FILE: devapp-3.4/server/main.js
================================================
import { Meteor } from 'meteor/meteor'
import { LinksCollection } from '/imports/api/links'
import { Random } from 'meteor/random'

async function insertLink({ title, url }) {
  await LinksCollection.insertAsync({ title, url, createdAt: new Date() })
}

Meteor.startup(async () => {
  // If the Links collection is empty, add some data.
  if ((await LinksCollection.find().countAsync()) === 0) {
    await insertLink({
      title: 'Do the Tutorial',
      url: 'https://docs.meteor.com/tutorials/react/',
    })

    await insertLink({
      title: 'Follow the Guide',
      url: 'https://docs.meteor.com/tutorials/application-structure/',
    })

    await insertLink({
      title: 'Read the Docs',
      url: 'https://docs.meteor.com',
    })

    await insertLink({
      title: 'Discussions',
      url: 'https://forums.meteor.com',
    })

    await insertLink({
      title: 'Join us on Discord',
      url: 'https://discord.gg/6mS3wHNg',
    })

    await insertLink({
      title: 'Deploying in Galaxy',
      url: 'https://www.meteor.com/hosting',
    })
  }

  // We publish the entire Links collection to all clients.
  // In order to be fetched in real-time to the clients
  Meteor.publish('links', function () {
    return LinksCollection.find()
  })
})

Meteor.methods({
  about() {
    return `This is a Meteor application running React with React Router. this is a generated id: ${Random.id()}`
  },
})


================================================
FILE: devapp-3.4/tests/main.js
================================================
import assert from 'assert'

describe('devapp-3.4', function () {
  it('package.json has correct name', async function () {
    const { name } = await import('../package.json')
    assert.strictEqual(name, 'devapp-3.4')
  })

  if (Meteor.isClient) {
    it('client is not server', function () {
      assert.strictEqual(Meteor.isServer, false)
    })
  }

  if (Meteor.isServer) {
    it('server is not client', function () {
      assert.strictEqual(Meteor.isClient, false)
    })
  }
})


================================================
FILE: eslint.config.mjs
================================================
import js from '@eslint/js'
import typescript from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import prettier from 'eslint-plugin-prettier'
import unicorn from 'eslint-plugin-unicorn'
import globals from 'globals'

export default [
  {
    ignores: [
      'node_modules/**',
      'extension/**',
      'devapp-*/**',
      '.yarn/**',
      'webpack/**',
    ],
  },
  // Base config for all files
  {
    files: ['**/*.{js,jsx,mjs,cjs}'],
    plugins: {
      react,
      'react-hooks': reactHooks,
      prettier,
      unicorn,
    },
    languageOptions: {
      ecmaVersion: 2020,
      sourceType: 'module',
      parser: typescriptParser,
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.mocha,
        Meteor: 'readonly',
        Helene: 'readonly',
      },
    },
    settings: {
      react: {
        version: 'detect',
      },
    },
    rules: {
      ...js.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...unicorn.configs.recommended.rules,
      'no-console': 0,
      'react/prop-types': 0,
      'react/jsx-curly-spacing': 0,
      'react/display-name': 0,
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 0,
      'no-inner-declarations': 0,
      'react/no-unescaped-entities': 0,
      'react/react-in-jsx-scope': 0,
      // Unicorn adjustments for this project
      'unicorn/prevent-abbreviations': 0,
      'unicorn/filename-case': 0,
      'unicorn/no-null': 0,
      'unicorn/prefer-module': 0,
      'unicorn/prefer-node-protocol': 0,
      'prettier/prettier': 'error',
    },
  },
  // TypeScript specific config
  {
    files: ['**/*.{ts,tsx}'],
    plugins: {
      '@typescript-eslint': typescript,
      react,
      'react-hooks': reactHooks,
      prettier,
      unicorn,
    },
    languageOptions: {
      ecmaVersion: 2020,
      sourceType: 'module',
      parser: typescriptParser,
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.mocha,
        Meteor: 'readonly',
        Helene: 'readonly',
      },
    },
    settings: {
      react: {
        version: 'detect',
      },
    },
    rules: {
      ...js.configs.recommended.rules,
      ...typescript.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...unicorn.configs.recommended.rules,
      'no-console': 0,
      'react/prop-types': 0,
      'react/jsx-curly-spacing': 0,
      'react/display-name': 0,
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 0,
      'no-inner-declarations': 0,
      'react/no-unescaped-entities': 0,
      'react/react-in-jsx-scope': 0,
      '@typescript-eslint/no-non-null-assertion': 0,
      '@typescript-eslint/no-explicit-any': 0,
      '@typescript-eslint/no-empty-interface': 0,
      '@typescript-eslint/explicit-module-boundary-types': 0,
      '@typescript-eslint/no-unused-vars': 0,
      '@typescript-eslint/no-this-alias': 0,
      '@typescript-eslint/ban-ts-comment': 0,
      '@typescript-eslint/no-namespace': 0,
      'no-undef': 0, // TypeScript handles this
      // Unicorn adjustments for this project
      'unicorn/prevent-abbreviations': 0,
      'unicorn/filename-case': 0,
      'unicorn/no-null': 0,
      'unicorn/prefer-module': 0,
      'unicorn/prefer-node-protocol': 0,
      'prettier/prettier': 'error',
    },
  },
]


================================================
FILE: extension/devtools-panel.html
================================================
<!doctype html>
<html data-theme="corporate">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <title>Panel</title>
  </head>
  <body>
    <div id="panel" class="bp4-dark"></div>
    <script src="/dist/bundle.js"></script>
  </body>
</html>


================================================
FILE: extension/devtools.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <title>Developer Tools</title>
  </head>
  <body>
    <!-- The sole purpose of this file is to load the JavaScript file as Chrome does not render it -->

    <script src="/dist/devtools.js"></script>
  </body>
</html>


================================================
FILE: extension/manifest-v2.json
================================================
{
  "manifest_version": 2,
  "name": "Meteor DevTools Evolved",
  "description": "The Meteor framework development tool belt, evolved.",
  "version": "1.8.1",
  "author": "Leonardo Venturini",
  "icons": {
    "16": "icons/meteor-16.png",
    "48": "icons/meteor-48.png",
    "128": "icons/meteor-128.png"
  },
  "browser_action": {
    "default_title": "Meteor"
  },
  "background": {
    "scripts": ["/dist/background.js"],
    "persistent": false
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["/dist/content.js"],
      "run_at": "document_start",
      "all_frames": true
    }
  ],
  "permissions": [
    "https://api.github.com/*",
    "https://www.google-analytics.com/*",
    "tabs"
  ],
  "content_security_policy": "script-src 'self'; object-src 'self'",
  "web_accessible_resources": ["/dist/inject.js"],
  "devtools_page": "devtools.html"
}


================================================
FILE: extension/manifest-v3.json
================================================
{
  "manifest_version": 3,
  "name": "Meteor DevTools Evolved",
  "description": "The Meteor framework development tool belt, evolved.",
  "version": "1.8.1",
  "author": "Leonardo Venturini",
  "icons": {
    "16": "icons/meteor-16.png",
    "48": "icons/meteor-48.png",
    "128": "icons/meteor-128.png"
  },
  "action": {
    "default_title": "Meteor",
    "default_icon": "icons/meteor-48.png"
  },
  "background": {
    "service_worker": "/dist/background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["/dist/content.js"],
      "run_at": "document_start",
      "all_frames": true
    }
  ],
  "host_permissions": [
    "https://api.github.com/*",
    "https://www.google-analytics.com/*"
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  },
  "web_accessible_resources": [
    {
      "resources": ["/dist/inject.js"],
      "matches": ["*://*/*"]
    }
  ],
  "devtools_page": "devtools.html"
}


================================================
FILE: extension/options.html
================================================
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <title>Options</title>
  </head>
  <body>
    <div id="options" class="bp4-dark"></div>
    <script src="/dist/bundle.js"></script>
  </body>
</html>


================================================
FILE: extension/popup.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <title>Popup</title>
  </head>
  <body>
    <div id="popup" class="bp4-dark"></div>
    <script src="/dist/bundle.js"></script>
  </body>
</html>


================================================
FILE: lint-staged.js
================================================
module.exports = {
  '*.{js,jsx,ts,tsx}': [
    'eslint',
    'react-scripts test --bail --watchAll=false --findRelatedTests --passWithNoTests',
    () => 'tsc-files --noEmit',
  ],
  '*.{js,jsx,ts,tsx,json,css,js}': ['prettier --write'],
}


================================================
FILE: package.json
================================================
{
  "name": "meteor-devtools-evolved",
  "version": "1.8.1",
  "description": "Meteor DevTools Evolved",
  "repository": "https://github.com/leonardoventurini/meteor-devtools-evolved",
  "packageManager": "yarn@4.12.0",
  "keywords": [
    "meteor",
    "ddp",
    "devtools"
  ],
  "scripts": {
    "setup": "cd devapp-3.4 && npm install && cd ../ && yarn",
    "devapp": "cd devapp-3.4 && npm start",
    "build:chrome": "webpack --config webpack/chrome.prod.js",
    "build:firefox": "webpack --config webpack/firefox.prod.js",
    "dev:chrome": "run-p build:chrome devapp open:chrome",
    "dev:firefox": "run-p build:firefox devapp open:firefox",
    "dev": "yarn dev:chrome",
    "wait:firefox": "wait-on extension/firefox/manifest.json http://localhost:2100",
    "wait:chrome": "wait-on extension/chrome/manifest.json http://localhost:2100",
    "open:firefox": "yarn wait:firefox && web-ext run --start-url \"http://localhost:2100\"  --source-dir ./extension/firefox/ --browser-console",
    "open:chrome": "yarn wait:chrome && web-ext run -t chromium --start-url \"http://localhost:2100\" --source-dir ./extension/chrome/ --browser-console",
    "clean": "rimraf extension/firefox extension/chrome",
    "lint": "eslint .",
    "audit": "yarn audit --all --recursive --severity high"
  },
  "author": "Leonardo Venturini",
  "license": "MIT",
  "dependencies": {
    "@babel/core": "^7.9.0",
    "@babel/preset-env": "^7.9.0",
    "@babel/preset-react": "^7.9.1",
    "@blueprintjs/core": "4.14.1",
    "@blueprintjs/icons": "4.12.1",
    "@blueprintjs/popover2": "^1.11.1",
    "@heroicons/react": "^2.0.13",
    "@types/chrome": "0.0.178",
    "@types/classnames": "^2.2.10",
    "@types/luxon": "^2.0.5",
    "@types/meteor": "^2.0.4",
    "@types/react": "^17.0.27",
    "@types/react-dom": "^17.0.9",
    "@types/react-json-tree": "^0.13.0",
    "@types/react-window": "^1.8.1",
    "@types/react-window-infinite-loader": "^1.0.3",
    "@types/styled-components": "^5.1.0",
    "babel-loader": "^9.1.0",
    "classnames": "2.3.1",
    "clean-webpack-plugin": "^4.0.0",
    "css-loader": "^6.3.0",
    "d3-collection": "^1.0.7",
    "d3-hierarchy": "^3.0.1",
    "d3-selection": "^3.0.0",
    "d3-shape": "^3.0.1",
    "daisyui": "^2.15.2",
    "dexie": "3.2.2",
    "lodash.debounce": "^4.0.8",
    "lodash.memoize": "^4.1.2",
    "lodash.sortby": "^4.7.0",
    "lodash.throttle": "^4.1.1",
    "luxon": "2.5.2",
    "mobx": "6.4.0",
    "mobx-react-lite": "3.3.0",
    "normalize.css": "8.0.1",
    "polished": "4.1.4",
    "postcss-loader": "^7.0.0",
    "pretty-bytes": "6.0.0",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-is": "17.0.2",
    "react-singleton-hook": "^3.2.1",
    "react-window": "1.8.6",
    "react-window-infinite-loader": "1.0.7",
    "sass": "^1.51.0",
    "sass-loader": "^12.1.0",
    "style-loader": "^3.3.0",
    "styled-components": "5.3.3",
    "tailwindcss": "^3.0.24",
    "terser-webpack-plugin": "^5.2.4",
    "ts-loader": "^9.2.6",
    "tslib": "^2.3.1",
    "typescript": "^4.4.3",
    "uuid": "^8.3.2",
    "webpack": "^5.76.0",
    "webpack-cli": "^4.9.0",
    "webpack-merge": "^5.8.0"
  },
  "volta": {
    "node": "24.13.0",
    "yarn": "4.12.0"
  },
  "resolutions": {
    "@babel/traverse": "^7.23.2",
    "axios": "^1.6.0",
    "braces": "^3.0.3",
    "cross-spawn": "^7.0.5",
    "fast-json-patch": "^3.1.1",
    "form-data": "^4.0.0",
    "http-cache-semantics": "^4.1.1",
    "json5": "^2.2.3",
    "jsonwebtoken": "^9.0.0",
    "jws": "^4.0.0",
    "loader-utils": "^3.2.1",
    "node-forge": "^1.3.2",
    "qs": "^6.14.1",
    "semver": "^7.5.4",
    "sha.js": "^2.4.12",
    "ws": "^8.17.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.0.0",
    "@types/webextension-polyfill": "^0.9.0",
    "@typescript-eslint/eslint-plugin": "^8.0.0",
    "@typescript-eslint/parser": "^8.0.0",
    "concurrently": "^7.2.2",
    "copy-webpack-plugin": "^11.0.0",
    "eslint": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "eslint-plugin-react": "^7.37.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-unicorn": "^56.0.0",
    "globals": "^15.0.0",
    "npm-run-all": "^4.1.5",
    "prettier": "^3.0.0",
    "prettier-plugin-tailwindcss": "^0.6.0",
    "wait-on": "^6.0.1",
    "web-ext": "^7.1.0",
    "webextension-polyfill": "^0.9.0"
  }
}


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: {
    tailwindcss: {},
  },
}


================================================
FILE: src/Analytics.ts
================================================
import { exists } from './Utils'
import { v4 as uuid } from 'uuid'
import { isString } from './Utils/StringUtils'

const GA_HOST = 'https://www.google-analytics.com'

type UUID = string

type RequestObject = {
  method?: string
  body?: string
  headers?: Record<string, string>
}

type EventOptions = {
  clientId?: UUID
  label?: string
  value?: number
}

type EventParams = {
  ec?: string
  ea?: string
  el?: string
  ev?: number
}

type PageViewParams = {
  dp?: string
  dh?: string
  dt?: string
  sc?: number
}

type ScreenParams = {
  an?: string
  av?: string
  cd?: string
  aiid?: string
  aid?: string
}

type TransactionOptions = {
  affiliation?: string
  revenue?: number
  shipping?: number
  tax?: number
  currencyCode?: string
}

type TransactionParams = {
  ti?: string
  ta?: string
  tr?: number
  ts?: number
  tt?: number
  cu?: string
}

type TimingOptions = {
  label?: string
  dns?: number
  pageDownTime?: number
  redirectTime?: number
  tcpConnectionTime?: number
  serverResponseTime?: number
}

type TimingParams = {
  utc?: string
  utv?: string
  utt?: number
  dns?: number
  utl?: string
  pdt?: number
  rrt?: number
  tcp?: number
  srt?: number
}

type AnalyticsOptions = {
  userAgent?: string
  debug?: boolean
  version?: number
  clientId?: string
}

export class Analytics {
  clientId = uuid()
  customParams = {}
  globalDebug = false
  globalUserAgent = ''
  globalBaseURL = GA_HOST
  globalDebugURL = '/debug'
  globalCollectURL = '/collect'
  globalBatchURL = '/batch'
  globalTrackingID: string
  globalVersion = 1

  constructor(trackingId, options: AnalyticsOptions = {}) {
    const { clientId, userAgent, debug = false, version = 1 } = options

    if (clientId) this.clientId = clientId
    if (userAgent) this.globalUserAgent = userAgent

    this.globalDebug = debug
    this.globalTrackingID = trackingId
    this.globalVersion = version
    this.customParams = {}
  }

  set(key: string, value = null) {
    if (value === null) {
      delete this.customParams[key]
    } else {
      this.customParams[key] = value
    }
  }

  pageView(
    hostname: string = location?.hostname,
    path: string = location?.pathname,
    title: string = document?.title,
    sessionDuration?: number,
  ) {
    const params: PageViewParams = {
      dh: hostname,
      dp: path,
      dt: title,
    }

    if (exists(sessionDuration)) {
      params.sc = sessionDuration
    }

    return this.send('pageview', params)
  }

  event(category?: string, action?: string, options: EventOptions = {}) {
    const { label, value } = options

    const params: EventParams = { ec: category, ea: action }

    if (label) params.el = label
    if (value) params.ev = value

    this.send('event', params).catch(console.error)
  }

  screen(
    appName: string,
    appVersion: string,
    appId: string,
    appInstallerId: string,
    screenName: string,
  ) {
    const params: ScreenParams = {
      an: appName,
      av: appVersion,
      aid: appId,
      aiid: appInstallerId,
      cd: screenName,
    }

    return this.send('screenview', params)
  }

  transaction(transactionId: UUID, options: TransactionOptions = {}) {
    const { affiliation, revenue, shipping, tax, currencyCode } = options
    const params: TransactionParams = { ti: transactionId }

    if (affiliation) params.ta = affiliation
    if (revenue) params.tr = revenue
    if (shipping) params.ts = shipping
    if (tax) params.tt = tax
    if (currencyCode) params.cu = currencyCode

    return this.send('transaction', params)
  }

  social(socialAction: string, socialNetwork: string, socialTarget: string) {
    const params = { sa: socialAction, sn: socialNetwork, st: socialTarget }

    return this.send('social', params)
  }

  exception(description: string, fatal: number, clientId: UUID) {
    const params = { exd: description, exf: fatal }

    return this.send('exception', params)
  }

  timingTrk(
    timingCategory: string,
    timingVariable: string,
    timingTime: number,
    options: TimingOptions,
  ) {
    const {
      label,
      dns,
      pageDownTime,
      redirectTime,
      tcpConnectionTime,
      serverResponseTime,
    } = options

    const params: TimingParams = {
      utc: timingCategory,
      utv: timingVariable,
      utt: timingTime,
    }

    if (label) params.utl = label
    if (dns) params.dns = dns
    if (pageDownTime) params.pdt = pageDownTime
    if (redirectTime) params.rrt = redirectTime
    if (tcpConnectionTime) params.tcp = tcpConnectionTime
    if (serverResponseTime) params.srt = serverResponseTime

    return this.send('timing', params)
  }

  send(hitType: string, params: Record<string, any>) {
    const payload = {
      v: this.globalVersion,
      tid: this.globalTrackingID,
      cid: this.clientId,
      t: hitType,
    }

    if (params) Object.assign(payload, params)

    if (Object.keys(this.customParams).length > 0) {
      Object.assign(payload, this.customParams)
    }

    let url = `${this.globalBaseURL}${this.globalCollectURL}`

    if (this.globalDebug) {
      url = `${this.globalBaseURL}${this.globalDebugURL}${this.globalCollectURL}`
    }

    const requestObject: RequestObject = {
      method: 'post',
      body: Object.keys(payload)
        .map(key => `${encodeURI(key)}=${encodeURI(payload[key])}`)
        .join('&'),
    }

    if (this.globalUserAgent && isString(this.globalUserAgent)) {
      requestObject.headers = { 'User-Agent': this.globalUserAgent }
    }

    return fetch(url, requestObject)
      .then(res => {
        let response = {}

        response =
          res.headers.get('content-type') === 'image/gif'
            ? res.text()
            : res.json()

        if (res.status === 200) {
          return response
        }

        throw new Error(response as string)
      })
      .then((json: any) => {
        if (this.globalDebug && json.hitParsingResult[0].valid) {
          return { clientId: payload.cid }
        }

        return { clientId: payload.cid }
      })
      .catch(error => new Error(error))
  }
}


================================================
FILE: src/App.tsx
================================================
import { FocusStyleManager } from '@blueprintjs/core'
import React from 'react'
import { render } from 'react-dom'
import { Options } from './Pages/Options'
import { Panel } from './Pages/Panel'
import { Popup } from './Pages/Popup'

import './Styles/Tailwind.css'
import './Styles/App.scss'

FocusStyleManager.onlyShowFocusOnTabs()

const panelElement = document.querySelector('#panel')
const optionsElement = document.querySelector('#options')
const popupElement = document.querySelector('#popup')

if (panelElement) render(<Panel />, panelElement)
if (optionsElement) render(<Options />, optionsElement)
if (popupElement) render(<Popup />, popupElement)


================================================
FILE: src/AppToaster.jsx
================================================
import { Position, Toaster } from '@blueprintjs/core'

export const AppToaster = Toaster.create({
  className: 'app-toaster',
  position: Position.TOP,
})


================================================
FILE: src/Bridge.ts
================================================
import { detectType } from '@/Pages/Panel/DDP/FilterConstants'
import prettyBytes from 'pretty-bytes'
import { PanelStore } from '@/Stores/PanelStore'
import { DateTime } from 'luxon'
import { StringUtils } from '@/Utils/StringUtils'
import browser from 'webextension-polyfill'

export const syncSubscriptions = () =>
  Bridge.sendContentMessage({
    eventType: 'sync-subscriptions',
    data: null,
  })

export const syncStats = () =>
  Bridge.sendContentMessage({
    eventType: 'stats',
    data: null,
  })

export const clearCache = () =>
  Bridge.sendContentMessage({
    eventType: 'cache:clear',
    data: null,
  })

export const Bridge = new (class {
  private handlers: Partial<Record<EventType, MessageHandler>> = {}

  register(eventType: EventType, handler: MessageHandler) {
    this.handlers[eventType] = handler
  }

  handle(message: Message<any>) {
    if (message.eventType in this.handlers) {
      const handler = this.handlers[message.eventType]

      if (handler) handler(message)
    }
  }

  sendContentMessage(message: Message<any>) {
    const payload: IMessagePayload<any> = {
      ...message,
      source: 'meteor-devtools-evolved',
    }

    if (browser && browser.devtools) {
      browser.devtools.inspectedWindow.eval(
        `__meteor_devtools_evolved_receiveMessage(${JSON.stringify(payload)})`,
      )
    }
  }

  chrome() {
    const backgroundConnection = browser.runtime.connect({
      name: 'panel',
    })

    backgroundConnection.postMessage({
      name: 'init',
      tabId: browser.devtools.inspectedWindow.tabId,
    })

    backgroundConnection.onMessage.addListener((message: Message<any>) =>
      Bridge.handle(message),
    )
  }

  init() {
    console.log('Setting up bridge...')

    if (!browser || !browser.devtools) return

    // FIXME : Need to confirm if using `chrome` instead of `browser` breaking any communication
    this.chrome()

    syncStats()
  }
})()

Bridge.register('ddp-event', (message: Message<DDPLog>) => {
  const size = StringUtils.getSize(message.data.content)
  const parsedContent = JSON.parse(message.data.content)
  const filterType = detectType(parsedContent)

  const log = {
    ...message.data,
    parsedContent,
    timestampPretty: message.data.timestamp
      ? DateTime.fromMillis(message.data.timestamp).toFormat('HH:mm:ss.SSS')
      : '',
    timestampLong: message.data.timestamp
      ? DateTime.fromMillis(message.data.timestamp).toLocaleString(
          DateTime.DATETIME_FULL,
        )
      : '',
    size,
    sizePretty: prettyBytes(size),
    filterType,
  }

  if (filterType === 'subscription') {
    syncSubscriptions()
  }

  PanelStore.ddpStore.pushItem(log)
})

Bridge.register(
  'minimongo-get-collections',
  (message: Message<RawCollections>) => {
    PanelStore.minimongoStore.setCollections(message.data)
  },
)

Bridge.register('sync-subscriptions', (message: Message<any>) => {
  PanelStore.syncSubscriptions(JSON.parse(message.data.subscriptions))
})

Bridge.register('stats', (message: Message<any>) => {
  console.log(message.data)

  PanelStore.setGitCommitHash(message.data.gitCommitHash)
})

Bridge.register('meteor-data-performance', (message: Message<CallData>) => {
  PanelStore.performanceStore.push(message.data)
})


================================================
FILE: src/Browser/Background.ts
================================================
import browser from 'webextension-polyfill'

type Connection = Map<number, any>

declare global {
  interface Window {
    connections: Connection
  }
}

const Cache = new Map<number, string[]>()

const connections: Connection = new Map()

globalThis.connections = connections

const panelListener = () => {
  browser.runtime.onConnect.addListener(port => {
    console.debug('runtime.onConnect', port)

    port.onMessage.addListener(request => {
      console.debug('port.onMessage', request)

      if (request.name === 'init') {
        connections.set(request.tabId, port)

        // Pick things from cache and send it to the panel.
        if (Cache.has(request.tabId)) {
          for (const message of Cache.get(request.tabId)) {
            port.postMessage(message)
          }
        }

        port.onDisconnect.addListener(() => {
          connections.delete(request.tabId)
        })
      }
    })
  })
}

const tabRemovalListener = () => {
  browser.tabs.onRemoved.addListener(tabId => {
    console.debug('tabs.onRemoved', tabId)

    if (connections.has(tabId)) {
      connections.delete(tabId)
      Cache.delete(tabId)
    }
  })
}

// For cross-browser support
const action = browser.browserAction || browser.action

action.onClicked.addListener(e => {
  console.debug('action.onClicked', e)

  browser.tabs
    .create({
      url: 'http://cloud.meteor.com/?utm_source=chrome_extension&utm_medium=extension&utm_campaign=meteor_devtools_evolved',
    })

    .catch(console.error)
})

const handleConsole = (
  tabId: number,
  { data: { type, message } }: Message<{ type: ConsoleType; message: string }>,
) => {
  if (type in console) {
    console[type](`[${tabId}]`, message)
  } else {
    console.warn('Wrong console type.')
  }
}

const contentListener = () => {
  // @ts-ignore
  browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
    setTimeout(() => {
      const tabId = sender?.tab?.id

      if (!tabId) return

      // The message event has to from the panel to the content and then through here.
      if (request?.eventType === 'cache:clear') {
        console.debug('clear cache')
        Cache.delete(tabId)
        return
      }

      if (request?.eventType === 'console') {
        handleConsole(tabId, request)
        return
      }

      if (Cache.has(tabId)) {
        const entry = Cache.get(tabId)

        if (entry.length >= 10_000) {
          entry.shift()
        }

        entry.push(request)
      } else {
        Cache.set(tabId, [request])
      }

      if (connections.has(tabId)) {
        connections.get(tabId).postMessage(request)
      }
    }, 0)

    sendResponse()
  })
}

const tabListener = () => {
  const tabEvent = {
    'create-tab': request =>
      browser.tabs
        .create({
          url: request.data.url,
        })
        .catch(console.error),
  }
  /**
   * @issue https://stackoverflow.com/a/73836810/10567157
   */
  chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
      sendResponse({ foo: true })

      if (request.source !== 'meteor-devtools-evolved') return true

      tabEvent[request.eventType]?.(request)

      return true
    },
  )
}

panelListener()
tabRemovalListener()
contentListener()
tabListener()


================================================
FILE: src/Browser/Content.ts
================================================
import browser from 'webextension-polyfill'

const messageHandler = (event: MessageEvent) => {
  // Only accept messages from same frame
  if (event.source !== globalThis) return

  // Only accept messages that we know are ours
  if (event.data.source !== 'meteor-devtools-evolved') return

  browser.runtime.sendMessage(event.data).catch(() => {
    // Cleans up and prevent "context invalidated" errors.
    window.removeEventListener('message', messageHandler)
  })
}

window.addEventListener('message', messageHandler)

const url = browser.runtime.getURL('/dist/inject.js')
const script = document.createElement('script')
script.setAttribute('type', 'text/javascript')
script.setAttribute('src', url)
document.documentElement.prepend(script)


================================================
FILE: src/Browser/DevTools.ts
================================================
import browser from 'webextension-polyfill'
import { checkFirefoxBrowser } from '@/Utils'

const isFirefox = checkFirefoxBrowser()

browser.devtools.panels.create(
  `${isFirefox ? '' : '☄️'} Meteor`,
  '',
  'devtools-panel.html',
)


================================================
FILE: src/Browser/Inject.ts
================================================
import { DDPInjector } from '@/Injectors/DDPInjector'
import {
  MinimongoInjector,
  updateCollections,
} from '@/Injectors/MinimongoInjector'
import { MeteorAdapter } from '@/Injectors/MeteorAdapter'

const isFrame = (function () {
  try {
    return globalThis.self !== window.top
  } catch {
    return true
  }
})()

const PARENTHESIS_REGEX = /(\S*) \(([^)]+)\)/

export const sendMessage = (eventType: EventType, data: object) => {
  window.postMessage(
    {
      eventType,
      data,
      source: 'meteor-devtools-evolved',
    } as Message<object>,
    '*',
  )
}

const warning = (message: string) => {
  sendMessage('console', {
    type: 'info',
    message,
  } as { type: ConsoleType; message: string })
}

/**
 * @todo Do nothing here, and run any stack trace processing logic inside the extension, so if any errors happen it happens in the sandbox console.
 */
const getStackTrace = (stackTraceLimit: number) => {
  const originalStackTraceLimit = Error.stackTraceLimit

  try {
    Error.stackTraceLimit = stackTraceLimit
    const error = new Error('Stack trace')

    if (!error.stack) return []

    return error?.stack
      ?.split('\n')
      .map(trace => {
        const matches = PARENTHESIS_REGEX.exec(trace)

        if (!matches) return null

        return {
          callee: matches?.[1],
          url: matches?.[2],
        }
      })
      .filter(Boolean)
  } finally {
    Error.stackTraceLimit = originalStackTraceLimit
  }
}

export const sendLogMessage = (message: DDPLog) => {
  const stackTrace = getStackTrace(15)

  if (stackTrace && stackTrace.length > 0) {
    stackTrace.splice(0, 2)
  }

  sendMessage('ddp-event', {
    ...message,
    trace: stackTrace,
    host: location.host,
  })

  if (
    message.content !== '{"msg":"ping"}' &&
    message.content !== '{"msg":"pong"}'
  )
    updateCollections()
}

type MessageHandler = (message: Message<any>) => void
type Registration = {
  eventType: EventType
  handler: MessageHandler
}

interface IRegistry {
  subscriptions: Registration[]

  register(eventType: EventType, handler: MessageHandler): void

  run(message: Message<any>): void
}

export const Registry: IRegistry = {
  subscriptions: [],

  register(eventType: EventType, handler: MessageHandler) {
    this.subscriptions.push({
      eventType,
      handler,
    })
  },

  run(message: IMessagePayload<any>) {
    for (const { eventType, handler } of this.subscriptions) {
      if (
        message.source === 'meteor-devtools-evolved' &&
        eventType === message.eventType
      ) {
        handler(message)
      }
    }
  },
}

export function injectAll() {
  if (!globalThis.__meteor_devtools_evolved) {
    if (isFrame) return false

    warning(
      isFrame
        ? `Initializing from iframe "${location.href}"...`
        : 'Initializing on the main page...',
    )

    let attempts = 100
    let interval = null

    function inject() {
      --attempts

      if (typeof Meteor === 'object' && !globalThis.__meteor_devtools_evolved) {
        globalThis.__meteor_devtools_evolved = true

        DDPInjector()
        MinimongoInjector()
        MeteorAdapter()

        globalThis.__meteor_devtools_evolved_receiveMessage =
          Registry.run.bind(Registry)

        warning(`Initialized. Attempts: ${100 - attempts}.`)
      }

      if (attempts === 0) {
        clearInterval(interval)

        if (!globalThis.Meteor) {
          warning(
            isFrame
              ? `Unable to find Meteor on iframe "${location.href}"`
              : 'Unable to find Meteor on the main page.',
          )
        }
      }
    }

    inject()

    interval = globalThis.setInterval(inject, 10)
  }
}

injectAll()


================================================
FILE: src/Browser/MeteorLibrary.ts
================================================
import { JSONUtils } from '@/Utils/JSONUtils'
import { mapValues, omit } from '@/Utils/Objects'

export const getSubscriptions = () => {
  const payload = mapValues(
    Meteor?.connection?._subscriptions ?? {},
    (value: any) => omit(value, ['connection', 'readyDeps']),
  )

  return JSONUtils.stringify(payload)
}


================================================
FILE: src/Components/Button.tsx
================================================
import React, { ButtonHTMLAttributes, FunctionComponent } from 'react'
import styled from 'styled-components'
import { Icon, IconName, Intent } from '@blueprintjs/core'
import { centerItems, truncate } from '@/Styles/Mixins'
import classnames from 'classnames'
import { isNumber, isString } from 'lodash'
import { Popover2 } from '@blueprintjs/popover2'

const ButtonWrapper = styled.button`
  ${centerItems};

  cursor: pointer;
  position: relative;
  overflow: hidden;
  background: transparent;
  border: none;
  color: #eee;
  font-size: 1rem;
  padding: 0 8px;

  .icon + span {
    margin-left: 4px;
  }

  &.warning {
    background-color: rgba(217, 130, 43, 0.25);
    color: #ffb366;

    &:hover {
      background-color: rgba(217, 130, 43, 0.25);
    }

    &:active {
      background-color: rgba(217, 130, 43, 0.1);
    }
  }

  &:hover:not([disabled], .warning) {
    background-color: rgba(0, 0, 0, 0.2);
  }

  &[disabled] {
    cursor: not-allowed;
  }

  &.shine {
    &:before {
      content: '';
      display: block;
      position: absolute;
      background: rgba(255, 255, 255, 0.5);
      width: 60px;
      height: 100%;
      left: 0;
      top: 0;
      opacity: 0.5;
      filter: blur(30px);
      transform: translateX(-100px) skewX(-15deg);
    }

    &:after {
      content: '';
      display: block;
      position: absolute;
      background: rgba(255, 255, 255, 0.2);
      width: 30px;
      height: 100%;
      left: 30px;
      top: 0;
      opacity: 0;
      filter: blur(5px);
      transform: translateX(-100px) skewX(-15deg);
    }

    &:hover:before {
      transform: translateX(300px) skewX(-15deg);
      opacity: 0.6;
      transition: 1.5s;
    }

    &:hover:after {
      transform: translateX(300px) skewX(-15deg);
      opacity: 1;
      transition: 1.5s;
    }
  }

  .button-wrapper {
    display: flex;
    align-items: center;
    width: 100%;

    span.content {
      flex-grow: 1;
      ${truncate};
      text-align: left;
    }

    span.subtitle {
      flex-shrink: 0;
      flex-grow: 1;
      font-size: 10px;
      color: #ccc;
      margin-left: auto;
      text-align: right;
    }
  }
`

interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
  icon?: IconName | JSX.Element
  intent?: Intent
  shine?: boolean
  active?: boolean
  subtitle?: string
}

export const Button: FunctionComponent<Props> = ({
  icon,
  children,
  intent,
  className,
  shine,
  active,
  subtitle,
  title,
  ...rest
}) => {
  const classes = classnames(
    {
      shine,
      active,
      warning: intent === 'warning',
    },
    className,
    'h-full',
  )

  if (title) {
    return (
      <Popover2
        content={<div className='p-4'>{title}</div>}
        interactionKind='hover'
        className='inline-flex items-center'
      >
        <ButtonWrapper className={classes} {...rest}>
          <div className='button-wrapper'>
            {icon &&
              (isString(icon) ? (
                <Icon icon={icon} className='icon' iconSize={12} />
              ) : (
                icon
              ))}
            {(children || isNumber(children)) && (
              <span className='content'>{children}</span>
            )}
            {(subtitle || isNumber(subtitle)) && (
              <span className='subtitle'>{subtitle}</span>
            )}
          </div>
        </ButtonWrapper>
      </Popover2>
    )
  }

  return (
    <ButtonWrapper className={classes} {...rest}>
      <div className='button-wrapper'>
        {icon &&
          (isString(icon) ? (
            <Icon icon={icon} className='icon' iconSize={12} />
          ) : (
            icon
          ))}
        {(children || isNumber(children)) && (
          <span className='content'>{children}</span>
        )}
        {(subtitle || isNumber(subtitle)) && (
          <span className='subtitle'>{subtitle}</span>
        )}
      </div>
    </ButtonWrapper>
  )
}


================================================
FILE: src/Components/Field.tsx
================================================
import React, { FunctionComponent } from 'react'
import styled from 'styled-components'
import { centerItems } from '@/Styles/Mixins'
import { Icon, IconName } from '@blueprintjs/core'
import { exists } from '@/Utils'
import classnames from 'classnames'

const Wrapper = styled.span`
  ${centerItems};
  height: 100%;
  padding: 0 8px;

  .icon + span {
    margin-left: 4px;
  }

  &.warning {
    background-color: rgba(217, 130, 43, 0.25);
    color: #ffb366;
  }
`

interface Props {
  icon?: IconName
  intent?: 'warning'
  className?: string
}

export const Field: FunctionComponent<Props> = ({
  children,
  icon,
  className,
  intent,
}) => {
  const classes = classnames(
    {
      warning: intent === 'warning',
    },
    className,
  )

  return (
    <Wrapper className={classes}>
      {icon && <Icon icon={icon} className='icon' iconSize={12} />}
      {exists(children) && <span>{children}</span>}
    </Wrapper>
  )
}


================================================
FILE: src/Components/PopoverButton.tsx
================================================
import React, { FunctionComponent } from 'react'
import { IconName } from '@blueprintjs/core'
import { Button } from '@/Components/Button'
import styled from 'styled-components'
import { Popover2, Popover2Props } from '@blueprintjs/popover2'

interface WrapperProps {
  height: number
}

const Wrapper = styled.span`
  button.popover-button {
    display: inline-block;
    height: ${(props: WrapperProps) => props.height}px;
  }
`

interface Props extends Popover2Props {
  icon: IconName
  height?: number
}

export const PopoverButton: FunctionComponent<Props> = ({
  icon,
  children,
  height = 28,
  ...rest
}) => (
  <Wrapper height={height}>
    <Popover2 {...rest}>
      <Button icon={icon} className='popover-button'>
        {children}
      </Button>
    </Popover2>
  </Wrapper>
)


================================================
FILE: src/Components/Separator.tsx
================================================
import React, { FunctionComponent } from 'react'
import styled from 'styled-components'

interface WrapperProps {
  horizontal?: boolean
}

const Wrapper = styled.div`
  width: ${({ horizontal }: WrapperProps) => (horizontal ? undefined : '1px')};
  height: ${({ horizontal }: WrapperProps) => (horizontal ? '1px' : undefined)};
  margin: 0 3px;
  background-color: rgba(0, 0, 0, 0.05);
`

export const Separator: FunctionComponent<WrapperProps> = props => (
  <Wrapper {...props} />
)


================================================
FILE: src/Components/StatusBar.tsx
================================================
import React, { FunctionComponent } from 'react'
import styled from 'styled-components'
import { NAVBAR_HEIGHT } from '@/Styles/Constants'
import { lighten } from 'polished'
import { centerItems } from '@/Styles/Mixins'

const backgroundColor = '#202b33'

const Wrapper = styled.div`
  user-select: none;
  display: flex;
  box-sizing: border-box;
  flex-direction: row;
  height: ${NAVBAR_HEIGHT}px;
  width: 100%;

  background-color: ${backgroundColor};

  button {
    height: 100%;
    flex: 1 1 auto;

    &:hover {
      background-color: ${lighten(0.05, backgroundColor)};
    }
  }

  .left-group,
  .right-group {
    ${centerItems};
  }

  .right-group {
    margin-left: auto;
  }

  & > * + * {
    margin-left: 8px;
  }
`

export const StatusBar: FunctionComponent = ({ children }) => (
  <Wrapper>{children}</Wrapper>
)


================================================
FILE: src/Components/TabBar.tsx
================================================
import React, { FunctionComponent, useState } from 'react'
import styled from 'styled-components'
import { IconName, Menu, MenuItem, Position } from '@blueprintjs/core'
import classnames from 'classnames'
import { Button } from './Button'
import { lighten } from 'polished'
import { NAVBAR_HEIGHT } from '@/Styles/Constants'
import { useBreakpoints } from '@/Utils/Hooks/useBreakpoints'
import { Popover2 } from '@blueprintjs/popover2'

const backgroundColor = '#202b33'

const TabBarWrapper = styled.div`
  user-select: none;
  display: flex;
  box-sizing: border-box;
  flex-direction: row;
  height: ${NAVBAR_HEIGHT}px;
  width: 100%;
  border-bottom: 1px solid ${lighten(0.1, backgroundColor)};

  background-color: ${backgroundColor};

  button.mde-tab {
    &.active {
      background-color: ${lighten(0.1, backgroundColor)};
    }

    &:hover:not(.active) {
      background-color: ${lighten(0.05, backgroundColor)};
    }
  }

  .right-menu {
    display: flex;
    flex-direction: row;
    margin-left: auto;

    button.menu-item {
      &:hover {
        background-color: ${lighten(0.05, backgroundColor)};
      }

      .bp3-icon {
        margin-bottom: 2px;
      }
    }
  }
`

export interface ITab {
  key: string
  content: JSX.Element | string
  icon: IconName
  shine?: boolean
  handler?: () => void
}

export interface IMenuItem {
  key: string
  content?: JSX.Element | string
  icon?: IconName | JSX.Element
  shine?: boolean
  handler: () => void

  title?: string
}

interface Props {
  tabs: ITab[]
  menu?: IMenuItem[]
  onChange?: (key: string) => void
}

export const TabBar: FunctionComponent<Props> = ({ tabs, menu, onChange }) => {
  const [activeKey, setKey] = useState(tabs[0].key)

  const { navigationCollapse } = useBreakpoints()

  const rightMenu = navigationCollapse ? (
    <Popover2
      content={
        <Menu>
          {menu?.map(item => (
            <MenuItem
              key={item.key}
              icon={item.icon}
              text={item.content}
              onClick={item.handler}
            />
          ))}
        </Menu>
      }
      position={Position.BOTTOM_LEFT}
    >
      <Button icon='menu' style={{ height: 28 }} />
    </Popover2>
  ) : (
    menu?.map(item => (
      <Button
        key={item.key}
        className='menu-item'
        onClick={item.handler}
        icon={item.icon}
        shine={item.shine}
        title={item.title}
      >
        {item.content}
      </Button>
    ))
  )

  return (
    <TabBarWrapper>
      {tabs.map(tab => (
        <Button
          key={tab.key}
          onClick={() => {
            setKey(tab.key)
            if (onChange) onChange(tab.key)
            if (tab.handler) tab.handler()
          }}
          className={classnames('mde-tab', {
            active: activeKey === tab.key,
          })}
          icon={tab.icon}
          shine={tab.shine}
        >
          {tab.content}
        </Button>
      ))}

      <div className='right-menu'>{rightMenu}</div>
    </TabBarWrapper>
  )
}


================================================
FILE: src/Components/TextInput.tsx
================================================
import React, { FunctionComponent, InputHTMLAttributes } from 'react'
import styled from 'styled-components'
import { Icon, IconName } from '@blueprintjs/core'
import { centerItems } from '@/Styles/Mixins'

const Wrapper = styled.div`
  ${centerItems};
  height: 100%;
  padding: 0 8px;
  background-color: rgba(0, 0, 0, 0.2);

  .icon {
    margin-right: 6px;
  }

  input[type='text'] {
    border: none;
    background: transparent;
    height: 100%;

    color: #eee;

    ::placeholder {
      color: #aaa;
    }
  }
`

interface Props extends InputHTMLAttributes<HTMLInputElement> {
  icon?: IconName
}

export const TextInput: FunctionComponent<Props> = ({ icon, ...rest }) => (
  <Wrapper>
    <Icon icon={icon} iconSize={12} className='icon' />
    <input type='text' {...rest} />
  </Wrapper>
)


================================================
FILE: src/Constants.ts
================================================
export const DEFAULT_OFFSET = 50

export const DEVELOPMENT = process.env.MODE === 'development'

export enum PanelPage {
  DDP = 'ddp',
  BOOKMARKS = 'bookmarks',
  MINIMONGO = 'minimongo',
  SUBSCRIPTIONS = 'subscriptions',
  PERFORMANCE = 'performance',
}


================================================
FILE: src/Database/PanelDatabase.ts
================================================
import Dexie from 'dexie'
import { toJS } from 'mobx'

class Database extends Dexie {
  bookmarks: Dexie.Table<Bookmark, string>
  data: Dexie.Table<Record<string, any>, string>

  constructor() {
    super('MeteorToolsDatabase')

    this.version(1).stores({
      bookmarks: 'id, timestamp, log',
    })

    this.version(2).stores({
      data: 'id',
    })

    this.bookmarks = this.table('bookmarks')
    this.data = this.table('data')
  }

  add(log: DDPLog) {
    return this.bookmarks.add({
      id: log.id,
      timestamp: Date.now(),
      log: toJS(log),
    })
  }

  get(key: string) {
    return this.bookmarks.get(key)
  }

  remove(key: string) {
    return this.bookmarks.delete(key)
  }

  getAll() {
    return this.bookmarks.toArray()
  }

  async getSettings() {
    return (await this.data.get('settings')) ?? {}
  }

  async saveSettings(settings: ISettings) {
    return (await this.data.get('settings'))
      ? this.data.update('settings', settings)
      : this.data.add({
          id: 'settings',
          ...settings,
        })
  }
}

export const PanelDatabase = new Database()


================================================
FILE: src/Injectors/DDPInjector.ts
================================================
import { sendLogMessage } from '@/Browser/Inject'

type MessageCallback = (message: DDPLog) => void

const generateId = () => (Date.now() + Math.random()).toString(36)

const injectOutboundInterceptor = (callback: MessageCallback) => {
  const send = Meteor.connection._stream.send

  Meteor.connection._stream.send = function (...args) {
    send.apply(this, args)

    callback({
      id: generateId(),
      content: args[0],
      isOutbound: true,
      timestamp: Date.now(),
    })
  }
}

const injectInboundInterceptor = (callback: MessageCallback) => {
  Meteor.connection._stream.on('message', (...args) => {
    callback({
      id: generateId(),
      content: args[0],
      isInbound: true,
      timestamp: Date.now(),
    })
  })
}

export const DDPInjector = () => {
  injectOutboundInterceptor(sendLogMessage)
  injectInboundInterceptor(sendLogMessage)
}


================================================
FILE: src/Injectors/MeteorAdapter.ts
================================================
import { Registry, sendMessage } from '@/Browser/Inject'
import { getSubscriptions } from '@/Browser/MeteorLibrary'
import { JSONUtils } from '@/Utils/JSONUtils'

export const MeteorAdapter = () => {
  Registry.register('ddp-run-method', (message: Message<any>) => {
    const { method, params } = message.data

    Meteor.call(method, ...params)
  })

  Registry.register('sync-subscriptions', () => {
    sendMessage('sync-subscriptions', {
      subscriptions: getSubscriptions(),
    })
  })

  Registry.register('stats', () => {
    sendMessage('stats', {
      gitCommitHash: Meteor.gitCommitHash,
    })
  })

  Registry.register('cache:clear', () => {
    sendMessage('cache:clear', {})
  })

  const prototype = Mongo.Collection.prototype

  for (const [key, val] of Object.entries(prototype)) {
    if (
      ['find', 'findOne', 'insert', 'update', 'upsert', 'remove'].includes(
        key,
      ) &&
      typeof val === 'function'
    ) {
      const original = prototype[key]

      prototype[key] = function (...args) {
        const startMs = Date.now()
        const result = original.apply(this, args)

        sendMessage('meteor-data-performance', {
          collectionName: this._name,
          key,
          args: JSON.stringify(args, JSONUtils.getCircularReplacer()),
          runtime: Date.now() - startMs,
        })

        return result
      }
    }
  }
}


================================================
FILE: src/Injectors/MinimongoInjector.ts
================================================
import { warning } from '@/Log'
import { Registry, sendMessage } from '@/Browser/Inject'
import throttle from 'lodash.throttle'

function cloneDeep(obj: any) {
  return structuredClone(obj)
}

function isArray(obj: any) {
  return Array.isArray(obj)
}

const cleanup = (object: any) => {
  if (typeof object !== 'object') return object

  const clonedObject = cloneDeep(object)

  if (!clonedObject) return clonedObject

  for (const key of Object.keys(clonedObject)) {
    if (!clonedObject[key]) {
      return
    }

    if (typeof clonedObject[key] === 'object') {
      if (isArray(clonedObject[key])) {
        clonedObject[key] = clonedObject[key].map((item: any) => cleanup(item))
        return
      }

      if (clonedObject[key] instanceof Date) {
        clonedObject[key] = `[Object::${
          clonedObject[key].constructor.name
        }] ${clonedObject[key].toISOString()}`
        return
      }

      if (clonedObject[key].constructor.name !== 'Object') {
        if (typeof clonedObject[key].toString === 'function') {
          clonedObject[key] = `[Object::${
            clonedObject[key].constructor.name
          }] ${clonedObject[key].toString()}`
          return
        } else {
          clonedObject[key] = `[Object::${clonedObject[key].constructor.name}]`
          return
        }
      }

      clonedObject[key] = cleanup(clonedObject[key])
    }
  }

  return clonedObject
}

const getDocs = (collection: any) => {
  return collection._docs._map instanceof Map
    ? collection._docs._map?.values() || []
    : Object.values(collection._docs._map || {})
}

const getCollections = () => {
  const collections = Meteor.connection._mongo_livedata_collections

  if (!collections) {
    warning(
      'Collections not initialized in the client yet. Possibly forgotten to be imported.',
    )
    return
  }

  const data = Object.fromEntries(
    Object.values(collections).map((collection: any) => [
      collection.name,
      [...getDocs(collection)].map(item => cleanup(item)),
    ]),
  )

  sendMessage('minimongo-get-collections', data as any)
}

export const updateCollections = throttle(getCollections, 1000, {
  leading: true,
  trailing: true,
})

export const MinimongoInjector = () => {
  Registry.register('minimongo-get-collections', () => {
    getCollections()
  })
}


================================================
FILE: src/Log.ts
================================================
export const warning = (message: string) => {
  console.log(
    '%c'.concat('Meteor DevTools Evolved: ').concat(message),
    'color: #bada55',
  )
}


================================================
FILE: src/Pages/Options.tsx
================================================
import React, { FunctionComponent } from 'react'

export const Options: FunctionComponent = () => {
  return (
    <div>
      <h1>Options</h1>
    </div>
  )
}


================================================
FILE: src/Pages/Panel/Bookmarks/Bookmarks.tsx
================================================
import { usePanelStore } from '@/Stores/PanelStore'
import { Hideable } from '@/Utils/Hideable'
import { observer } from 'mobx-react-lite'
import React, { FunctionComponent } from 'react'
import { DDPContainer } from '@/Pages/Panel/DDP/DDPContainer'
import { BookmarksStatus } from './BookmarksStatus'

interface Props {
  isVisible: boolean
}

export const Bookmarks: FunctionComponent<Props> = observer(({ isVisible }) => {
  const store = usePanelStore()
  const bookmarkStore = store.bookmarkStore

  return (
    <Hideable isVisible={isVisible}>
      <DDPContainer isVisible={isVisible} source={bookmarkStore} />

      <BookmarksStatus />
    </Hideable>
  )
})


================================================
FILE: src/Pages/Panel/Bookmarks/BookmarksStatus.tsx
================================================
import { observer } from 'mobx-react-lite'
import React, { FormEvent, FunctionComponent, useCallback } from 'react'

import { usePanelStore } from '@/Stores/PanelStore'
import { StatusBar } from '@/Components/StatusBar'
import { DDPFilterMenu } from '@/Pages/Panel/DDP/DDPFilterMenu'
import { Position } from '@blueprintjs/core/lib/esm/common/position'
import { TextInput } from '@/Components/TextInput'
import { PopoverButton } from '@/Components/PopoverButton'
import { Field } from '@/Components/Field'
import { exists } from '@/Utils'

export const BookmarksStatus: FunctionComponent = observer(() => {
  const store = usePanelStore()
  const { bookmarkStore, settingStore } = store

  const activeFilters = store.settingStore.activeFilters
  const setFilter = useCallback(
    (type, isEnabled) => settingStore.setFilter(type, isEnabled),
    [settingStore],
  )
  const collectionLength = bookmarkStore.collection.length
  const { pagination } = bookmarkStore

  return (
    <StatusBar>
      <div className='left-group'>
        <PopoverButton
          icon='filter'
          height={28}
          content={
            <DDPFilterMenu
              setFilter={setFilter}
              activeFilters={activeFilters}
            />
          }
          position={Position.RIGHT_TOP}
        >
          Filter
        </PopoverButton>

        <TextInput
          icon='search'
          placeholder='Search...'
          onChange={(event: FormEvent<HTMLInputElement>) =>
            pagination.setSearch(event.currentTarget.value)
          }
        />

        <Field icon='eye-open'>{pagination.length}</Field>
      </div>

      <div className='right-group'>
        {exists(collectionLength) && (
          <Field intent='warning' icon='inbox'>
            {collectionLength}
          </Field>
        )}
      </div>
    </StatusBar>
  )
})


================================================
FILE: src/Pages/Panel/DDP/DDP.tsx
================================================
import { usePanelStore } from '@/Stores/PanelStore'
import { Hideable } from '@/Utils/Hideable'
import { observer } from 'mobx-react-lite'
import React, { FunctionComponent } from 'react'
import { DDPStatus } from './DDPStatus'
import { DDPContainer } from '@/Pages/Panel/DDP/DDPContainer'

interface Props {
  isVisible: boolean
}

export const DDP: FunctionComponent<Props> = observer(({ isVisible }) => {
  const store = usePanelStore()
  const ddpStore = store.ddpStore

  return (
    <Hideable isVisible={isVisible}>
      <DDPContainer isVisible={isVisible} source={ddpStore} />

      <DDPStatus />
    </Hideable>
  )
})


================================================
FILE: src/Pages/Panel/DDP/DDPContainer.tsx
================================================
import React, { FunctionComponent, useRef } from 'react'
import { DDPLog } from '@/Pages/Panel/DDP/DDPLog'
import { FixedSizeList } from 'react-window'
import { observer } from 'mobx-react-lite'
import { DDPStore } from '@/Stores/Panel/DDPStore'
import { BookmarkStore } from '@/Stores/Panel/BookmarkStore'
import { useDimensions } from '@/Utils/Hooks/useDimensions'
import { usePanelStore } from '@/Stores/PanelStore'

interface Props {
  source: DDPStore | BookmarkStore
  isVisible: boolean
}

export const DDPContainer: FunctionComponent<Props> = observer(
  ({ source, isVisible }) => {
    const store = usePanelStore()
    const contentRef = useRef<HTMLDivElement>(null)

    const { width, height } = useDimensions(contentRef, [isVisible])

    const Row: FunctionComponent<any> = observer(({ data, index, style }) => {
      const item = (data as any).items[index]
      const log = 'log' in item ? item.log : item

      return (
        <DDPLog
          key={log.id}
          style={style}
          log={log}
          isNew={'newLogs' in source && source.newLogs.includes(log.id)}
          isStarred={store.bookmarkStore.bookmarkIds.includes(log.id)}
        />
      )
    })

    const list = (
      <FixedSizeList
        height={height}
        width={width}
        itemCount={source.filtered.length}
        itemSize={28}
        itemData={{ items: source.filtered }}
      >
        {Row}
      </FixedSizeList>
    )

    return (
      <div className='mde-content mde-ddp' ref={contentRef}>
        {source.filtered.length > 0 ? list : null}
      </div>
    )
  },
)


================================================
FILE: src/Pages/Panel/DDP/DDPFilterMenu.tsx
================================================
import { Switch } from '@blueprintjs/core'
import { observer } from 'mobx-react-lite'
import React, { FormEvent, FunctionComponent } from 'react'
import { FilterCriteria } from './FilterConstants'

interface Props {
  activeFilters: FilterTypeMap<boolean>
  setFilter: (filter: FilterType, isEnabled: boolean) => void
}

export const DDPFilterMenu: FunctionComponent<Props> = observer(
  ({ activeFilters, setFilter }) => {
    const filters = Object.keys(FilterCriteria).map(filter => (
      <Switch
        key={filter}
        checked={activeFilters[filter as FilterType]}
        label={filter.charAt(0).toUpperCase() + filter.slice(1)}
        onChange={(event: FormEvent<HTMLInputElement>) =>
          setFilter(filter as FilterType, event.currentTarget.checked)
        }
      />
    ))

    return <div style={{ padding: 10 }}>{filters}</div>
  },
)


================================================
FILE: src/Pages/Panel/DDP/DDPLog.tsx
================================================
import { Tag, Tooltip } from '@blueprintjs/core'
import classnames from 'classnames'
import React, { CSSProperties, FunctionComponent } from 'react'
import { DDPLogDirection } from './DDPLogDirection'
import { DDPLogPreview } from './DDPLogPreview'
import { DateTime } from 'luxon'
import styled from 'styled-components'
import { truncate } from '@/Styles/Mixins'
import { DDPLogMenu } from '@/Pages/Panel/DDP/DDPLogMenu'

interface Props {
  log: DDPLog
  style: CSSProperties
  isNew: boolean
  isStarred: boolean
}

const DDPLogWrapper = styled.div`
  display: flex;
  align-items: center;
  flex-direction: row;
  justify-content: space-between;
  padding: 5px 15px;

  transition: background-color 0.5s ease;

  &.m-new {
    background-color: #30594d;
  }

  &.m-starred {
    background-color: #304066;
  }

  div + div {
    margin-left: 10px;
  }

  .time {
    font-size: 11px;
    font-family: inherit;
  }

  .content {
    display: flex;
    flex: 1;
    align-items: center;
    min-width: 0;

    .content-icon {
      margin-right: 10px;
    }

    .content-preview {
      flex: 0 1 auto;
      min-width: 0;

      code {
        font-family: monospace;
        ${truncate}
      }
    }
  }

  &:hover {
    background-color: #394b59;
  }
`

export const DDPLog: FunctionComponent<Props> = ({
  log,
  style,
  isNew,
  isStarred,
}) => {
  const classes = classnames(
    {
      'm-new': isNew,
      'm-starred': isStarred,
    },
    'group',
  )

  return (
    <DDPLogWrapper className={classes} style={style}>
      <div className='time'>
        <Tooltip
          content={
            log.timestampLong ||
            (log.timestamp
              ? DateTime.fromMillis(log.timestamp).toLocaleString()
              : '')
          }
          hoverOpenDelay={800}
          position='top'
        >
          <small>{log.timestampPretty}</small>
        </Tooltip>
      </div>
      <div className='direction'>
        <DDPLogDirection
          isOutbound={log.isOutbound}
          isInbound={log.isInbound}
        />
      </div>
      <div className='content'>
        <DDPLogPreview
          parsedContent={log.parsedContent}
          preview={log.preview}
          filterType={log.filterType}
        />
      </div>

      <DDPLogMenu log={log} />

      <div className='size'>
        <Tag minimal>{log.sizePretty}</Tag>
      </div>
    </DDPLogWrapper>
  )
}


================================================
FILE: src/Pages/Panel/DDP/DDPLogDirection.tsx
================================================
import { Icon } from '@blueprintjs/core'
import React, { FunctionComponent } from 'react'

interface Prop {
  isOutbound?: boolean
  isInbound?: boolean
}

export const DDPLogDirection: FunctionComponent<Prop> = ({
  isOutbound,
  isInbound,
}) => {
  if (isOutbound && isInbound) return <Icon icon='full-circle' iconSize={12} />

  if (isOutbound)
    return <Icon icon='arrow-top-right' intent='danger' iconSize={12} />

  if (isInbound)
    return <Icon icon='arrow-bottom-left' intent='success' iconSize={12} />

  return <Icon icon='warning-sign' intent='warning' iconSize={12} />
}


================================================
FILE: src/Pages/Panel/DDP/DDPLogMenu.tsx
================================================
import { Icon } from '@blueprintjs/core'
import { PanelPage } from '@/Constants'
import { Bridge } from '@/Bridge'
import React, { FunctionComponent } from 'react'
import { usePanelStore } from '@/Stores/PanelStore'

interface Props {
  log: DDPLog
}

export const DDPLogMenu: FunctionComponent<Props> = ({ log }) => {
  const store = usePanelStore()

  return (
    <div className='menu invisible flex flex-row gap-2 group-hover:visible'>
      <Icon
        icon='eye-open'
        onClick={() => log.trace && store.setActiveStackTrace(log.trace)}
        style={{ cursor: 'pointer' }}
      />
      <Icon
        icon={
          store.bookmarkStore.bookmarkIds.includes(log.id)
            ? 'star'
            : 'star-empty'
        }
        onClick={() =>
          store.bookmarkStore.bookmarkIds.includes(log.id)
            ? store.bookmarkStore.remove(log)
            : store.bookmarkStore.add(log)
        }
        style={{ cursor: 'pointer' }}
      />
      {log.parsedContent?.msg === 'method' && (
        <Icon
          icon='play'
          onClick={() => {
            store.setSelectedTabId(PanelPage.DDP)

            Bridge.sendContentMessage({
              eventType: 'ddp-run-method',
              data: log.parsedContent,
            })
          }}
          style={{ cursor: 'pointer' }}
        />
      )}
    </div>
  )
}


================================================
FILE: src/Pages/Panel/DDP/DDPLogPreview.tsx
================================================
import { usePanelStore } from '@/Stores/PanelStore'
import { Icon, IconName, Tag, Tooltip } from '@blueprintjs/core'
import React, { FunctionComponent } from 'react'

const getTag = (icon: IconName, title: string) => (
  <Tooltip
    content={title}
    hoverOpenDelay={800}
    position='top'
    className='content-icon'
  >
    <Icon
      icon={icon}
      style={{
        color: '#8a9ba8',
      }}
      iconSize={12}
    />
  </Tooltip>
)

const getTypeTag = (filterType?: FilterType | null) => {
  switch (filterType) {
    case 'heartbeat': {
      return getTag('heart', 'Heartbeat')
    }
    case 'connection': {
      return getTag('globe-network', 'Connection')
    }
    case 'collection': {
      return getTag('database', 'Collection')
    }
    case 'subscription': {
      return getTag('feed-subscribed', 'Subscription')
    }
    case 'method': {
      return getTag('derive-column', 'Method')
    }
    default: {
      return getTag('warning-sign', 'Unknown')
    }
  }
}

export const DDPLogPreview: FunctionComponent<Partial<DDPLog>> = ({
  filterType,
  parsedContent,
  preview,
}) => {
  const store = usePanelStore()

  return (
    <>
      {getTypeTag(filterType)}
      <Tag
        interactive
        minimal
        onClick={() => {
          if (parsedContent) store.setActiveObject(parsedContent)
        }}
        className='content-preview'
        intent={parsedContent?.error ? 'danger' : 'none'}
      >
        <small>
          <code>{preview}</code>
        </small>
      </Tag>
    </>
  )
}


================================================
FILE: src/Pages/Panel/DDP/DDPStatus.tsx
================================================
import { Spinner, Tag, Tooltip } from '@blueprintjs/core'
import { isNumber } from 'lodash'
import { observer } from 'mobx-react-lite'
import React, { FormEvent, FunctionComponent, useCallback } from 'react'
import { usePanelStore } from '@/Stores/PanelStore'
import { StatusBar } from '@/Components/StatusBar'
import { DDPFilterMenu } from '@/Pages/Panel/DDP/DDPFilterMenu'
import { Position } from '@blueprintjs/core/lib/esm/common/position'
import { TextInput } from '@/Components/TextInput'
import { PopoverButton } from '@/Components/PopoverButton'
import { Button } from '@/Components/Button'
import prettyBytes from 'pretty-bytes'
import { Field } from '@/Components/Field'
import { StringUtils } from '@/Utils/StringUtils'
import { AppToaster } from '@/AppToaster'

export const DDPStatus: FunctionComponent = observer(() => {
  const store = usePanelStore()
  const { ddpStore, settingStore } = store

  const activeFilters = settingStore.activeFilters
  const setFilter = useCallback(
    (type, isEnabled) => settingStore.setFilter(type, isEnabled),
    [settingStore],
  )
  const collectionLength = ddpStore.collection.length
  const { inboundBytes, outboundBytes, isLoading, pagination } = ddpStore

  return (
    <StatusBar>
      <div className='left-group'>
        <PopoverButton
          icon='filter'
          height={28}
          content={
            <DDPFilterMenu
              setFilter={setFilter}
              activeFilters={activeFilters}
            />
          }
          position={Position.RIGHT_TOP}
        >
          Filter
        </PopoverButton>

        <TextInput
          icon='search'
          placeholder='Search...'
          onChange={(event: FormEvent<HTMLInputElement>) =>
            pagination.setSearch(event.currentTarget.value)
          }
        />

        <Field icon='eye-open'>{pagination.length}</Field>
      </div>

      <div className='right-group'>
        {isLoading && (
          <Field>
            <Spinner size={12} intent='warning' />
          </Field>
        )}

        {store.gitCommitHash ? (
          <Tooltip
            content='Git Commit Hash'
            hoverOpenDelay={800}
            position='top'
          >
            <Tag
              minimal
              interactive
              onClick={() => {
                StringUtils.toClipboard(store.gitCommitHash as string)
                AppToaster.show({
                  icon: 'tick',
                  message: 'Copied to Clipboard',
                  intent: 'success',
                  timeout: 1000,
                })
              }}
              style={{ marginRight: 4 }}
            >
              {store.gitCommitHash.slice(0, 8)}
            </Tag>
          </Tooltip>
        ) : null}

        {!!inboundBytes && (
          <Field icon='cloud-download'>{prettyBytes(inboundBytes)}</Field>
        )}

        {!!outboundBytes && (
          <Field icon='cloud-upload'>{prettyBytes(outboundBytes)}</Field>
        )}

        {isNumber(collectionLength) && (
          <Button
            intent='warning'
            onClick={() => ddpStore.clearLogs()}
            icon='inbox'
          >
            {collectionLength}
          </Button>
        )}
      </div>
    </StatusBar>
  )
})


================================================
FILE: src/Pages/Panel/DDP/FilterConstants.ts
================================================
export const FilterCriteria: FilterTypeMap<string[]> = {
  heartbeat: ['ping', 'pong'],
  subscription: ['sub', 'unsub', 'nosub', 'ready'],
  collection: ['added', 'removed', 'changed'],
  method: ['method', 'result', 'updated'],
  connection: ['connect', 'connected', 'failed'],
}

export const FilterCriteriaMap: {
  [key: string]: FilterType
} = Object.fromEntries(
  Object.entries(FilterCriteria).flatMap(([key, matchers]) =>
    matchers.map(matcher => [matcher, key]),
  ),
)

export const detectType = (content?: DDPLogContent) => {
  if (content && content.msg && content.msg in FilterCriteriaMap) {
    return FilterCriteriaMap[content.msg]
  }

  return null
}


================================================
FILE: src/Pages/Panel/DrawerJSON.tsx
================================================
import { ObjectTreerinator } from '@/Utils/ObjectTreerinator'
import { Button, Classes, Drawer } from '@blueprintjs/core'
import React, { FunctionComponent } from 'react'
import { StringUtils } from '@/Utils/StringUtils'
import { Popover2 } from '@blueprintjs/popover2'

interface Props {
  title: string | null
  viewableObject: ViewableObject
  onClose(): void
}

export const DrawerJSON: FunctionComponent<Props> = ({
  title,
  viewableObject,
  onClose,
}) => {
  return (
    <Drawer
      icon='document'
      title={title ?? 'JSON'}
      isOpen={!!viewableObject}
      onClose={onClose}
      size='72%'
    >
      <div className={Classes.DRAWER_BODY}>
        <div className={Classes.DIALOG_BODY}>
          {!!viewableObject && <ObjectTreerinator object={viewableObject} />}
        </div>
      </div>
      <div className={Classes.DRAWER_FOOTER}>
        <Popover2
          position='top'
          content={<div style={{ padding: '0.5rem' }}>Copied</div>}
        >
          <Button
            onClick={() =>
              StringUtils.toClipboard(JSON.stringify(viewableObject, null, 2))
            }
            icon='clipboard'
            minimal
          >
            Copy
          </Button>
        </Popover2>
      </div>
    </Drawer>
  )
}


================================================
FILE: src/Pages/Panel/DrawerStackTrace.tsx
================================================
import { Classes, Drawer } from '@blueprintjs/core'
import { Tooltip2 } from '@blueprintjs/popover2'
import classnames from 'classnames'
import React, { FunctionComponent } from 'react'

interface Props {
  activeStackTrace: StackTrace[] | null

  onClose(): void
}

export const DrawerStackTrace: FunctionComponent<Props> = ({
  activeStackTrace,
  onClose,
}) => (
  <Drawer
    icon='document'
    title='Stack Trace'
    isOpen={!!activeStackTrace}
    onClose={onClose}
    size='72%'
  >
    <div className={Classes.DRAWER_BODY}>
      <div className={classnames(Classes.DIALOG_BODY, 'mde-stack-trace')}>
        {activeStackTrace?.map((stack: StackTrace, index: number) => {
          const text = (
            <div>
              <em>{stack?.callee?.trim() || 'Anonymous'}</em>
            </div>
          )

          return (
            <pre key={index}>
              {stack?.url ? (
                <Tooltip2 content={stack.url.trim()}>
                  <a
                    href={stack.url.trim()}
                    target='_blank'
                    rel='noopener noreferrer'
                  >
                    {text}
                  </a>
                </Tooltip2>
              ) : (
                text
              )}
            </pre>
          )
        })}
      </div>
    </div>
  </Drawer>
)


================================================
FILE: src/Pages/Panel/HelpDrawer.tsx
================================================
import { Classes, Drawer, DrawerSize, Icon } from '@blueprintjs/core'
import React, { FunctionComponent } from 'react'
import { GridItem, PartnersGrid } from './PartnersGrid'
import AuthorLogo from '@/Assets/leonardoventurini.png'
import MeteorCloudLogo from '@/Assets/meteor-cloud-logo.png'

const people: GridItem[] = [
  {
    name: 'Leonardo Venturini',
    title: 'Senior Software Engineer',
    role: 'Author',
    email: 'leonardo@techster.tech',
    imageUrl: AuthorLogo,
    description:
      'If you need help with extension related issues or general Node.js or Meteor consulting',
    slack: 'https://meteor-community.slack.com/archives/DRKE6HDD5/',
    linkedin: 'https://www.linkedin.com/in/leonardo-venturini/',
    website: 'https://leonardoventurini.tech/',
  },
]

const orgs: GridItem[] = [
  {
    name: 'Galaxy',
    title: 'Organization',
    role: 'Partner',
    website: 'https://social.meteor.com/devtools-evolved/',
    imageUrl: MeteorCloudLogo,
    description:
      'If you want a full service cloud offering for deploying, hosting, and scaling your apps with zero DevOps',
  },
]

interface Props {
  isHelpDrawerVisible: boolean

  onClose(): void
}

const YEAR = new Date().getFullYear()

export const HelpDrawer: FunctionComponent<Props> = ({
  isHelpDrawerVisible,
  onClose,
}) => {
  return (
    <Drawer
      title={
        <div className='flex items-center gap-2'>
          <Icon icon='help' /> Help
        </div>
      }
      isOpen={isHelpDrawerVisible}
      onClose={onClose}
      size={DrawerSize.LARGE}
    >
      <div className={Classes.DRAWER_BODY}>
        <div className={Classes.DIALOG_BODY}>
          <div className='mb-4 w-full space-y-8 text-lg'>
            <div className='section'>
              <h2 className='section-title'>Extension</h2>
              <PartnersGrid items={people} />
            </div>

            <div className='section'>
              <h2 className='section-title'>Meteor & Development</h2>
              <PartnersGrid items={orgs} />
            </div>

            <div className='section'>
              <h2 className='section-title'>Basics</h2>
              <p>
                <em>Behold, the evolution of Meteor DevTools.</em>
              </p>
              <p>
                <a
                  href='https://github.com/leonardoventurini/meteor-devtools-evolved/blob/development/CHANGELOG.md'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  Change Log
                </a>
              </p>
              <p>
                The extension initializes with the page content, which means you
                have to refresh the page after installation, also it needs the
                devtools panel to be opened at least once in the current tab for
                any messages to be processed.
              </p>
              <p>
                Other than that you can just explore the extension at your
                leisure. It should be easy enough.
              </p>
            </div>

            <div className='section'>
              <h2 className='section-title'>Feedback</h2>
              <p>
                Any feedback you might have can be addressed directly at our{' '}
                <a
                  href='https://github.com/leonardoventurini/meteor-devtools-evolved/issues'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  GitHub Issues
                </a>{' '}
                page, that way we can discuss and transition into development
                more easily. You can also reach the author on the{' '}
                <a
                  href='https://join.slack.com/t/meteor-community/shared_invite/zt-a9lwcfb7-~UwR3Ng6whEqRxcP5rORZw'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  Meteor Community Slack
                </a>{' '}
                or the{' '}
                <a
                  href='https://forums.meteor.com/u/leonardoventurini'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  Meteor Forums
                </a>
                .
              </p>
              <p>
                Starring the project is the easiest way to support the work and
                be part of our community of{' '}
                <a
                  href='https://github.com/leonardoventurini/meteor-devtools-evolved/stargazers'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  stargazers
                </a>
                .
              </p>
              <p>Let&apos;s make Meteor great again.</p>
            </div>

            <div className='section'>
              <h2 className='section-title'>Firefox</h2>
              <p>
                The Firefox port of the extension was a contribution made by{' '}
                <a
                  href='https://github.com/nilooy'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  RF Niloy
                </a>
                . Thank you!
              </p>
            </div>

            <div className='section'>
              <h2 className='section-title'>License</h2>
              <p>The MIT License (MIT)</p>
              <p>
                Copyright (c) {YEAR}{' '}
                <a
                  href='https://leonardoventurini.tech'
                  target='_blank'
                  rel='noopener noreferrer'
                >
                  Leonardo Venturini
                </a>
              </p>
              <p>
                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:
              </p>
              <p>
                The above copyright notice and this permission notice shall be
                included in all copies or substantial portions of the Software.
              </p>
              <p>
                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.
              </p>
            </div>
          </div>
        </div>
      </div>
    </Drawer>
  )
}


================================================
FILE: src/Pages/Panel/Minimongo/Minimongo.tsx
================================================
import { MinimongoNavigator } from '@/Pages/Panel/Minimongo/MinimongoNavigator'
import { usePanelStore } from '@/Stores/PanelStore'
import { Hideable } from '@/Utils/Hideable'
import { observer } from 'mobx-react-lite'
import React, { FunctionComponent } from 'react'
import { MinimongoContainer } from '@/Pages/Panel/Minimongo/MinimongoContainer'
import styled from 'styled-components'
import { MinimongoStatus } from '@/Pages/Panel/Minimongo/MinimongoStatus'
import { Button } from '@/Components/Button'
import prettyBytes from 'pretty-bytes'

interface Props {
  isVisible: boolean
}

const Wrapper = styled.div`
  display: flex;
  flex-direction: row;
  height: 100%;

  .sidebar {
    display: flex;
    height: 100%;
    width: 222px;
    overflow-y: auto;
    font-size: 11px;
    font-family: monospace;

    nav {
      display: flex;
      flex: 1;
      flex-direction: column;
      width: 100%;

      button {
        flex: 0 0 20px;
        width: 100%;

        &.active {
          background: rgba(255, 255, 255, 0.15);
        }

        &:hover:not(.active) {
          background: rgba(255, 255, 255, 0.1);
        }
      }
    }
  }

  .container {
    height: 100%;
    min-width: 0;
    flex-grow: 1;
    flex-shrink: 1;

    .row {
      display: flex;
      align-items: center;
      padding: 5px 15px;

      & > * + * {
        margin-left: 8px;
      }
    }
  }
`

export const Minimongo: FunctionComponent<Props> = observer(({ isVisible }) => {
  const { minimongoStore } = usePanelStore()

  const isActiveCollectionMissing =
    minimongoStore.activeCollection &&
    !(minimongoStore.activeCollection in minimongoStore.collections)

  if (isActiveCollectionMissing) {
    minimongoStore.setActiveCollection(null)
  }

  return (
    <Hideable isVisible={isVisible}>
      <div className={'mde-content'}>
        <Wrapper>
          <div className='sidebar'>
            <nav>
              {minimongoStore.collectionNames.length > 0 &&
                minimongoStore.collectionNames.map(key => (
                  <Button
                    key={key}
                    active={minimongoStore.activeCollection === key}
                    onClick={() => minimongoStore.setActiveCollection(key)}
                    subtitle={`${
                      minimongoStore.getMetadata(key)?.collectionSizePretty
                    } (${minimongoStore.collections[key]?.length ?? 0})`}
                    title={key}
                  >
                    {key}
                  </Button>
                ))}

              <Button
                active={!minimongoStore.activeCollection}
                onClick={() => minimongoStore.setActiveCollection(null)}
                subtitle={`${prettyBytes(minimongoStore.totalSize)} (${
                  minimongoStore.totalDocuments
                })`}
              >
                All Documents
              </Button>
            </nav>
          </div>
          <MinimongoContainer isVisible={isVisible} />
        </Wrapper>
      </div>

      <MinimongoStatus />

      <MinimongoNavigator />
    </Hideable>
  )
})


================================================
FILE: src/Pages/Panel/Minimongo/MinimongoContainer.tsx
================================================
import React, { CSSProperties, FunctionComponent, useRef } from 'react'
import { areEqual, FixedSizeList } from 'react-window'
import { observer } from 'mobx-react-lite'
import { usePanelStore } from '@/Stores/PanelStore'
import { MinimongoRow } from '@/Pages/Panel/Minimongo/MinimongoRow'
import { useDimensions } from '@/Utils/Hooks/useDimensions'

interface Props {
  isVisible: boolean
}

export const MinimongoContainer: FunctionComponent<Props> = observer(
  ({ isVisible }) => {
    const contentRef = useRef<HTMLDivElement>(null)

    const store = usePanelStore()

    const { activeCollectionDocuments, activeCollection } = store.minimongoStore

    const { width, height } = useDimensions(contentRef, [isVisible])

    interface IRow {
      data: { items: IDocumentWrapper[] }
      index: number
      style: CSSProperties
    }

    const Row: FunctionComponent<any> = React.memo(
      ({ data, index, style }: IRow) => {
        const item = data.items![index]

        return (
          <MinimongoRow
            style={style}
            key={item.document._id}
            item={item}
            onClick={() => store.setActiveObject(item.document)}
            onCollectionClick={() =>
              store.minimongoStore.setActiveCollection(item.collectionName)
            }
            isAllVisible={!activeCollection}
          />
        )
      },
      areEqual,
    )

    return (
      <div className='container' ref={contentRef}>
        <FixedSizeList
          height={height}
          width={width}
          itemCount={activeCollectionDocuments.filtered.length}
          itemSize={28}
          itemData={{ items: activeCollectionDocuments.filtered }}
        >
          {Row}
        </FixedSizeList>
      </div>
    )
  },
)


================================================
FILE: src/Pages/Panel/Minimongo/MinimongoNavigator.tsx
================================================
import {
  Button,
  Classes,
  Dialog,
  InputGroup,
  Menu,
  MenuItem,
  NonIdealState,
} from '@blueprintjs/core'
import React, { FormEvent, FunctionComponent } from 'react'
import { usePanelStore } from '@/Stores/PanelStore'
import { observer } from 'mobx-react-lite'

export const MinimongoNavigator: FunctionComponent = observer(() => {
  const { minimongoStore } = usePanelStore()

  const setActiveCollection = (collectionName: string | null) => {
    minimongoStore.setActiveCollection(collectionName)
    minimongoStore.setNavigatorVisible(false)
  }

  return (
    <Dialog
      icon='database'
      onClose={() => {
        minimongoStore.setNavigatorVisible(false)
        minimongoStore.setSearch('')
      }}
      title='Collections'
      isOpen={minimongoStore.isNavigatorVisible}
    >
      <div
        className={Classes.DIALOG_BODY}
        style={{ height: '50vh', overflowY: 'scroll' }}
      >
        <Menu>
          {minimongoStore.filteredCollectionNames.length > 0 ? (
            minimongoStore.filteredCollectionNames.map(key => (
              <MenuItem
                key={key}
                icon='database'
                text={`${key} (${minimongoStore.collections[key]?.length ?? 0})`}
                active={minimongoStore.activeCollection === key}
                onClick={() => setActiveCollection(key)}
              />
            ))
          ) : (
            <div style={{ marginTop: 50, marginBottom: 50 }}>
              <NonIdealState icon='search' title='No Results' />
            </div>
          )}
        </Menu>
      </div>
      <div className={Classes.DIALOG_FOOTER}>
        <div style={{ display: 'flex' }}>
          <div style={{ flexGrow: 1, marginRight: 8 }}>
            <InputGroup
              leftIcon='search'
              placeholder='Search...'
              className={Classes.FILL}
              onChange={(event: FormEvent<HTMLInputElement>) =>
                minimongoStore.setSearch(event.currentTarget.value)
              }
            />
          </div>

          <Button
            icon='asterisk'
            onClick={() => setActiveCollection(null)}
            active={minimongoStore.activeCollection === null}
          >
            Everything
          </Button>
        </div>
      </div>
    </Dialog>
  )
})


================================================
FILE: src/Pages/Panel/Minimongo/MinimongoRow.tsx
================================================
import { StringUtils } from '@/Utils/StringUtils'
import { Tag } from '@blueprintjs/core'
import React, { CSSProperties, FunctionComponent } from 'react'
import styled from 'styled-components'
import { truncate } from '@/Styles/Mixins'

const Wrapper = styled.div`
  &,
  & code {
    font-family: monospace;
    font-size: 12px;
  }

  .collection {
    ${truncate};
    cursor: pointer;
    flex: 0 0 auto;
  }

  .preview {
    ${truncate};
    flex: 0 1 auto;
  }
`

interface Props {
  item: IDocumentWrapper
  style: CSSProperties
  onClick: () => void
  onCollectionClick: () => void
  isAllVisible: boolean
}

export const MinimongoRow: FunctionComponent<Props> = ({
  item,
  style,
  onClick,
  onCollectionClick,
  isAllVisible,
}) => {
  return (
    <Wrapper className='row' style={style}>
      {isAllVisible && (
        <Tag
          className='collection'
          style={{ cursor: 'pointer' }}
          minimal
          onClick={() => onCollectionClick()}
        >
          {item.collectionName}
        </Tag>
      )}
      <Tag className='preview' minimal interactive onClick={() => onClick()}>
        <code>{StringUtils.truncate(item._string, 256)}</code>
      </Tag>
    </Wrapper>
  )
}


================================================
FILE: src/Pages/Panel/Minimongo/MinimongoStatus.tsx
================================================
import React, { FormEvent, FunctionComponent } from 'react'
import { StatusBar } from '@/Components/StatusBar'
import { Button } from '@/Components/Button'
import { TextInput } from '@/Components/TextInput'
import { Field } from '@/Components/Field'
import { observer } from 'mobx-react-lite'
import { usePanelStore } from '@/Stores/PanelStore'

export const MinimongoStatus: FunctionComponent = observer(() => {
  const { minimongoStore } = usePanelStore()

  return (
    <StatusBar>
      <div className='left-group'>
        <Button
          icon={minimongoStore.activeCollection ? 'database' : 'asterisk'}
          onClick={() => minimongoStore.setNavigatorVisible(true)}
          disabled={minimongoStore.collectionNames.length === 0}
        >
          {minimongoStore.activeCollection || 'Everything'}
        </Button>

        {minimongoStore.activeCollection && (
          <Button
            icon='asterisk'
            onClick={() => minimongoStore.setActiveCollection(null)}
          >
            Clear
          </Button>
        )}

        <TextInput
          icon='search'
          placeholder='Search...'
          onChange={(event: FormEvent<HTMLInputElement>) =>
            minimongoStore.activeCollectionDocuments.pagination.setSearch(
              event.currentTarget.value,
            )
          }
        />

        <Field icon='eye-open'>
          {minimongoStore.activeCollectionDocuments.pagination.length}
        </Field>
      </div>
    </StatusBar>
  )
})


================================================
FILE: src/Pages/Panel/Navigation.tsx
================================================
import { PanelPage } from '@/Constants'
import React, { FunctionComponent, useEffect } from 'react'
import { usePanelStore } from '@/Stores/PanelStore'
import { observer } from 'mobx-react-lite'
import { Bridge, syncSubscriptions } from '@/Bridge'
import { IMenuItem, ITab, TabBar } from '@/Components/TabBar'
import { Tag } from '@blueprintjs/core'
import { isNumber } from 'lodash'
import { useAnalytics } from '@/Utils/Hooks/useAnalytics'
import { openTab } from '@/Utils/BackgroundEvents'

export const Navigation: FunctionComponent = observer(() => {
  const panelStore = usePanelStore()
  const analytics = useAnalytics()

  useEffect(() => {
    setTimeout(() => {
      panelStore.settingStore.updateRepositoryData()
    }, 2000)
  }, [])

  const { repositoryData } = panelStore.settingStore

  const tabs: ITab[] = [
    {
      key: PanelPage.DDP,
      content: 'DDP',
      icon: 'changes',
    },
    {
      key: PanelPage.BOOKMARKS,
      content: 'Bookmarks',
      icon: 'star',
    },
    {
      key: PanelPage.MINIMONGO,
      content: 'Minimongo',
      icon: 'database',
      handler: () => {
        // Fetch collection data from the page.
        Bridge.sendContentMessage({
          eventType: 'minimongo-get-collections',
          data: null,
        })
      },
    },
    {
      key: PanelPage.SUBSCRIPTIONS,
      content: 'Subscriptions',
      icon: 'feed-subscribed',
      handler: () => {
        syncSubscriptions()
      },
    },
    {
      key: PanelPage.PERFORMANCE,
      content: 'Performance',
      icon: 'lightning',
    },
  ]

  const menu: IMenuItem[] = [
    {
      key: 'help',
      icon: 'help',
      content: 'Help',
      shine: true,
      handler: () => {
        panelStore.setHelpDrawerVisible(true)
        analytics?.event('navigation', 'click', { label: 'partners' })
      },
    },
    {
      key: 'reload',
      icon: 'refresh',
      content: 'Reload',
      handler: () => location.reload(),
      shine: true,
    },
  ]

  if (repositoryData) {
    menu.unshift({
      key: 'feedback',
      icon: 'issue',
      content: <strong>Issues</strong>,
      handler: () => {
        openTab([...repositoryData.html_url, '/issues'])
        analytics?.event('navigation', 'click', { label: 'feedback' })
      },
      shine: true,
    })

    menu.unshift({
      key: 'star',
      icon: 'star',
      content: (
        <>
          <strong>Star</strong>
          {isNumber(repositoryData.stargazers_count) ? (
            <Tag minimal round style={{ marginLeft: '.5rem' }}>
              {repositoryData.stargazers_count}
            </Tag>
          ) : null}
        </>
      ),
      shine: true,
      handler: () => {
        openTab([...repositoryData.html_url, '/stargazers'])

        analytics?.event('navigation', 'click
Download .txt
gitextract_md1jik7s/

├── .babelrc
├── .claude/
│   └── settings.json
├── .editorconfig
├── .envrc
├── .eslintrc.js
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .serena/
│   ├── .gitignore
│   ├── memories/
│   │   ├── architecture_patterns.md
│   │   ├── code_style_and_conventions.md
│   │   ├── codebase_structure.md
│   │   ├── project_overview.md
│   │   ├── security_and_auditing.md
│   │   ├── suggested_commands.md
│   │   ├── task_completion_checklist.md
│   │   └── tech_stack.md
│   └── project.yml
├── .yarnrc.yml
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── devapp-2.0.0/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   ├── links.js
│   │   │   └── random.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Hello.jsx
│   │       └── Info.jsx
│   ├── package.json
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── devapp-2.2.0/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   ├── links.js
│   │   │   └── random.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Hello.jsx
│   │       └── Info.jsx
│   ├── package.json
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── devapp-2.2.4/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   ├── links.js
│   │   │   └── random.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Hello.jsx
│   │       └── Info.jsx
│   ├── package.json
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── devapp-3.4/
│   ├── .gitignore
│   ├── .meteor/
│   │   ├── .finished-upgraders
│   │   ├── .gitignore
│   │   ├── .id
│   │   ├── packages
│   │   ├── platforms
│   │   ├── release
│   │   └── versions
│   ├── .swcrc
│   ├── client/
│   │   ├── main.css
│   │   ├── main.html
│   │   └── main.jsx
│   ├── imports/
│   │   ├── api/
│   │   │   └── links.js
│   │   └── ui/
│   │       ├── App.jsx
│   │       ├── Counter.jsx
│   │       ├── Header.jsx
│   │       ├── Info.jsx
│   │       └── styles.css
│   ├── package.json
│   ├── rspack.config.js
│   ├── server/
│   │   └── main.js
│   └── tests/
│       └── main.js
├── eslint.config.mjs
├── extension/
│   ├── devtools-panel.html
│   ├── devtools.html
│   ├── manifest-v2.json
│   ├── manifest-v3.json
│   ├── options.html
│   └── popup.html
├── lint-staged.js
├── package.json
├── postcss.config.js
├── src/
│   ├── Analytics.ts
│   ├── App.tsx
│   ├── AppToaster.jsx
│   ├── Bridge.ts
│   ├── Browser/
│   │   ├── Background.ts
│   │   ├── Content.ts
│   │   ├── DevTools.ts
│   │   ├── Inject.ts
│   │   └── MeteorLibrary.ts
│   ├── Components/
│   │   ├── Button.tsx
│   │   ├── Field.tsx
│   │   ├── PopoverButton.tsx
│   │   ├── Separator.tsx
│   │   ├── StatusBar.tsx
│   │   ├── TabBar.tsx
│   │   └── TextInput.tsx
│   ├── Constants.ts
│   ├── Database/
│   │   └── PanelDatabase.ts
│   ├── Injectors/
│   │   ├── DDPInjector.ts
│   │   ├── MeteorAdapter.ts
│   │   └── MinimongoInjector.ts
│   ├── Log.ts
│   ├── Pages/
│   │   ├── Options.tsx
│   │   ├── Panel/
│   │   │   ├── Bookmarks/
│   │   │   │   ├── Bookmarks.tsx
│   │   │   │   └── BookmarksStatus.tsx
│   │   │   ├── DDP/
│   │   │   │   ├── DDP.tsx
│   │   │   │   ├── DDPContainer.tsx
│   │   │   │   ├── DDPFilterMenu.tsx
│   │   │   │   ├── DDPLog.tsx
│   │   │   │   ├── DDPLogDirection.tsx
│   │   │   │   ├── DDPLogMenu.tsx
│   │   │   │   ├── DDPLogPreview.tsx
│   │   │   │   ├── DDPStatus.tsx
│   │   │   │   └── FilterConstants.ts
│   │   │   ├── DrawerJSON.tsx
│   │   │   ├── DrawerStackTrace.tsx
│   │   │   ├── HelpDrawer.tsx
│   │   │   ├── Minimongo/
│   │   │   │   ├── Minimongo.tsx
│   │   │   │   ├── MinimongoContainer.tsx
│   │   │   │   ├── MinimongoNavigator.tsx
│   │   │   │   ├── MinimongoRow.tsx
│   │   │   │   └── MinimongoStatus.tsx
│   │   │   ├── Navigation.tsx
│   │   │   ├── PartnersGrid.tsx
│   │   │   ├── Performance/
│   │   │   │   └── Performance.tsx
│   │   │   └── Subscriptions/
│   │   │       └── Subscriptions.tsx
│   │   ├── Panel.tsx
│   │   └── Popup.tsx
│   ├── Stores/
│   │   ├── Common/
│   │   │   └── Searchable.ts
│   │   ├── Panel/
│   │   │   ├── BookmarkStore.ts
│   │   │   ├── DDPStore.ts
│   │   │   ├── MinimongoStore/
│   │   │   │   ├── CollectionStore.ts
│   │   │   │   └── index.ts
│   │   │   ├── PerformanceStore.ts
│   │   │   ├── SettingStore.ts
│   │   │   └── SubscriptionStore.ts
│   │   └── PanelStore.tsx
│   ├── Styles/
│   │   ├── App.scss
│   │   ├── Breakpoints.ts
│   │   ├── Constants.ts
│   │   ├── Mixins.ts
│   │   ├── Tailwind.css
│   │   └── _Utils.scss
│   ├── Utils/
│   │   ├── BackgroundEvents.ts
│   │   ├── Hideable.tsx
│   │   ├── Hooks/
│   │   │   ├── useAnalytics.ts
│   │   │   ├── useBreakpoints.ts
│   │   │   ├── useDimensions.ts
│   │   │   ├── useInterval.ts
│   │   │   └── useResize.ts
│   │   ├── JSONUtils.ts
│   │   ├── MessageFormatter.ts
│   │   ├── Numbers.ts
│   │   ├── ObjectTreerinator/
│   │   │   ├── ArrayNodeRenderer.tsx
│   │   │   ├── ArrayRenderer.tsx
│   │   │   ├── BooleanRenderer.tsx
│   │   │   ├── Collapsible.tsx
│   │   │   ├── NullRenderer.tsx
│   │   │   ├── NumberRenderer.tsx
│   │   │   ├── ObjectRenderer.tsx
│   │   │   ├── StringRenderer.tsx
│   │   │   └── index.tsx
│   │   ├── Objects.ts
│   │   ├── Pagination.ts
│   │   ├── StringUtils.ts
│   │   └── index.ts
│   └── index.d.ts
├── tailwind.config.js
├── tsconfig.json
└── webpack/
    ├── base.js
    ├── chrome.dev.js
    ├── chrome.prod.js
    ├── firefox.dev.js
    ├── firefox.prod.js
    └── utils.js
Download .txt
SYMBOL INDEX (200 symbols across 55 files)

FILE: devapp-2.0.0/server/main.js
  function insertLink (line 5) | function insertLink(title, url) {
  method echo (line 10) | echo(echo) {

FILE: devapp-2.2.0/server/main.js
  function insertLink (line 5) | function insertLink(title, url) {
  method echo (line 10) | echo(echo) {

FILE: devapp-2.2.4/server/main.js
  function insertLink (line 5) | function insertLink(title, url) {
  method echo (line 10) | echo(echo) {

FILE: devapp-3.4/server/main.js
  function insertLink (line 5) | async function insertLink({ title, url }) {
  method about (line 51) | about() {

FILE: src/Analytics.ts
  constant GA_HOST (line 5) | const GA_HOST = 'https://www.google-analytics.com'
  type UUID (line 7) | type UUID = string
  type RequestObject (line 9) | type RequestObject = {
  type EventOptions (line 15) | type EventOptions = {
  type EventParams (line 21) | type EventParams = {
  type PageViewParams (line 28) | type PageViewParams = {
  type ScreenParams (line 35) | type ScreenParams = {
  type TransactionOptions (line 43) | type TransactionOptions = {
  type TransactionParams (line 51) | type TransactionParams = {
  type TimingOptions (line 60) | type TimingOptions = {
  type TimingParams (line 69) | type TimingParams = {
  type AnalyticsOptions (line 81) | type AnalyticsOptions = {
  class Analytics (line 88) | class Analytics {
    method constructor (line 100) | constructor(trackingId, options: AnalyticsOptions = {}) {
    method set (line 112) | set(key: string, value = null) {
    method pageView (line 120) | pageView(
    method event (line 139) | event(category?: string, action?: string, options: EventOptions = {}) {
    method screen (line 150) | screen(
    method transaction (line 168) | transaction(transactionId: UUID, options: TransactionOptions = {}) {
    method social (line 181) | social(socialAction: string, socialNetwork: string, socialTarget: stri...
    method exception (line 187) | exception(description: string, fatal: number, clientId: UUID) {
    method timingTrk (line 193) | timingTrk(
    method send (line 224) | send(hitType: string, params: Record<string, any>) {

FILE: src/Bridge.ts
  method register (line 29) | register(eventType: EventType, handler: MessageHandler) {
  method handle (line 33) | handle(message: Message<any>) {
  method sendContentMessage (line 41) | sendContentMessage(message: Message<any>) {
  method chrome (line 54) | chrome() {
  method init (line 69) | init() {

FILE: src/Browser/Background.ts
  type Connection (line 3) | type Connection = Map<number, any>
  type Window (line 6) | interface Window {

FILE: src/Browser/Inject.ts
  constant PARENTHESIS_REGEX (line 16) | const PARENTHESIS_REGEX = /(\S*) \(([^)]+)\)/
  type MessageHandler (line 86) | type MessageHandler = (message: Message<any>) => void
  type Registration (line 87) | type Registration = {
  type IRegistry (line 92) | interface IRegistry {
  method register (line 103) | register(eventType: EventType, handler: MessageHandler) {
  method run (line 110) | run(message: IMessagePayload<any>) {
  function injectAll (line 122) | function injectAll() {

FILE: src/Components/Button.tsx
  type Props (line 110) | interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {

FILE: src/Components/Field.tsx
  type Props (line 23) | interface Props {

FILE: src/Components/PopoverButton.tsx
  type WrapperProps (line 7) | interface WrapperProps {
  type Props (line 18) | interface Props extends Popover2Props {

FILE: src/Components/Separator.tsx
  type WrapperProps (line 4) | interface WrapperProps {

FILE: src/Components/TabBar.tsx
  type ITab (line 51) | interface ITab {
  type IMenuItem (line 59) | interface IMenuItem {
  type Props (line 69) | interface Props {

FILE: src/Components/TextInput.tsx
  type Props (line 29) | interface Props extends InputHTMLAttributes<HTMLInputElement> {

FILE: src/Constants.ts
  constant DEFAULT_OFFSET (line 1) | const DEFAULT_OFFSET = 50
  constant DEVELOPMENT (line 3) | const DEVELOPMENT = process.env.MODE === 'development'
  type PanelPage (line 5) | enum PanelPage {

FILE: src/Database/PanelDatabase.ts
  class Database (line 4) | class Database extends Dexie {
    method constructor (line 8) | constructor() {
    method add (line 23) | add(log: DDPLog) {
    method get (line 31) | get(key: string) {
    method remove (line 35) | remove(key: string) {
    method getAll (line 39) | getAll() {
    method getSettings (line 43) | async getSettings() {
    method saveSettings (line 47) | async saveSettings(settings: ISettings) {

FILE: src/Injectors/DDPInjector.ts
  type MessageCallback (line 3) | type MessageCallback = (message: DDPLog) => void

FILE: src/Injectors/MinimongoInjector.ts
  function cloneDeep (line 5) | function cloneDeep(obj: any) {
  function isArray (line 9) | function isArray(obj: any) {

FILE: src/Pages/Panel/Bookmarks/Bookmarks.tsx
  type Props (line 8) | interface Props {

FILE: src/Pages/Panel/DDP/DDP.tsx
  type Props (line 8) | interface Props {
  constant DDP (line 12) | const DDP: FunctionComponent<Props> = observer(({ isVisible }) => {

FILE: src/Pages/Panel/DDP/DDPContainer.tsx
  type Props (line 10) | interface Props {

FILE: src/Pages/Panel/DDP/DDPFilterMenu.tsx
  type Props (line 6) | interface Props {

FILE: src/Pages/Panel/DDP/DDPLog.tsx
  type Props (line 11) | interface Props {

FILE: src/Pages/Panel/DDP/DDPLogDirection.tsx
  type Prop (line 4) | interface Prop {

FILE: src/Pages/Panel/DDP/DDPLogMenu.tsx
  type Props (line 7) | interface Props {

FILE: src/Pages/Panel/DrawerJSON.tsx
  type Props (line 7) | interface Props {

FILE: src/Pages/Panel/DrawerStackTrace.tsx
  type Props (line 6) | interface Props {

FILE: src/Pages/Panel/HelpDrawer.tsx
  type Props (line 34) | interface Props {
  constant YEAR (line 40) | const YEAR = new Date().getFullYear()

FILE: src/Pages/Panel/Minimongo/Minimongo.tsx
  type Props (line 12) | interface Props {

FILE: src/Pages/Panel/Minimongo/MinimongoContainer.tsx
  type Props (line 8) | interface Props {
  type IRow (line 22) | interface IRow {

FILE: src/Pages/Panel/Minimongo/MinimongoRow.tsx
  type Props (line 26) | interface Props {

FILE: src/Pages/Panel/PartnersGrid.tsx
  type GridItem (line 9) | type GridItem = {
  function PartnersGrid (line 24) | function PartnersGrid({ items, className = '' }) {

FILE: src/Pages/Panel/Performance/Performance.tsx
  type Props (line 10) | type Props = { isVisible: boolean }

FILE: src/Pages/Panel/Subscriptions/Subscriptions.tsx
  type Props (line 14) | interface Props {

FILE: src/Stores/Common/Searchable.ts
  type BufferCallback (line 6) | type BufferCallback<T> = ((buffer: T[]) => void) | null
  type FilterFunction (line 7) | type FilterFunction<T> = ((collection: T[], search: string) => T[]) | null
  method setCollection (line 25) | setCollection(collection: T[]) {
  method pushItem (line 29) | pushItem(log: T) {
  method _submitLogs (line 56) | _submitLogs() {
  method setLoadingState (line 76) | setLoadingState(isLoading: boolean) {
  method setCurrentPage (line 92) | setCurrentPage(currentPage: number) {
  method filtered (line 97) | get filtered() {
  method pagination (line 104) | get pagination() {
  method paginated (line 115) | get paginated() {

FILE: src/Stores/Panel/BookmarkStore.ts
  class BookmarkStore (line 6) | class BookmarkStore extends Searchable<Bookmark> {
    method constructor (line 7) | constructor() {
    method sync (line 14) | async sync() {
    method remove (line 26) | async remove(log: DDPLog) {
    method add (line 34) | async add(log: DDPLog) {
    method filterRegularExpression (line 58) | get filterRegularExpression() {

FILE: src/Stores/Panel/DDPStore.ts
  class DDPStore (line 8) | class DDPStore extends Searchable<DDPLog> {
    method constructor (line 13) | constructor() {
    method clearLogs (line 60) | clearLogs() {
    method filterRegularExpression (line 69) | get filterRegularExpression() {
    method subscriptionLogs (line 76) | get subscriptionLogs() {
    method getSubscriptionInit (line 83) | getSubscriptionInit(subscription) {
    method getSubscriptionReady (line 89) | getSubscriptionReady(subscription) {
    method getSubscriptionDuration (line 95) | getSubscriptionDuration(subscription) {
    method getSubscriptionMeta (line 109) | getSubscriptionMeta(subscription) {

FILE: src/Stores/Panel/MinimongoStore/CollectionStore.ts
  class CollectionStore (line 4) | class CollectionStore extends Searchable<IDocumentWrapper> {
    method constructor (line 5) | constructor() {

FILE: src/Stores/Panel/MinimongoStore/index.ts
  class MinimongoStore (line 9) | class MinimongoStore {
    method constructor (line 19) | constructor() {
    method totalDocuments (line 24) | get totalDocuments() {
    method collectionNames (line 32) | get collectionNames() {
    method filteredCollectionNames (line 37) | get filteredCollectionNames() {
    method totalSize (line 45) | get totalSize() {
    method getMetadata (line 53) | getMetadata(collectionName: string) {
    method computeCollectionSizes (line 58) | computeCollectionSizes() {
    method syncDocuments (line 73) | syncDocuments() {
    method setCollections (line 90) | setCollections(collections: RawCollections) {
    method setActiveCollection (line 103) | setActiveCollection(collection: string | null) {
    method setNavigatorVisible (line 115) | setNavigatorVisible(isVisible: boolean) {
    method wrapDocument (line 119) | static wrapDocument(

FILE: src/Stores/Panel/PerformanceStore.ts
  type AccCallData (line 5) | type AccCallData = {
  class PerformanceStore (line 16) | class PerformanceStore<T> {
    method constructor (line 17) | constructor() {
    method push (line 41) | push(data: CallData) {
    method clear (line 76) | clear() {

FILE: src/Stores/Panel/SettingStore.ts
  class SettingStore (line 13) | class SettingStore implements ISettings {
    method constructor (line 28) | constructor() {
    method setRepositoryData (line 59) | setRepositoryData(repositoryData: IGitHubRepository) {
    method updateRepositoryData (line 64) | updateRepositoryData() {
    method setFilter (line 86) | setFilter(type: FilterType, isEnabled: boolean) {

FILE: src/Stores/Panel/SubscriptionStore.ts
  class SubscriptionStore (line 5) | class SubscriptionStore extends Searchable<IMeteorSubscription> {
    method constructor (line 6) | constructor() {
    method subsWithMeta (line 19) | get subsWithMeta() {

FILE: src/Stores/PanelStore.tsx
  class PanelStoreConstructor (line 11) | class PanelStoreConstructor {
    method constructor (line 30) | constructor() {
    method syncSubscriptions (line 37) | syncSubscriptions(subscriptions: Record<MeteorID, IMeteorSubscription>) {
    method setActiveObject (line 42) | setActiveObject(viewableObject: ViewableObject, title: string | null =...
    method setActiveStackTrace (line 48) | setActiveStackTrace(trace: StackTrace[] | null) {
    method setSelectedTabId (line 53) | setSelectedTabId(selectedTabId: string) {
    method setHelpDrawerVisible (line 58) | setHelpDrawerVisible(isHelpDrawerVisible: boolean) {
    method getSubscriptionById (line 63) | getSubscriptionById(id: string) {
    method setGitCommitHash (line 70) | setGitCommitHash(hash: string) {

FILE: src/Styles/Breakpoints.ts
  type BreakpointLabel (line 4) | type BreakpointLabel = 'xs' | 'sm' | 'md' | 'lg' | 'xl'

FILE: src/Styles/Constants.ts
  constant MIN_LAYOUT_WIDTH (line 1) | const MIN_LAYOUT_WIDTH = 600
  constant NAVBAR_HEIGHT (line 2) | const NAVBAR_HEIGHT = 29
  constant STATUS_HEIGHT (line 3) | const STATUS_HEIGHT = 29
  constant BACKGROUND_COLOR (line 4) | const BACKGROUND_COLOR = '#30404d'

FILE: src/Utils/Hideable.tsx
  type Props (line 3) | interface Props {

FILE: src/Utils/Hooks/useBreakpoints.ts
  type Breakpoint (line 4) | type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'navigationCollapse'

FILE: src/Utils/MessageFormatter.ts
  constant MAX_CHARACTERS (line 4) | const MAX_CHARACTERS = 512
  method heartbeat (line 7) | heartbeat({ msg }: DDPLogContent) {
  method collection (line 11) | collection({ msg, collection }: DDPLogContent) {
  method connection (line 23) | connection({ msg, session }: DDPLogContent) {
  method subscription (line 27) | subscription({ msg, id, name, subs }: any) {
  method method (line 49) | method({ msg, method, result, error }: DDPLogContent) {

FILE: src/Utils/ObjectTreerinator/ArrayRenderer.tsx
  type Props (line 5) | interface Props {

FILE: src/Utils/ObjectTreerinator/Collapsible.tsx
  type Props (line 4) | interface Props {

FILE: src/Utils/ObjectTreerinator/ObjectRenderer.tsx
  type Props (line 4) | interface Props {

FILE: src/Utils/Objects.ts
  function omit (line 3) | function omit(object, keys) {
  function mapValues (line 13) | function mapValues(object, fn) {
  function flatten (line 21) | function flatten(array) {
  function compact (line 25) | function compact(array) {

FILE: src/Utils/Pagination.ts
  method setSearch (line 30) | setSearch(search: string) {
  method next (line 33) | next() {
  method prev (line 38) | prev() {

FILE: src/Utils/StringUtils.ts
  function getPrefixedClass (line 45) | function getPrefixedClass(className) {

FILE: src/index.d.ts
  type MeteorID (line 4) | type MeteorID = string
  type Window (line 6) | interface Window {
  type MessageSource (line 17) | type MessageSource = 'meteor-devtools-evolved'
  type EventType (line 18) | type EventType =
  type Message (line 28) | interface Message<T> {
  type IMessagePayload (line 33) | interface IMessagePayload<T> extends Message<T> {
  type StackTrace (line 37) | interface StackTrace {
  type DDPError (line 42) | interface DDPError {
  type DDPLogContent (line 50) | interface DDPLogContent {
  type DDPLog (line 62) | interface DDPLog {
  type Bookmark (line 79) | interface Bookmark {
  type FilterType (line 85) | type FilterType =
  type FilterTypeMap (line 92) | type FilterTypeMap<T> = { [key in FilterType]: T }
  type Pagination (line 94) | interface Pagination {
  type IDocument (line 116) | interface IDocument extends Record<string, any> {
  type MinimongoCollections (line 120) | type MinimongoCollections = Record<string, IDocumentWrapper[]>
  type RawCollections (line 121) | type RawCollections = Record<string, IDocument[]>
  type ViewableObject (line 123) | type ViewableObject = object | null
  type MessageHandler (line 125) | type MessageHandler = (message: Message<any>) => void
  type IDocumentWrapper (line 127) | interface IDocumentWrapper {
  type IGitHubRepository (line 134) | interface IGitHubRepository {
  type ISettings (line 238) | interface ISettings {
  type ConsoleType (line 244) | type ConsoleType = 'log' | 'info' | 'warn' | 'error'
  type IMeteorSubscription (line 246) | interface IMeteorSubscription {
  type ICollectionMetadata (line 254) | interface ICollectionMetadata {
  type CallData (line 261) | type CallData = {
Condensed preview — 210 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (263K chars).
[
  {
    "path": ".babelrc",
    "chars": 48,
    "preview": "{\n  \"presets\": [\"@babel/env\", \"@babel/react\"]\n}\n"
  },
  {
    "path": ".claude/settings.json",
    "chars": 1131,
    "preview": "{\n  \"enabledPlugins\": {\n    \"frontend-design@claude-plugins-official\": true,\n    \"context7@claude-plugins-official\": tru"
  },
  {
    "path": ".editorconfig",
    "chars": 201,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[*.{js, j"
  },
  {
    "path": ".envrc",
    "chars": 1142,
    "preview": "#!/usr/bin/env bash\nexport DEVTOOLS_HOME=\"$(git rev-parse --show-toplevel)\"\nexport MAC_CHROME=\"/Applications/Google Chro"
  },
  {
    "path": ".eslintrc.js",
    "chars": 390,
    "preview": "const { merge } = require('lodash')\n\nmodule.exports = merge(require('@tstt/eslint-config/index.js'), {\n  rules: {\n    'g"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 627,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - development\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  lint-and-audit:\n  "
  },
  {
    "path": ".gitignore",
    "chars": 172,
    "preview": ".DS_Store\n.idea\nnode_modules\nchrome/build\nfirefox/build\nchrome.pem\nfirefox.pem\nextension/chrome\nextension/firefox\nmongo-"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 76,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run lint-staged -s\n"
  },
  {
    "path": ".prettierignore",
    "chars": 88,
    "preview": "node_modules/\nextension/\ndevapp-3.4/\n.yarn/\nwebpack/\n*.lock\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": ".prettierrc.json",
    "chars": 230,
    "preview": "{\n  \"plugins\": [\"prettier-plugin-tailwindcss\"],\n  \"semi\": false,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"pri"
  },
  {
    "path": ".serena/.gitignore",
    "chars": 7,
    "preview": "/cache\n"
  },
  {
    "path": ".serena/memories/architecture_patterns.md",
    "chars": 4745,
    "preview": "# Architecture and Design Patterns\n\n## Browser Extension Architecture\n\n### Multi-Context Design\n\nThe extension operates "
  },
  {
    "path": ".serena/memories/code_style_and_conventions.md",
    "chars": 2213,
    "preview": "# Code Style and Conventions\n\n## ESLint Configuration\n\nThe project extends `@tstt/eslint-config` with custom overrides:\n"
  },
  {
    "path": ".serena/memories/codebase_structure.md",
    "chars": 2148,
    "preview": "# Codebase Structure\n\n## Root Directory\n\n```\nmeteor-devtools-evolved/\n├── src/                    # Main source code\n├──"
  },
  {
    "path": ".serena/memories/project_overview.md",
    "chars": 1112,
    "preview": "# Meteor DevTools Evolved - Project Overview\n\n## Purpose\n\nMeteor DevTools Evolved is a browser extension for Google Chro"
  },
  {
    "path": ".serena/memories/security_and_auditing.md",
    "chars": 2684,
    "preview": "# Security and Auditing\n\n## Audit Script\n\nThe project includes a security audit script that checks for high and critical"
  },
  {
    "path": ".serena/memories/suggested_commands.md",
    "chars": 2256,
    "preview": "# Suggested Commands\n\n## Initial Setup\n\n```bash\n# Install dependencies for both root and devapp\nyarn setup\n```\n\n## Devel"
  },
  {
    "path": ".serena/memories/task_completion_checklist.md",
    "chars": 2817,
    "preview": "# Task Completion Checklist\n\nWhen completing a coding task in this project, follow these steps:\n\n## 1. Pre-Commit Automa"
  },
  {
    "path": ".serena/memories/tech_stack.md",
    "chars": 1838,
    "preview": "# Tech Stack\n\n## Core Technologies\n\n- **TypeScript** 4.4.3 - Main programming language with ES6 target\n- **React** 17.0."
  },
  {
    "path": ".serena/project.yml",
    "chars": 7377,
    "preview": "# the name by which the project can be referenced within Serena\nproject_name: 'meteor-devtools-evolved'\n\n# list of langu"
  },
  {
    "path": ".yarnrc.yml",
    "chars": 25,
    "preview": "nodeLinker: node-modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5248,
    "preview": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\n> The dates refer to when it was mad"
  },
  {
    "path": "CLAUDE.md",
    "chars": 114,
    "preview": "IMPORTANT: The first thing you ever do is to start the project `meteor-devtools-evolved` in the Serena MCP server\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1388,
    "preview": "## Setting the Environment Up\n\n1. Install dependencies for `devapp-3.4` & `root` with `yarn`.\n\n```shell\nyarn setup\n```\n\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1085,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2020 Leonardo Venturini\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "README.md",
    "chars": 2704,
    "preview": "<div align=\"center\">\n\n<img src=\"https://media.giphy.com/media/Pt2yOXUALOhpB5dpiM/giphy.gif\" alt=\"Meteor Devtool Evolved "
  },
  {
    "path": "devapp-2.0.0/.gitignore",
    "chars": 14,
    "preview": "node_modules/\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/.finished-upgraders",
    "chars": 598,
    "preview": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should"
  },
  {
    "path": "devapp-2.0.0/.meteor/.gitignore",
    "chars": 6,
    "preview": "local\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/.id",
    "chars": 328,
    "preview": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this"
  },
  {
    "path": "devapp-2.0.0/.meteor/packages",
    "chars": 1294,
    "preview": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into you"
  },
  {
    "path": "devapp-2.0.0/.meteor/platforms",
    "chars": 15,
    "preview": "server\nbrowser\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/release",
    "chars": 11,
    "preview": "METEOR@2.0\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/versions",
    "chars": 1411,
    "preview": "allow-deny@1.1.0\nautoupdate@1.7.0\nbabel-compiler@7.6.2\nbabel-runtime@1.5.0\nbase64@1.0.12\nbinary-heap@1.0.11\nblaze-tools@"
  },
  {
    "path": "devapp-2.0.0/client/main.css",
    "chars": 53,
    "preview": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-2.0.0/client/main.html",
    "chars": 87,
    "preview": "<head>\n  <title>devapp</title>\n</head>\n\n<body>\n  <div id=\"react-target\"></div>\n</body>\n"
  },
  {
    "path": "devapp-2.0.0/client/main.jsx",
    "chars": 288,
    "preview": "import React from 'react'\nimport { Meteor } from 'meteor/meteor'\nimport { render } from 'react-dom'\nimport { App } from "
  },
  {
    "path": "devapp-2.0.0/imports/api/links.js",
    "chars": 99,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-2.0.0/imports/api/random.js",
    "chars": 101,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const RandomCollection = new Mongo.Collection('random')\n"
  },
  {
    "path": "devapp-2.0.0/imports/ui/App.jsx",
    "chars": 3409,
    "preview": "import React, { useEffect, useRef, useState } from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport "
  },
  {
    "path": "devapp-2.0.0/imports/ui/Hello.jsx",
    "chars": 322,
    "preview": "import React, { useState } from 'react'\n\nexport const Hello = () => {\n  const [counter, setCounter] = useState(0)\n\n  con"
  },
  {
    "path": "devapp-2.0.0/imports/ui/Info.jsx",
    "chars": 525,
    "preview": "import React from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/"
  },
  {
    "path": "devapp-2.0.0/package.json",
    "chars": 626,
    "preview": "{\n  \"name\": \"devapp-2.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once"
  },
  {
    "path": "devapp-2.0.0/server/main.js",
    "chars": 2202,
    "preview": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '../imports/api/links'\nimport { RandomCollection "
  },
  {
    "path": "devapp-2.0.0/tests/main.js",
    "chars": 494,
    "preview": "import assert from 'assert'\n\ndescribe('devapp-2.0.0', function () {\n  it('package.json has correct name', async function"
  },
  {
    "path": "devapp-2.2.0/.gitignore",
    "chars": 14,
    "preview": "node_modules/\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/.finished-upgraders",
    "chars": 598,
    "preview": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should"
  },
  {
    "path": "devapp-2.2.0/.meteor/.gitignore",
    "chars": 6,
    "preview": "local\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/.id",
    "chars": 328,
    "preview": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this"
  },
  {
    "path": "devapp-2.2.0/.meteor/packages",
    "chars": 1294,
    "preview": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into you"
  },
  {
    "path": "devapp-2.2.0/.meteor/platforms",
    "chars": 15,
    "preview": "server\nbrowser\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/release",
    "chars": 11,
    "preview": "METEOR@2.2\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/versions",
    "chars": 1411,
    "preview": "allow-deny@1.1.0\nautoupdate@1.7.0\nbabel-compiler@7.6.2\nbabel-runtime@1.5.0\nbase64@1.0.12\nbinary-heap@1.0.11\nblaze-tools@"
  },
  {
    "path": "devapp-2.2.0/client/main.css",
    "chars": 53,
    "preview": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-2.2.0/client/main.html",
    "chars": 87,
    "preview": "<head>\n  <title>devapp</title>\n</head>\n\n<body>\n  <div id=\"react-target\"></div>\n</body>\n"
  },
  {
    "path": "devapp-2.2.0/client/main.jsx",
    "chars": 288,
    "preview": "import React from 'react'\nimport { Meteor } from 'meteor/meteor'\nimport { render } from 'react-dom'\nimport { App } from "
  },
  {
    "path": "devapp-2.2.0/imports/api/links.js",
    "chars": 99,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-2.2.0/imports/api/random.js",
    "chars": 101,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const RandomCollection = new Mongo.Collection('random')\n"
  },
  {
    "path": "devapp-2.2.0/imports/ui/App.jsx",
    "chars": 3409,
    "preview": "import React, { useEffect, useRef, useState } from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport "
  },
  {
    "path": "devapp-2.2.0/imports/ui/Hello.jsx",
    "chars": 322,
    "preview": "import React, { useState } from 'react'\n\nexport const Hello = () => {\n  const [counter, setCounter] = useState(0)\n\n  con"
  },
  {
    "path": "devapp-2.2.0/imports/ui/Info.jsx",
    "chars": 508,
    "preview": "import React from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/"
  },
  {
    "path": "devapp-2.2.0/package.json",
    "chars": 626,
    "preview": "{\n  \"name\": \"devapp-2.2.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once"
  },
  {
    "path": "devapp-2.2.0/server/main.js",
    "chars": 2202,
    "preview": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '../imports/api/links'\nimport { RandomCollection "
  },
  {
    "path": "devapp-2.2.0/tests/main.js",
    "chars": 494,
    "preview": "import assert from 'assert'\n\ndescribe('devapp-2.2.0', function () {\n  it('package.json has correct name', async function"
  },
  {
    "path": "devapp-2.2.4/.gitignore",
    "chars": 14,
    "preview": "node_modules/\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/.finished-upgraders",
    "chars": 598,
    "preview": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should"
  },
  {
    "path": "devapp-2.2.4/.meteor/.gitignore",
    "chars": 6,
    "preview": "local\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/.id",
    "chars": 327,
    "preview": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this"
  },
  {
    "path": "devapp-2.2.4/.meteor/packages",
    "chars": 1294,
    "preview": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into you"
  },
  {
    "path": "devapp-2.2.4/.meteor/platforms",
    "chars": 15,
    "preview": "server\nbrowser\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/release",
    "chars": 13,
    "preview": "METEOR@2.2.4\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/versions",
    "chars": 1411,
    "preview": "allow-deny@1.1.0\nautoupdate@1.7.0\nbabel-compiler@7.7.0\nbabel-runtime@1.5.0\nbase64@1.0.12\nbinary-heap@1.0.11\nblaze-tools@"
  },
  {
    "path": "devapp-2.2.4/client/main.css",
    "chars": 53,
    "preview": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-2.2.4/client/main.html",
    "chars": 87,
    "preview": "<head>\n  <title>devapp</title>\n</head>\n\n<body>\n  <div id=\"react-target\"></div>\n</body>\n"
  },
  {
    "path": "devapp-2.2.4/client/main.jsx",
    "chars": 288,
    "preview": "import React from 'react'\nimport { Meteor } from 'meteor/meteor'\nimport { render } from 'react-dom'\nimport { App } from "
  },
  {
    "path": "devapp-2.2.4/imports/api/links.js",
    "chars": 99,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-2.2.4/imports/api/random.js",
    "chars": 101,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const RandomCollection = new Mongo.Collection('random')\n"
  },
  {
    "path": "devapp-2.2.4/imports/ui/App.jsx",
    "chars": 3409,
    "preview": "import React, { useEffect, useRef, useState } from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport "
  },
  {
    "path": "devapp-2.2.4/imports/ui/Hello.jsx",
    "chars": 322,
    "preview": "import React, { useState } from 'react'\n\nexport const Hello = () => {\n  const [counter, setCounter] = useState(0)\n\n  con"
  },
  {
    "path": "devapp-2.2.4/imports/ui/Info.jsx",
    "chars": 525,
    "preview": "import React from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/"
  },
  {
    "path": "devapp-2.2.4/package.json",
    "chars": 626,
    "preview": "{\n  \"name\": \"devapp-2.2.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once"
  },
  {
    "path": "devapp-2.2.4/server/main.js",
    "chars": 2202,
    "preview": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '../imports/api/links'\nimport { RandomCollection "
  },
  {
    "path": "devapp-2.2.4/tests/main.js",
    "chars": 494,
    "preview": "import assert from 'assert'\n\ndescribe('devapp-2.2.4', function () {\n  it('package.json has correct name', async function"
  },
  {
    "path": "devapp-3.4/.gitignore",
    "chars": 110,
    "preview": "node_modules/\n\n# Meteor Modern-Tools build context directories\n_build\n*/build-assets\n*/build-chunks\n.rsdoctor\n"
  },
  {
    "path": "devapp-3.4/.meteor/.finished-upgraders",
    "chars": 598,
    "preview": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should"
  },
  {
    "path": "devapp-3.4/.meteor/.gitignore",
    "chars": 6,
    "preview": "local\n"
  },
  {
    "path": "devapp-3.4/.meteor/.id",
    "chars": 327,
    "preview": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this"
  },
  {
    "path": "devapp-3.4/.meteor/packages",
    "chars": 1309,
    "preview": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into you"
  },
  {
    "path": "devapp-3.4/.meteor/platforms",
    "chars": 15,
    "preview": "server\nbrowser\n"
  },
  {
    "path": "devapp-3.4/.meteor/release",
    "chars": 11,
    "preview": "METEOR@3.4\n"
  },
  {
    "path": "devapp-3.4/.meteor/versions",
    "chars": 1348,
    "preview": "allow-deny@2.1.0\nautoupdate@2.0.1\nbabel-compiler@7.13.0\nbabel-runtime@1.5.2\nbase64@1.0.13\nbinary-heap@1.0.12\nboilerplate"
  },
  {
    "path": "devapp-3.4/.swcrc",
    "chars": 100,
    "preview": "{\n  \"jsc\": {\n    \"transform\": {\n      \"react\": {\n        \"runtime\": \"automatic\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "devapp-3.4/client/main.css",
    "chars": 53,
    "preview": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-3.4/client/main.html",
    "chars": 292,
    "preview": "<head>\n  <title>devapp-3.4</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <link\n  "
  },
  {
    "path": "devapp-3.4/client/main.jsx",
    "chars": 302,
    "preview": "import { createRoot } from 'react-dom/client'\nimport { Meteor } from 'meteor/meteor'\nimport { App } from '/imports/ui/Ap"
  },
  {
    "path": "devapp-3.4/imports/api/links.js",
    "chars": 99,
    "preview": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-3.4/imports/ui/App.jsx",
    "chars": 264,
    "preview": "import { Counter } from './Counter.jsx'\nimport { Header } from './Header.jsx'\nimport { Info } from './Info.jsx'\n\nexport "
  },
  {
    "path": "devapp-3.4/imports/ui/Counter.jsx",
    "chars": 586,
    "preview": "import { useState } from 'react'\n\nexport const Counter = () => {\n  const [counter, setCounter] = useState(0)\n\n  const in"
  },
  {
    "path": "devapp-3.4/imports/ui/Header.jsx",
    "chars": 339,
    "preview": "import MeteorLogo from './meteor-logo.svg'\n\nexport const Header = () => {\n  return (\n    <div className='header'>\n      "
  },
  {
    "path": "devapp-3.4/imports/ui/Info.jsx",
    "chars": 863,
    "preview": "import { useFind, useSubscribe } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/links'\n\nexport "
  },
  {
    "path": "devapp-3.4/imports/ui/styles.css",
    "chars": 5839,
    "preview": "/* this file is imported in client/main.jsx */\n\n:root {\n  /* Colors */\n  --color-background: hsl(210, 20%, 98%);\n  --col"
  },
  {
    "path": "devapp-3.4/package.json",
    "chars": 944,
    "preview": "{\n  \"name\": \"devapp-3.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once -"
  },
  {
    "path": "devapp-3.4/rspack.config.js",
    "chars": 649,
    "preview": "const { defineConfig } = require('@meteorjs/rspack')\n\n/**\n * Rspack configuration for Meteor projects.\n *\n * Provides ty"
  },
  {
    "path": "devapp-3.4/server/main.js",
    "chars": 1421,
    "preview": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '/imports/api/links'\nimport { Random } from 'mete"
  },
  {
    "path": "devapp-3.4/tests/main.js",
    "chars": 490,
    "preview": "import assert from 'assert'\n\ndescribe('devapp-3.4', function () {\n  it('package.json has correct name', async function ("
  },
  {
    "path": "eslint.config.mjs",
    "chars": 3704,
    "preview": "import js from '@eslint/js'\nimport typescript from '@typescript-eslint/eslint-plugin'\nimport typescriptParser from '@typ"
  },
  {
    "path": "extension/devtools-panel.html",
    "chars": 383,
    "preview": "<!doctype html>\n<html data-theme=\"corporate\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n    "
  },
  {
    "path": "extension/devtools.html",
    "chars": 442,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"wi"
  },
  {
    "path": "extension/manifest-v2.json",
    "chars": 887,
    "preview": "{\n  \"manifest_version\": 2,\n  \"name\": \"Meteor DevTools Evolved\",\n  \"description\": \"The Meteor framework development tool "
  },
  {
    "path": "extension/manifest-v3.json",
    "chars": 991,
    "preview": "{\n  \"manifest_version\": 3,\n  \"name\": \"Meteor DevTools Evolved\",\n  \"description\": \"The Meteor framework development tool "
  },
  {
    "path": "extension/options.html",
    "chars": 364,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device"
  },
  {
    "path": "extension/popup.html",
    "chars": 370,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"wi"
  },
  {
    "path": "lint-staged.js",
    "chars": 241,
    "preview": "module.exports = {\n  '*.{js,jsx,ts,tsx}': [\n    'eslint',\n    'react-scripts test --bail --watchAll=false --findRelatedT"
  },
  {
    "path": "package.json",
    "chars": 4340,
    "preview": "{\n  \"name\": \"meteor-devtools-evolved\",\n  \"version\": \"1.8.1\",\n  \"description\": \"Meteor DevTools Evolved\",\n  \"repository\":"
  },
  {
    "path": "postcss.config.js",
    "chars": 60,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n  },\n}\n"
  },
  {
    "path": "src/Analytics.ts",
    "chars": 6081,
    "preview": "import { exists } from './Utils'\nimport { v4 as uuid } from 'uuid'\nimport { isString } from './Utils/StringUtils'\n\nconst"
  },
  {
    "path": "src/App.tsx",
    "chars": 657,
    "preview": "import { FocusStyleManager } from '@blueprintjs/core'\nimport React from 'react'\nimport { render } from 'react-dom'\nimpor"
  },
  {
    "path": "src/AppToaster.jsx",
    "chars": 155,
    "preview": "import { Position, Toaster } from '@blueprintjs/core'\n\nexport const AppToaster = Toaster.create({\n  className: 'app-toas"
  },
  {
    "path": "src/Bridge.ts",
    "chars": 3260,
    "preview": "import { detectType } from '@/Pages/Panel/DDP/FilterConstants'\nimport prettyBytes from 'pretty-bytes'\nimport { PanelStor"
  },
  {
    "path": "src/Browser/Background.ts",
    "chars": 3274,
    "preview": "import browser from 'webextension-polyfill'\n\ntype Connection = Map<number, any>\n\ndeclare global {\n  interface Window {\n "
  },
  {
    "path": "src/Browser/Content.ts",
    "chars": 746,
    "preview": "import browser from 'webextension-polyfill'\n\nconst messageHandler = (event: MessageEvent) => {\n  // Only accept messages"
  },
  {
    "path": "src/Browser/DevTools.ts",
    "chars": 234,
    "preview": "import browser from 'webextension-polyfill'\nimport { checkFirefoxBrowser } from '@/Utils'\n\nconst isFirefox = checkFirefo"
  },
  {
    "path": "src/Browser/Inject.ts",
    "chars": 3707,
    "preview": "import { DDPInjector } from '@/Injectors/DDPInjector'\nimport {\n  MinimongoInjector,\n  updateCollections,\n} from '@/Injec"
  },
  {
    "path": "src/Browser/MeteorLibrary.ts",
    "chars": 319,
    "preview": "import { JSONUtils } from '@/Utils/JSONUtils'\nimport { mapValues, omit } from '@/Utils/Objects'\n\nexport const getSubscri"
  },
  {
    "path": "src/Components/Button.tsx",
    "chars": 3935,
    "preview": "import React, { ButtonHTMLAttributes, FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { I"
  },
  {
    "path": "src/Components/Field.tsx",
    "chars": 938,
    "preview": "import React, { FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { centerItems } from '@/S"
  },
  {
    "path": "src/Components/PopoverButton.tsx",
    "chars": 795,
    "preview": "import React, { FunctionComponent } from 'react'\nimport { IconName } from '@blueprintjs/core'\nimport { Button } from '@/"
  },
  {
    "path": "src/Components/Separator.tsx",
    "chars": 486,
    "preview": "import React, { FunctionComponent } from 'react'\nimport styled from 'styled-components'\n\ninterface WrapperProps {\n  hori"
  },
  {
    "path": "src/Components/StatusBar.tsx",
    "chars": 835,
    "preview": "import React, { FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { NAVBAR_HEIGHT } from '@"
  },
  {
    "path": "src/Components/TabBar.tsx",
    "chars": 3027,
    "preview": "import React, { FunctionComponent, useState } from 'react'\nimport styled from 'styled-components'\nimport { IconName, Men"
  },
  {
    "path": "src/Components/TextInput.tsx",
    "chars": 805,
    "preview": "import React, { FunctionComponent, InputHTMLAttributes } from 'react'\nimport styled from 'styled-components'\nimport { Ic"
  },
  {
    "path": "src/Constants.ts",
    "chars": 258,
    "preview": "export const DEFAULT_OFFSET = 50\n\nexport const DEVELOPMENT = process.env.MODE === 'development'\n\nexport enum PanelPage {"
  },
  {
    "path": "src/Database/PanelDatabase.ts",
    "chars": 1114,
    "preview": "import Dexie from 'dexie'\nimport { toJS } from 'mobx'\n\nclass Database extends Dexie {\n  bookmarks: Dexie.Table<Bookmark,"
  },
  {
    "path": "src/Injectors/DDPInjector.ts",
    "chars": 874,
    "preview": "import { sendLogMessage } from '@/Browser/Inject'\n\ntype MessageCallback = (message: DDPLog) => void\n\nconst generateId = "
  },
  {
    "path": "src/Injectors/MeteorAdapter.ts",
    "chars": 1391,
    "preview": "import { Registry, sendMessage } from '@/Browser/Inject'\nimport { getSubscriptions } from '@/Browser/MeteorLibrary'\nimpo"
  },
  {
    "path": "src/Injectors/MinimongoInjector.ts",
    "chars": 2324,
    "preview": "import { warning } from '@/Log'\nimport { Registry, sendMessage } from '@/Browser/Inject'\nimport throttle from 'lodash.th"
  },
  {
    "path": "src/Log.ts",
    "chars": 151,
    "preview": "export const warning = (message: string) => {\n  console.log(\n    '%c'.concat('Meteor DevTools Evolved: ').concat(message"
  },
  {
    "path": "src/Pages/Options.tsx",
    "chars": 161,
    "preview": "import React, { FunctionComponent } from 'react'\n\nexport const Options: FunctionComponent = () => {\n  return (\n    <div>"
  },
  {
    "path": "src/Pages/Panel/Bookmarks/Bookmarks.tsx",
    "chars": 669,
    "preview": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from"
  },
  {
    "path": "src/Pages/Panel/Bookmarks/BookmarksStatus.tsx",
    "chars": 1860,
    "preview": "import { observer } from 'mobx-react-lite'\nimport React, { FormEvent, FunctionComponent, useCallback } from 'react'\n\nimp"
  },
  {
    "path": "src/Pages/Panel/DDP/DDP.tsx",
    "chars": 630,
    "preview": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPContainer.tsx",
    "chars": 1594,
    "preview": "import React, { FunctionComponent, useRef } from 'react'\nimport { DDPLog } from '@/Pages/Panel/DDP/DDPLog'\nimport { Fixe"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPFilterMenu.tsx",
    "chars": 861,
    "preview": "import { Switch } from '@blueprintjs/core'\nimport { observer } from 'mobx-react-lite'\nimport React, { FormEvent, Functio"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLog.tsx",
    "chars": 2403,
    "preview": "import { Tag, Tooltip } from '@blueprintjs/core'\nimport classnames from 'classnames'\nimport React, { CSSProperties, Func"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLogDirection.tsx",
    "chars": 588,
    "preview": "import { Icon } from '@blueprintjs/core'\nimport React, { FunctionComponent } from 'react'\n\ninterface Prop {\n  isOutbound"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLogMenu.tsx",
    "chars": 1358,
    "preview": "import { Icon } from '@blueprintjs/core'\nimport { PanelPage } from '@/Constants'\nimport { Bridge } from '@/Bridge'\nimpor"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLogPreview.tsx",
    "chars": 1541,
    "preview": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Icon, IconName, Tag, Tooltip } from '@blueprintjs/core'\nimp"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPStatus.tsx",
    "chars": 3264,
    "preview": "import { Spinner, Tag, Tooltip } from '@blueprintjs/core'\nimport { isNumber } from 'lodash'\nimport { observer } from 'mo"
  },
  {
    "path": "src/Pages/Panel/DDP/FilterConstants.ts",
    "chars": 672,
    "preview": "export const FilterCriteria: FilterTypeMap<string[]> = {\n  heartbeat: ['ping', 'pong'],\n  subscription: ['sub', 'unsub',"
  },
  {
    "path": "src/Pages/Panel/DrawerJSON.tsx",
    "chars": 1273,
    "preview": "import { ObjectTreerinator } from '@/Utils/ObjectTreerinator'\nimport { Button, Classes, Drawer } from '@blueprintjs/core"
  },
  {
    "path": "src/Pages/Panel/DrawerStackTrace.tsx",
    "chars": 1336,
    "preview": "import { Classes, Drawer } from '@blueprintjs/core'\nimport { Tooltip2 } from '@blueprintjs/popover2'\nimport classnames f"
  },
  {
    "path": "src/Pages/Panel/HelpDrawer.tsx",
    "chars": 7229,
    "preview": "import { Classes, Drawer, DrawerSize, Icon } from '@blueprintjs/core'\nimport React, { FunctionComponent } from 'react'\ni"
  },
  {
    "path": "src/Pages/Panel/Minimongo/Minimongo.tsx",
    "chars": 3111,
    "preview": "import { MinimongoNavigator } from '@/Pages/Panel/Minimongo/MinimongoNavigator'\nimport { usePanelStore } from '@/Stores/"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoContainer.tsx",
    "chars": 1766,
    "preview": "import React, { CSSProperties, FunctionComponent, useRef } from 'react'\nimport { areEqual, FixedSizeList } from 'react-w"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoNavigator.tsx",
    "chars": 2313,
    "preview": "import {\n  Button,\n  Classes,\n  Dialog,\n  InputGroup,\n  Menu,\n  MenuItem,\n  NonIdealState,\n} from '@blueprintjs/core'\nim"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoRow.tsx",
    "chars": 1219,
    "preview": "import { StringUtils } from '@/Utils/StringUtils'\nimport { Tag } from '@blueprintjs/core'\nimport React, { CSSProperties,"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoStatus.tsx",
    "chars": 1504,
    "preview": "import React, { FormEvent, FunctionComponent } from 'react'\nimport { StatusBar } from '@/Components/StatusBar'\nimport { "
  },
  {
    "path": "src/Pages/Panel/Navigation.tsx",
    "chars": 3372,
    "preview": "import { PanelPage } from '@/Constants'\nimport React, { FunctionComponent, useEffect } from 'react'\nimport { usePanelSto"
  },
  {
    "path": "src/Pages/Panel/PartnersGrid.tsx",
    "chars": 5256,
    "preview": "import React from 'react'\nimport {\n  ChatBubbleLeftIcon,\n  EnvelopeIcon,\n  LinkIcon,\n} from '@heroicons/react/20/solid'\n"
  },
  {
    "path": "src/Pages/Panel/Performance/Performance.tsx",
    "chars": 2500,
    "preview": "import React, { PropsWithChildren } from 'react'\nimport { Hideable } from '@/Utils/Hideable'\nimport { HTMLTable, Tag } f"
  },
  {
    "path": "src/Pages/Panel/Subscriptions/Subscriptions.tsx",
    "chars": 4008,
    "preview": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from"
  },
  {
    "path": "src/Pages/Panel.tsx",
    "chars": 2877,
    "preview": "import { PanelStoreProvider, usePanelStore } from '@/Stores/PanelStore'\nimport { observer } from 'mobx-react-lite'\nimpor"
  },
  {
    "path": "src/Pages/Popup.tsx",
    "chars": 136,
    "preview": "import React, { FunctionComponent } from 'react'\n\nexport const Popup: FunctionComponent = () => (\n  <div>\n    <h1>Popup<"
  },
  {
    "path": "src/Stores/Common/Searchable.ts",
    "chars": 2379,
    "preview": "import { DEFAULT_OFFSET } from '@/Constants'\nimport { calculatePagination } from '@/Utils/Pagination'\nimport debounce fr"
  },
  {
    "path": "src/Stores/Panel/BookmarkStore.ts",
    "chars": 1566,
    "preview": "import { PanelDatabase } from '@/Database/PanelDatabase'\nimport { action, computed, makeObservable, observable, runInAct"
  },
  {
    "path": "src/Stores/Panel/DDPStore.ts",
    "chars": 2890,
    "preview": "import debounce from 'lodash.debounce'\nimport { action, computed, makeObservable, observable, runInAction } from 'mobx'\n"
  },
  {
    "path": "src/Stores/Panel/MinimongoStore/CollectionStore.ts",
    "chars": 441,
    "preview": "import { Searchable } from '@/Stores/Common/Searchable'\nimport { makeObservable } from 'mobx'\n\nexport class CollectionSt"
  },
  {
    "path": "src/Stores/Panel/MinimongoStore/index.ts",
    "chars": 3246,
    "preview": "import debounce from 'lodash.debounce'\nimport { action, computed, makeObservable, observable } from 'mobx'\nimport { Coll"
  },
  {
    "path": "src/Stores/Panel/PerformanceStore.ts",
    "chars": 1699,
    "preview": "import { action, makeObservable, observable } from 'mobx'\nimport sortBy from 'lodash.sortby'\nimport debounce from 'lodas"
  },
  {
    "path": "src/Stores/Panel/SettingStore.ts",
    "chars": 2212,
    "preview": "import {\n  action,\n  makeObservable,\n  observable,\n  reaction,\n  runInAction,\n  toJS,\n} from 'mobx'\nimport { PanelDataba"
  },
  {
    "path": "src/Stores/Panel/SubscriptionStore.ts",
    "chars": 664,
    "preview": "import { Searchable } from '@/Stores/Common/Searchable'\nimport { computed, makeObservable } from 'mobx'\nimport { PanelSt"
  },
  {
    "path": "src/Stores/PanelStore.tsx",
    "chars": 2633,
    "preview": "import { action, makeObservable, observable, toJS } from 'mobx'\nimport React, { createContext, FunctionComponent, useCon"
  },
  {
    "path": "src/Styles/App.scss",
    "chars": 895,
    "preview": "@import '~normalize.css/normalize.css';\n@import '~@blueprintjs/core/lib/css/blueprint.css';\n@import '~@blueprintjs/popov"
  },
  {
    "path": "src/Styles/Breakpoints.ts",
    "chars": 474,
    "preview": "import { mapValues } from '@/Utils/Objects'\nimport { css, FlattenSimpleInterpolation } from 'styled-components'\n\ntype Br"
  },
  {
    "path": "src/Styles/Constants.ts",
    "chars": 142,
    "preview": "export const MIN_LAYOUT_WIDTH = 600\nexport const NAVBAR_HEIGHT = 29\nexport const STATUS_HEIGHT = 29\nexport const BACKGRO"
  },
  {
    "path": "src/Styles/Mixins.ts",
    "chars": 252,
    "preview": "import { css } from 'styled-components'\n\nexport const truncate = () => css`\n  white-space: nowrap;\n  overflow: hidden;\n "
  },
  {
    "path": "src/Styles/Tailwind.css",
    "chars": 164,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n.section {\n  @apply text-base;\n}\n\n.section-title {\n  @apply "
  },
  {
    "path": "src/Styles/_Utils.scss",
    "chars": 90,
    "preview": "@mixin truncate {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n"
  },
  {
    "path": "src/Utils/BackgroundEvents.ts",
    "chars": 263,
    "preview": "import browser from 'webextension-polyfill'\n\nexport const openTab = (url: string): void => {\n  browser.runtime\n    .send"
  },
  {
    "path": "src/Utils/Hideable.tsx",
    "chars": 399,
    "preview": "import React, { FunctionComponent, HTMLProps } from 'react'\n\ninterface Props {\n  isVisible: boolean\n}\n\nexport const Hide"
  },
  {
    "path": "src/Utils/Hooks/useAnalytics.ts",
    "chars": 414,
    "preview": "import { singletonHook } from 'react-singleton-hook'\nimport { useEffect, useState } from 'react'\nimport { Analytics } fr"
  },
  {
    "path": "src/Utils/Hooks/useBreakpoints.ts",
    "chars": 522,
    "preview": "import { useRef } from 'react'\nimport { useDimensions } from '@/Utils/Hooks/useDimensions'\n\ntype Breakpoint = 'xs' | 'sm"
  },
  {
    "path": "src/Utils/Hooks/useDimensions.ts",
    "chars": 599,
    "preview": "import { RefObject, useEffect, useState } from 'react'\nimport { useResize } from '@/Utils/Hooks/useResize'\n\nexport const"
  },
  {
    "path": "src/Utils/Hooks/useInterval.ts",
    "chars": 447,
    "preview": "import { useEffect, useRef } from 'react'\n\nexport const useInterval = (callback: () => void, delay: number) => {\n  const"
  },
  {
    "path": "src/Utils/Hooks/useResize.ts",
    "chars": 246,
    "preview": "import { useEffect } from 'react'\n\nexport const useResize = (onResize: () => void) => {\n  useEffect(() => {\n    window.a"
  },
  {
    "path": "src/Utils/JSONUtils.ts",
    "chars": 396,
    "preview": "export namespace JSONUtils {\n  export const getCircularReplacer = () => {\n    const seen = new WeakSet()\n    return (key"
  },
  {
    "path": "src/Utils/MessageFormatter.ts",
    "chars": 2056,
    "preview": "import { isString, StringUtils } from '@/Utils/StringUtils'\nimport { isNumber } from './Numbers'\n\nconst MAX_CHARACTERS ="
  },
  {
    "path": "src/Utils/Numbers.ts",
    "chars": 66,
    "preview": "export const isNumber = (value: any) => typeof value === 'number'\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/ArrayNodeRenderer.tsx",
    "chars": 1194,
    "preview": "import React from 'react'\nimport { ObjectTreeNode } from '@/Utils/ObjectTreerinator/index'\nimport { isArray, isBoolean, "
  },
  {
    "path": "src/Utils/ObjectTreerinator/ArrayRenderer.tsx",
    "chars": 771,
    "preview": "import React, { FunctionComponent } from 'react'\nimport { Collapsible } from '@/Utils/ObjectTreerinator/Collapsible'\nimp"
  },
  {
    "path": "src/Utils/ObjectTreerinator/BooleanRenderer.tsx",
    "chars": 222,
    "preview": "import React from 'react'\n\nexport const BooleanRenderer = (key: string, child: boolean) => (\n  <li key={key}>\n    <span "
  },
  {
    "path": "src/Utils/ObjectTreerinator/Collapsible.tsx",
    "chars": 1382,
    "preview": "import React, { FunctionComponent, useState } from 'react'\nimport { isArray, isEmpty, isObject } from 'lodash'\n\ninterfac"
  },
  {
    "path": "src/Utils/ObjectTreerinator/NullRenderer.tsx",
    "chars": 181,
    "preview": "import React from 'react'\n\nexport const NullRenderer = (key: string) => (\n  <li key={key}>\n    <span role='property'>{ke"
  },
  {
    "path": "src/Utils/ObjectTreerinator/NumberRenderer.tsx",
    "chars": 198,
    "preview": "import React from 'react'\n\nexport const NumberRenderer = (key: string, child: number) => (\n  <li key={key}>\n    <span ro"
  },
  {
    "path": "src/Utils/ObjectTreerinator/ObjectRenderer.tsx",
    "chars": 428,
    "preview": "import React, { FunctionComponent } from 'react'\nimport { ObjectTreeNode } from '@/Utils/ObjectTreerinator/index'\n\ninter"
  },
  {
    "path": "src/Utils/ObjectTreerinator/StringRenderer.tsx",
    "chars": 210,
    "preview": "import React from 'react'\n\nexport const StringRenderer = (key: string, child: string) => (\n  <li key={key}>\n    <span ro"
  },
  {
    "path": "src/Utils/ObjectTreerinator/index.tsx",
    "chars": 2630,
    "preview": "import {\n  isArray,\n  isBoolean,\n  isNil,\n  isNumber,\n  isObject,\n  isString,\n  toPairs,\n} from 'lodash'\nimport React, {"
  },
  {
    "path": "src/Utils/Objects.ts",
    "chars": 674,
    "preview": "export const isObject = (value: any) => typeof value === 'object'\n\nexport function omit(object, keys) {\n  const result ="
  },
  {
    "path": "src/Utils/Pagination.ts",
    "chars": 995,
    "preview": "export const calculatePagination = (\n  offset: number,\n  length: number,\n  currentPage: number,\n  setSearch: (search: st"
  },
  {
    "path": "src/Utils/StringUtils.ts",
    "chars": 1293,
    "preview": "import memoize from 'lodash.memoize'\n\nexport const isString = (value: any) => typeof value === 'string'\n\nexport namespac"
  }
]

// ... and 10 more files (download for full content)

About this extraction

This page contains the full source code of the leonardoventurini/meteor-devtools-evolved GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 210 files (234.5 KB), approximately 68.1k tokens, and a symbol index with 200 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!