[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"@babel/env\", \"@babel/react\"]\n}\n"
  },
  {
    "path": ".claude/settings.json",
    "content": "{\n  \"enabledPlugins\": {\n    \"frontend-design@claude-plugins-official\": true,\n    \"context7@claude-plugins-official\": true,\n    \"code-review@claude-plugins-official\": true,\n    \"feature-dev@claude-plugins-official\": true,\n    \"code-simplifier@claude-plugins-official\": true,\n    \"typescript-lsp@claude-plugins-official\": true,\n    \"superpowers@claude-plugins-official\": true\n  },\n  \"defaultMode\": \"bypassPermissions\",\n  \"hooks\": {\n    \"Stop\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"afplay /System/Library/Sounds/Glass.aiff\"\n          }\n        ]\n      }\n    ],\n    \"Notification\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"afplay /System/Library/Sounds/Funk.aiff\"\n          }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Write|Edit|MultiEdit\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"yarn prettier --write \\\"$CLAUDE_TOOL_INPUT_FILE_PATH\\\" && yarn eslint --fix \\\"$CLAUDE_TOOL_INPUT_FILE_PATH\\\" || exit 2\"\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[*.{js, jsx, ts, tsx, groovy}]\nindent_size = 2\nindent_style = space\nij_visual_guides = 80\n"
  },
  {
    "path": ".envrc",
    "content": "#!/usr/bin/env bash\nexport DEVTOOLS_HOME=\"$(git rev-parse --show-toplevel)\"\nexport MAC_CHROME=\"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\"\n\nfunction mpm {\n  meteor npm $@\n}\n\nfunction start {\n  yarn devapp\n}\n\nfunction develop {\n  local BROWSER=${1-\"chrome\"}\n  echo \"Starting Development mode for => ${BROWSER}\"\n  yarn concurrently -n ext,app \"webpack --config webpack/${BROWSER}.dev.js\" \"cd devapp-3.4 && npm start\"\n}\n\nfunction watch {\n  local BROWSER=${1-\"chrome\"}\n  yarn webpack --config webpack/${BROWSER}.dev.js\n}\n\nfunction setup {\n  yarn\n  cd devapp-3.4 || exit\n  npm install\n}\n\nfunction update-meteor {\n  cd devapp-3.4 || exit\n  meteor update\n  cd ..\n}\n\nfunction package-version {\n  grep version <package.json |\n    head -1 |\n    awk -F: '{ print $2 }' |\n    sed 's/[\", ]//g'\n}\n\nfunction build-for-browser {\n  local BROWSER=$1\n  local VERSION=$(package-version)\n\n  mkdir releases\n\n  yarn run build:${BROWSER}\n\n  cd extension/${BROWSER} || exit\n\n  zip -r \"../../releases/meteor-devtools-evolved-${VERSION}.${BROWSER}.zip\" -- *\n\n  cd - || exit\n}\n\nfunction build {\n  build-for-browser chrome\n  build-for-browser firefox\n}\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "const { merge } = require('lodash')\n\nmodule.exports = merge(require('@tstt/eslint-config/index.js'), {\n  rules: {\n    'global-require': 0,\n    '@typescript-eslint/no-var-requires': 0,\n    '@typescript-eslint/no-inferrable-types': [\n      2,\n      {\n        ignoreParameters: true,\n        ignoreProperties: true,\n      },\n    ],\n  },\n  globals: { Meteor: 'readonly', i18n: 'readonly' },\n})\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - development\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  lint-and-audit:\n    name: Lint & Security Audit\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Enable Corepack\n        run: corepack enable\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 24\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install --immutable\n\n      - name: Run lint\n        run: yarn run lint\n\n      - name: Run security audit\n        run: yarn run audit\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.idea\nnode_modules\nchrome/build\nfirefox/build\nchrome.pem\nfirefox.pem\nextension/chrome\nextension/firefox\nmongo-decimal-test-main\n.eslintcache\nyarn-error.log\n.yarn\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run lint-staged -s\n"
  },
  {
    "path": ".prettierignore",
    "content": "node_modules/\nextension/\ndevapp-3.4/\n.yarn/\nwebpack/\n*.lock\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"plugins\": [\"prettier-plugin-tailwindcss\"],\n  \"semi\": false,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"bracketSpacing\": true,\n  \"jsxSingleQuote\": true,\n  \"arrowParens\": \"avoid\"\n}\n"
  },
  {
    "path": ".serena/.gitignore",
    "content": "/cache\n"
  },
  {
    "path": ".serena/memories/architecture_patterns.md",
    "content": "# Architecture and Design Patterns\n\n## Browser Extension Architecture\n\n### Multi-Context Design\n\nThe extension operates in multiple browser contexts:\n\n1. **DevTools Panel** (`src/Pages/Panel`) - Main UI for DDP tracking and Minimongo inspection\n2. **Options Page** (`src/Pages/Options`) - Extension settings and configuration\n3. **Browser Action Popup** (`src/Pages/Popup`) - Quick access popup from toolbar\n4. **Background Scripts** - Browser extension background processes (in `src/Browser/`)\n5. **Content Scripts** - Injected into Meteor app pages (in `src/Injectors/`)\n\n### Communication Bridge\n\n- `src/Bridge.ts` handles inter-context communication\n- Uses browser extension messaging APIs\n- Coordinates between DevTools panel, content scripts, and page context\n\n## State Management with MobX\n\n### Store Pattern\n\n- MobX 6 with `mobx-react-lite` for React integration\n- Stores located in `src/Stores/`\n- Likely uses observable state with computed values and actions\n- React components observe stores using hooks\n\n### State Persistence\n\n- **Dexie (IndexedDB)** for persistent storage (bookmarks)\n- Located in `src/Database/`\n- Allows saving DDP messages across sessions and browser restarts\n\n## Component Architecture\n\n### UI Framework Hierarchy\n\n1. **Blueprint** - Primary component library (buttons, dialogs, forms, etc.)\n2. **Custom Components** - Project-specific components in `src/Components/`\n3. **Styled Components** - CSS-in-JS for component-specific styling\n4. **SCSS** - Global styles and theming\n5. **Tailwind CSS + DaisyUI** - Utility classes for rapid styling\n\n### Component Patterns\n\n- Functional components with React Hooks (React 17)\n- MobX observer components using `observer()` HOC or hooks\n- Likely uses `react-singleton-hook` for global singletons\n- `react-window` for virtualized lists (performance for large DDP message lists)\n\n## Data Visualization\n\n### D3.js Integration\n\nUsed for visualizing Meteor/DDP data structures:\n\n- **d3-hierarchy** - Tree structures for Minimongo documents\n- **d3-shape** - Shapes for data visualization\n- **d3-selection** - DOM manipulation for charts\n- **d3-collection** - Data structure utilities\n\nCustom \"Object Treerinator\" for document visualization (mentioned in README)\n\n## Performance Optimizations\n\n### Virtualization\n\n- `react-window` + `react-window-infinite-loader` for handling thousands of DDP messages\n- Virtual scrolling prevents DOM bloat with large datasets\n\n### Memoization & Throttling\n\n- `lodash.memoize` - Cache expensive computations\n- `lodash.debounce` - Debounce user input\n- `lodash.throttle` - Throttle high-frequency events (DDP message processing)\n\n## Build System\n\n### Webpack Configuration\n\n- **Base config** - Shared settings\n- **Dev builds** - Watch mode, source maps, faster builds\n- **Prod builds** - Minification, optimization\n- **Target-specific** - Different configs for Chrome vs Firefox\n  - Chrome uses Manifest V3\n  - Firefox may use Manifest V2 or adapted V3\n\n### Asset Handling\n\n- `file-loader` - Images and static assets\n- `css-loader` + `style-loader` - CSS module handling\n- `sass-loader` - SCSS compilation\n- `postcss-loader` - PostCSS transformations (Tailwind)\n- `ts-loader` - TypeScript compilation\n- `babel-loader` - JavaScript transpilation\n\n### Code Splitting\n\nLikely splits code by extension context (panel, popup, options, background)\n\n## Extension-Specific Patterns\n\n### Webextension Polyfill\n\n- `webextension-polyfill` provides cross-browser compatibility\n- Converts callback-based APIs to Promises\n- Allows same code to work on Chrome and Firefox\n\n### Injectors Pattern\n\n- Content scripts in `src/Injectors/` inject code into Meteor app pages\n- Intercepts DDP messages by hooking into Meteor's DDP client\n- Sends captured data to DevTools panel via messaging bridge\n\n### Analytics\n\n- `src/Analytics.ts` likely tracks usage (opt-in)\n- Helps understand feature usage and bugs\n\n## Development Workflow\n\n### Hot Module Replacement\n\n- Webpack dev builds watch for changes\n- Browser extension reloads on rebuild\n- Devapp runs concurrently on port 3000\n\n### Concurrent Development\n\n- `npm-run-all` / `concurrently` - Run multiple processes\n- `wait-on` - Synchronize startup (wait for builds and devapp)\n- `web-ext run` - Launches browser with extension loaded\n\n## Styling Strategy\n\n### Layered Approach\n\n1. **Normalize.css** - Base reset\n2. **Tailwind base** - Utility-first foundation\n3. **DaisyUI** - Component classes on top of Tailwind\n4. **Blueprint** - Pre-built React components\n5. **SCSS modules** - Component-specific styles\n6. **Styled Components** - Dynamic, prop-based styling\n7. **Polished** - Color manipulation and mixins for styled-components\n\nThis multi-layered approach provides flexibility while maintaining consistency.\n"
  },
  {
    "path": ".serena/memories/code_style_and_conventions.md",
    "content": "# Code Style and Conventions\n\n## ESLint Configuration\n\nThe project extends `@tstt/eslint-config` with custom overrides:\n\n- **global-require**: Disabled (allows require() anywhere)\n- **@typescript-eslint/no-var-requires**: Disabled (allows const x = require())\n- **@typescript-eslint/no-inferrable-types**: Error with ignoreParameters and ignoreProperties\n- **Global variables**: `Meteor` and `i18n` are defined as readonly\n\n## Prettier Configuration\n\nInherits from `@tstt/eslint-config/.prettierrc.js` (exact settings not visible but standard Prettier defaults)\n\n## EditorConfig Settings\n\n- **Indentation**: 2 spaces (for .js, .jsx, .ts, .tsx, .groovy)\n- **Line endings**: LF (Unix-style)\n- **Charset**: UTF-8\n- **Trailing whitespace**: Trimmed\n- **Final newline**: Required\n- **Visual guides**: 80 characters\n\n## TypeScript Configuration\n\n- **Strict mode**: Disabled (`strict: false`)\n- **noImplicitAny**: Disabled\n- **Target**: ES6\n- **Module**: CommonJS\n- **Module resolution**: Node\n- **Decorators**: Experimental decorators enabled\n- **JSX**: React\n- **Source maps**: Enabled\n- **ESModuleInterop**: Enabled\n- **Path mapping**: `@/*` → `src/*`\n\n## Naming Conventions\n\nBased on code inspection:\n\n- **Components**: PascalCase (e.g., `Panel`, `Options`, `Popup`)\n- **Files**: Match component names for React components\n- **Constants**: Likely UPPER_SNAKE_CASE (based on Constants.ts)\n- **Utilities**: camelCase functions\n\n## Code Organization Patterns\n\n- **React**: Functional components with hooks (React 17)\n- **State Management**: MobX stores for global state\n- **Styling**: Mix of styled-components, SCSS modules, and Tailwind utilities\n- **Type Definitions**: Global types in `src/index.d.ts`\n- **Imports**: Use path alias `@/` for src directory imports\n\n## Component Structure\n\nBased on `App.tsx`:\n\n- Import external libraries first (React, Blueprint, etc.)\n- Then internal components/pages\n- Finally styles (CSS/SCSS)\n- Component logic and rendering\n\n## Best Practices (from CONTRIBUTING.md)\n\n1. Code must be linted and properly formatted\n2. Features should benefit the Meteor community as a whole\n3. Be friendly and supportive in contributions\n4. Use appropriate IDE (WebStorm recommended by maintainer)\n"
  },
  {
    "path": ".serena/memories/codebase_structure.md",
    "content": "# Codebase Structure\n\n## Root Directory\n\n```\nmeteor-devtools-evolved/\n├── src/                    # Main source code\n├── devapp/                 # Test Meteor application for development\n├── webpack/                # Webpack configuration files\n├── extension/              # Built extension output (generated, not in git)\n├── releases/               # Release builds\n├── .husky/                 # Git hooks\n├── .github/                # GitHub Actions and configuration\n└── [config files]          # Various configuration files\n```\n\n## Source Directory (`src/`)\n\n```\nsrc/\n├── Assets/         # Static assets (images, icons, etc.)\n├── Browser/        # Browser extension specific code (background, content scripts)\n├── Components/     # Reusable React components\n├── Database/       # Dexie (IndexedDB) setup and models\n├── Injectors/      # Content script injectors for page context\n├── Pages/          # Main extension pages\n│   ├── Panel       # DevTools panel (main UI)\n│   ├── Options     # Extension options page\n│   └── Popup       # Browser action popup\n├── Stores/         # MobX state stores\n├── Styles/         # Global styles (SCSS, CSS, Tailwind)\n├── Utils/          # Utility functions and helpers\n├── App.tsx         # Main React application entry\n├── Bridge.ts       # Communication bridge between contexts\n├── Constants.ts    # Global constants\n├── Log.ts          # Logging utilities\n└── Analytics.ts    # Analytics tracking\n```\n\n## Webpack Configuration (`webpack/`)\n\n- `base.js` - Base configuration shared by all builds\n- `chrome.dev.js` / `chrome.prod.js` - Chrome-specific builds\n- `firefox.dev.js` / `firefox.prod.js` - Firefox-specific builds\n- `utils.js` - Webpack utility functions\n\n## Development App (`devapp/`)\n\nA minimal Meteor application used for testing the extension during development:\n\n- Runs on port 3000\n- Standard Meteor project structure\n- Uses Meteor's module system\n- Includes basic client and server code\n\n## Key Entry Points\n\n- `src/App.tsx` - Renders Panel, Options, or Popup based on context\n- Extension manifests generated by webpack for Chrome/Firefox\n- Path alias: `@/*` maps to `src/*` for imports\n"
  },
  {
    "path": ".serena/memories/project_overview.md",
    "content": "# Meteor DevTools Evolved - Project Overview\n\n## Purpose\n\nMeteor 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:\n\n- **DDP (Distributed Data Protocol) Tracking**: Filter and search through thousands of DDP messages to understand what's happening under the hood\n- **Bookmarks**: Save DDP messages for later search and retrieval (stored in IndexedDB)\n- **Minimongo Inspection**: Search and visualize documents in Minimongo with a custom Object Tree viewer\n\n## Project Context\n\nThis 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.\n\n## Distribution\n\n- Chrome Web Store: https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf\n- Firefox Add-ons: https://addons.mozilla.org/en-US/firefox/addon/meteor-devtools-evolved/\n\n## Repository\n\nGitHub: https://github.com/leonardoventurini/meteor-devtools-evolved\n"
  },
  {
    "path": ".serena/memories/security_and_auditing.md",
    "content": "# Security and Auditing\n\n## Audit Script\n\nThe project includes a security audit script that checks for high and critical severity vulnerabilities:\n\n```bash\nyarn run audit\n```\n\nThis runs: `yarn npm audit --all --recursive --severity high`\n\n## Security Resolutions\n\nDue 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.\n\n### Current Security Resolutions\n\nThe following packages are pinned to secure versions to address known vulnerabilities:\n\n- **@babel/traverse**: ^7.23.2 - Fixes arbitrary code execution vulnerability\n- **axios**: ^1.6.0 - Fixes SSRF and credential leakage vulnerability\n- **braces**: ^3.0.3 - Fixes uncontrolled resource consumption\n- **cross-spawn**: ^7.0.5 - Fixes Regular Expression Denial of Service (ReDoS)\n- **fast-json-patch**: ^3.1.1 - Fixes prototype pollution\n- **form-data**: ^4.0.0 - Fixes unsafe random function in boundary generation\n- **http-cache-semantics**: ^4.1.1 - Fixes ReDoS vulnerability\n- **json5**: ^2.2.3 - Fixes prototype pollution via parse method\n- **jsonwebtoken**: ^9.0.0 - Fixes unrestricted key type vulnerability\n- **jws**: ^4.0.0 - Fixes improper HMAC signature verification\n- **loader-utils**: ^3.2.1 - Fixes prototype pollution and ReDoS vulnerabilities\n- **node-forge**: ^1.3.2 - Fixes ASN.1 unbounded recursion\n- **qs**: ^6.14.1 - Fixes DoS via memory exhaustion\n- **semver**: ^7.5.4 - Fixes ReDoS vulnerabilities across multiple version ranges\n- **sha.js**: ^2.4.12 - Fixes missing type checks\n- **ws**: ^8.17.1 - Fixes DoS when handling requests with many HTTP headers\n\n## Security Update Process\n\nWhen security vulnerabilities are discovered:\n\n1. Run `yarn run audit` to identify issues\n2. Update direct dependencies where possible\n3. Add/update resolutions for transitive dependencies\n4. Run `yarn install` to apply changes\n5. Run `yarn run audit` again to verify fixes\n6. Test the application to ensure nothing broke\n7. Commit the changes with a clear security-focused message\n\n## Regular Security Maintenance\n\n- Run `yarn run audit` regularly (monthly recommended)\n- Keep direct dependencies up to date\n- Monitor GitHub security advisories\n- Review and update resolutions when new vulnerabilities are disclosed\n\n## Important Notes\n\n- The project uses **Yarn 4.12.0**, not npm, so use `yarn` commands\n- Resolutions in package.json override nested dependency versions\n- Some older dependencies (like file-loader) may have vulnerabilities that require resolutions because they're no longer maintained\n- Always test builds after security updates: `yarn build:chrome` and `yarn build:firefox`\n"
  },
  {
    "path": ".serena/memories/suggested_commands.md",
    "content": "# Suggested Commands\n\n## Initial Setup\n\n```bash\n# Install dependencies for both root and devapp\nyarn setup\n```\n\n## Development\n\n```bash\n# Start development mode for Chrome (default)\nyarn dev\n\n# Start development mode specifically for Chrome\nyarn dev:chrome\n\n# Start development mode for Firefox\nyarn dev:firefox\n\n# Run just the devapp (Meteor test application)\nyarn devapp\n```\n\n**Note**: The `yarn dev` commands will:\n\n1. Build and watch the extension\n2. Run the devapp in parallel\n3. Launch a browser instance with the extension installed\n4. Auto-reload on code changes\n\n## Building\n\n```bash\n# Build Chrome extension for production\nyarn build:chrome\n\n# Build Firefox extension for production\nyarn build:firefox\n\n# Clean build artifacts\nyarn clean\n```\n\n## Linting & Formatting\n\n```bash\n# Run ESLint on all source files\nyarn lint\n\n# Prettier and ESLint run automatically via pre-commit hooks\n# Manual formatting is handled by lint-staged\n```\n\n## Security Auditing\n\n```bash\n# Check for high and critical security vulnerabilities\nyarn run audit\n\n# This runs: yarn npm audit --all --recursive --severity high\n```\n\n## Git Operations\n\n```bash\n# Standard git commands work normally\ngit status\ngit add <files>\ngit commit -m \"message\"  # Pre-commit hook runs lint-staged automatically\ngit push\n```\n\n## Useful System Commands (macOS/Darwin)\n\n```bash\n# List files\nls -la\n\n# Search for files\nfind . -name \"*.tsx\"\n\n# Search in files\ngrep -r \"pattern\" src/\n\n# View file contents\ncat <file>\nhead -n 20 <file>\ntail -n 20 <file>\n\n# Navigate directories\ncd <directory>\npwd\n```\n\n## Testing Extension Manually\n\nAfter 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.\n\n## Node/Yarn Version Management\n\nThe project uses Volta to manage Node and Yarn versions:\n\n- Node.js: 14.19.3\n- Yarn: 1.22.18\n\nVolta will automatically use the correct versions if installed.\n\n## Troubleshooting\n\n```bash\n# If dependencies are out of sync, reinstall\nrm -rf node_modules yarn.lock\nyarn install\n\n# If devapp has issues\ncd devapp\nrm -rf node_modules yarn.lock\nyarn install\ncd ..\n\n# Reset Meteor (if devapp is broken)\ncd devapp\nmeteor reset\ncd ..\n```\n"
  },
  {
    "path": ".serena/memories/task_completion_checklist.md",
    "content": "# Task Completion Checklist\n\nWhen completing a coding task in this project, follow these steps:\n\n## 1. Pre-Commit Automatic Checks\n\nThe project uses Husky + lint-staged for pre-commit hooks. When you commit, the following runs automatically:\n\n- ✓ ESLint on all staged .js, .jsx, .ts, .tsx files\n- ✓ TypeScript type checking (tsc --noEmit)\n- ✓ Prettier formatting on staged files\n- ✓ React Scripts tests (if applicable, with --passWithNoTests)\n\n**If the pre-commit hook fails**, fix the issues before committing:\n\n```bash\n# Run lint manually to see all issues\nyarn lint\n\n# TypeScript errors need to be fixed in code\n# ESLint and Prettier issues are often auto-fixed by lint-staged\n```\n\n## 2. Manual Verification\n\nAfter making changes, verify:\n\n### Linting\n\n```bash\nyarn lint\n```\n\nEnsure no ESLint errors or warnings.\n\n### Security Audit\n\n```bash\nyarn run audit\n```\n\nEnsure no high or critical security vulnerabilities are introduced.\n\n### Building\n\n```bash\n# Test Chrome build\nyarn build:chrome\n\n# Test Firefox build\nyarn build:firefox\n```\n\nEnsure both builds complete successfully without errors.\n\n### Manual Testing\n\n```bash\n# Start dev environment\nyarn dev:chrome  # or yarn dev:firefox\n```\n\n- Open the browser instance that launches\n- Navigate to http://localhost:2100 (devapp)\n- Open DevTools and find the \"Meteor\" panel\n- Test your changes manually in the extension UI\n\n## 3. Code Quality Checks\n\nBefore considering the task done:\n\n- [ ] TypeScript compiles without errors\n- [ ] ESLint shows no errors or warnings\n- [ ] Code follows project conventions (2-space indent, LF line endings, etc.)\n- [ ] No unused imports or variables\n- [ ] MobX stores updated if state changes\n- [ ] Components are properly typed\n- [ ] Path imports use `@/` alias where appropriate\n\n## 4. Cross-Browser Compatibility\n\nIf the change affects browser-specific code:\n\n- [ ] Test in Chrome build (`yarn dev:chrome`)\n- [ ] Test in Firefox build (`yarn dev:firefox`)\n- [ ] Check for webextension-polyfill usage for cross-browser APIs\n\n## 5. Git Commit\n\n```bash\ngit add <changed-files>\ngit commit -m \"descriptive message\"\n# Pre-commit hooks run automatically\n```\n\n## 6. Common Issues\n\n### Pre-commit hook fails\n\n- Check `yarn lint` output\n- Run prettier manually if needed\n- Fix TypeScript errors shown by tsc\n\n### Build fails\n\n- Check webpack output for specific errors\n- Verify all imports exist and are correct\n- Check for TypeScript compilation errors\n\n### Extension doesn't load\n\n- Check browser console for errors\n- Verify manifest.json was generated correctly\n- Check extension/chrome or extension/firefox directories exist\n\n## Notes\n\n- The devapp must be running for the extension to work properly\n- IndexedDB data persists between sessions (for bookmarks)\n- Check Chrome DevTools console in the extension context for runtime errors\n"
  },
  {
    "path": ".serena/memories/tech_stack.md",
    "content": "# Tech Stack\n\n## Core Technologies\n\n- **TypeScript** 4.4.3 - Main programming language with ES6 target\n- **React** 17.0.2 - UI framework (functional components with hooks)\n- **MobX** 6.4.0 - State management library\n- **Webpack** 5 - Module bundler and build tool\n\n## UI & Styling\n\n- **Blueprint** 4.14.1 - Core UI components library by Palantir\n- **Styled Components** 5.3.3 - CSS-in-JS styling\n- **SASS/SCSS** - Additional styling with sass-loader\n- **Tailwind CSS** 3.0.24 - Utility-first CSS framework\n- **DaisyUI** 2.15.2 - Tailwind CSS component library\n- **Normalize.css** - CSS reset\n- **Heroicons React** - Icon library\n\n## Data & State\n\n- **Dexie** 3.2.2 - IndexedDB wrapper for bookmarks storage\n- **mobx-react-lite** 3.3.0 - React bindings for MobX\n\n## Utilities\n\n- **Luxon** 2.5.2 - Date/time manipulation\n- **Lodash** (selective imports: debounce, memoize, sortby, throttle)\n- **D3** (collection, hierarchy, selection, shape) - Data visualization\n- **pretty-bytes** - Byte formatting\n- **uuid** - Unique ID generation\n\n## Build Tools & Dev Environment\n\n- **Babel** 7 - JavaScript transpiler\n- **ts-loader** - TypeScript loader for Webpack\n- **PostCSS** - CSS processing\n- **Terser** - JavaScript minification\n- **web-ext** - Browser extension development tool\n- **concurrently** / **npm-run-all** - Run multiple commands\n- **wait-on** - Wait for resources before starting\n\n## Code Quality\n\n- **ESLint** - JavaScript/TypeScript linter (extends @tstt/eslint-config)\n- **Prettier** - Code formatter (from @tstt/eslint-config)\n- **Husky** - Git hooks\n- **lint-staged** - Run linters on staged files\n\n## Runtime Environment\n\n- **Node.js** 14.19.3 (managed by Volta)\n- **Yarn** 1.22.18 (managed by Volta)\n\n## Browser APIs\n\n- **@types/chrome** - Chrome extension API types\n- **webextension-polyfill** - Cross-browser extension API\n"
  },
  {
    "path": ".serena/project.yml",
    "content": "# the name by which the project can be referenced within Serena\nproject_name: 'meteor-devtools-evolved'\n\n# list of languages for which language servers are started; choose from:\n#   al                  bash                clojure             cpp                 csharp\n#   csharp_omnisharp    dart                elixir              elm                 erlang\n#   fortran             fsharp              go                  groovy              haskell\n#   java                julia               kotlin              lua                 markdown\n#   matlab              nix                 pascal              perl                php\n#   powershell          python              python_jedi         r                   rego\n#   ruby                ruby_solargraph     rust                scala               swift\n#   terraform           toml                typescript          typescript_vts      vue\n#   yaml                zig\n#   (This list may be outdated. For the current list, see values of Language enum here:\n#   https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py\n#   For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)\n# Note:\n#   - For C, use cpp\n#   - For JavaScript, use typescript\n#   - For Free Pascal/Lazarus, use pascal\n# Special requirements:\n#   Some languages require additional setup/installations.\n#   See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers\n# When using multiple languages, the first language server that supports a given file will be used for that file.\n# The first language is the default language and the respective language server will be used as a fallback.\n# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.\nlanguages:\n  - typescript\n\n# the encoding used by text files in the project\n# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings\nencoding: 'utf-8'\n\n# whether to use project's .gitignore files to ignore files\nignore_all_files_in_gitignore: true\n\n# list of additional paths to ignore in all projects\n# same syntax as gitignore, so you can use * and **\nignored_paths: []\n\n# whether the project is in read-only mode\n# If set to true, all editing tools will be disabled and attempts to use them will result in an error\n# Added on 2025-04-18\nread_only: false\n\n# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.\n# Below is the complete list of tools for convenience.\n# To make sure you have the latest list of tools, and to view their descriptions,\n# execute `uv run scripts/print_tool_overview.py`.\n#\n#  * `activate_project`: Activates a project by name.\n#  * `check_onboarding_performed`: Checks whether project onboarding was already performed.\n#  * `create_text_file`: Creates/overwrites a file in the project directory.\n#  * `delete_lines`: Deletes a range of lines within a file.\n#  * `delete_memory`: Deletes a memory from Serena's project-specific memory store.\n#  * `execute_shell_command`: Executes a shell command.\n#  * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.\n#  * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).\n#  * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).\n#  * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.\n#  * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.\n#  * `initial_instructions`: Gets the initial instructions for the current project.\n#     Should only be used in settings where the system prompt cannot be set,\n#     e.g. in clients you have no control over, like Claude Desktop.\n#  * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.\n#  * `insert_at_line`: Inserts content at a given line in a file.\n#  * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.\n#  * `list_dir`: Lists files and directories in the given directory (optionally with recursion).\n#  * `list_memories`: Lists memories in Serena's project-specific memory store.\n#  * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).\n#  * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).\n#  * `read_file`: Reads a file within the project directory.\n#  * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.\n#  * `remove_project`: Removes a project from the Serena configuration.\n#  * `replace_lines`: Replaces a range of lines within a file with new content.\n#  * `replace_symbol_body`: Replaces the full definition of a symbol.\n#  * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.\n#  * `search_for_pattern`: Performs a search for a pattern in the project.\n#  * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.\n#  * `switch_modes`: Activates modes by providing a list of their names\n#  * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.\n#  * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.\n#  * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.\n#  * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.\nexcluded_tools: []\n\n# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)\nincluded_optional_tools: []\n\n# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.\n# This cannot be combined with non-empty excluded_tools or included_optional_tools.\nfixed_tools: []\n\n# list of mode names to that are always to be included in the set of active modes\n# The full set of modes to be activated is base_modes + default_modes.\n# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.\n# Otherwise, this setting overrides the global configuration.\n# Set this to [] to disable base modes for this project.\n# Set this to a list of mode names to always include the respective modes for this project.\nbase_modes:\n\n# list of mode names that are to be activated by default.\n# The full set of modes to be activated is base_modes + default_modes.\n# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.\n# Otherwise, this overrides the setting from the global configuration (serena_config.yml).\n# This setting can, in turn, be overridden by CLI parameters (--mode).\ndefault_modes:\n\n# initial prompt for the project. It will always be given to the LLM upon activating the project\n# (contrary to the memories, which are loaded on demand).\ninitial_prompt: ''\n"
  },
  {
    "path": ".yarnrc.yml",
    "content": "nodeLinker: node-modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\n> The dates refer to when it was made available in the Chrome platform.\n\n## [1.8] - 2023-01-17\n\n## Fixed\n\n- Fixed a bug where the extension crashes on custom Minimongo types specifically on MongoDecimal type.\n- Issue where the lodash package would break the extension randomly on some pages and reduced bundle size.\n\n## Added\n\n- Help drawer containing contact information for the author and for partners. Moved the content from the About drawer to the Help drawer.\n- Added sponsor button so users can support the project.\n\n## [1.7] - 2022-07-05\n\n## Added\n\n- The Firefox browser is now supported. Thanks to Niloy.\n\n## Changed (development only)\n\n- Now we can build extensions for both Chrome and Firefox using the same codebase.\n- New commands added to support the workflow.\n\n## [1.6] - 2022-06-08\n\n## Added\n\n- Now logs are intercepted and stored in the background and loaded when you open the devtools panel.\n- Add Meteor emoji to devtool tab.\n- Add emojis to some actions in the top bar.\n- Add Galaxy Sponsor content.\n\n## Removed\n\n- Removed CRC32 hashes, they did not have much use besides looking cool. Improves performance a bit.\n\n## [1.5] - 2022-04-14\n\n## Added\n\n- Google Analytics for improving the extension.\n- The browser action of the extension opens Galaxy.\n- Add subscription duration, so we know how long specific subscriptions take.\n- Performance tab which measures Minimongo calls.\n\n## Changed\n\n- Upgrade dependencies to latest.\n- Add copy JSON button to minimongo drawer.\n- Improved minimongo tracking performance and responsiveness.\n- By default JSON documents are expanded up to 5 levels now.\n- Remove Iosevka font, the default monospaced font from OS.\n- Now the extension is loaded slightly earlier, so we don't miss initial Meteor activity.\n\n## Fixed\n\n- Fix stack trace error issue caused by a third party library affecting some users.\n\n## [1.4] - 2020-07-21\n\n## Changed\n\n- Make stack trace and bookmark buttons more accessible.\n- Make right menu more responsive.\n- Estimated collection size is always visible for all collections.\n\n## Fixed\n\n- Fix `error-stack-parser` global pollution interacting badly with some websites.\n\n## [1.3] - 2020-06-17\n\n## Added\n\n- Meteor `gitCommitHash` is now shown in the status bar.\n- Community Slack button (with VFX!!)\n- Added subscription search.\n- Estimate Minimongo collection byte size.\n\n## Changed\n\n- Subscriptions are clickable and open the params object viewer.\n- Improved naming for the extension global variables to avoid collisions.\n- Removed horizontal scroll constraint, but making it more responsive is too much work for now.\n\n## Fixed\n\n- Fixed horizontal scroll showing when resizing.\n\n## [1.2] - 2020-04-29\n\nIn 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.\n\n### Added\n\n- Minimongo sidebar navigation ordered alphabetically and with counts.\n- Add the Iosevka font as it is more space efficient in certain scenarios.\n- Subscriptions tab listing all current subscriptions in real-time-ish.\n\n### Changed\n\n- The DDP log is now a virtualized list with INFINITE scrolling and new logs come at the top.\n- Moved extension logs from top frame to background. NO MORE ANNOYING CONSOLE LOGS!!!\n- Logs now have their interaction menu as a popover in order to be more space efficient.\n- More space-efficient tabs and status bars.\n\n### Fixed\n\n- Small fixes and improvements in Treerinator (JSON Viewer).\n- Show subscription name when ready as well.\n- Fixed GitHub stats not persisting as they should.\n\n## [1.1] - 2020-04-02\n\nI 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.\n\n### Added\n\n- [Issue #1](https://github.com/leonardoventurini/meteor-devtools-evolved/issues/1)\n  Added ability to replay methods either from the logs or bookmarks.\n- Added document count to collection navigator.\n- Added Minimongo active collection clear button.\n- Added GitHub buttons to make receiving feedback easier.\n- Added long timestamp format on hover for logs which is useful for bookmarks.\n- Added setting persistence, which means the filters will persist between sessions.\n- Added about page with some basics and license information.\n\n### Changed\n\n- Adjusted the layout, so it is responsive to screens with less horizontal real-estate.\n- Collection tags are now clickable.\n\n### Fixed\n\n- [Issue #2](https://github.com/leonardoventurini/meteor-devtools-evolved/issues/2)\n  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.\n\n## [1.0] - 2020-03-05\n\nInitial release.\n\n### Added\n\n- Added DDP logging.\n- Added DDP bookmarking.\n- Added Minimongo browsing.\n- Added search and pagination.\n- Added a bunch of stuff really.\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "IMPORTANT: The first thing you ever do is to start the project `meteor-devtools-evolved` in the Serena MCP server\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Setting the Environment Up\n\n1. Install dependencies for `devapp-3.4` & `root` with `yarn`.\n\n```shell\nyarn setup\n```\n\n> As of now we use Node.js `v14.19.3`.\n\n2. Run the extension locally\n\n```shell\nyarn dev # default chrome\n```\n\n```shell\nyarn dev:chrome # for chrome\n```\n\n```shell\nyarn dev:firefox # for firefox\n```\n\n> 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\n\n5. Hack away!\n\n   > Open a Pull Request from your fork to our repo once it is done or need a review.\n\n## Environment Commands\n\nIf you use Linux you can run `source .envrc` for some useful commands\n\n> -c: for chrome, -f: firefox, (chrome is default)\n\n- Setup extension and test project Dependencies\n\n```shell\nsetup\n```\n\n## Build\n\n- Chrome\n\n```shell\nnpm run build:chrome\n```\n\n- Firefox\n\n```shell\nnpm run build:firefox\n```\n\n## Guidelines & Objectives\n\n1. 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.\n2. Every feature needs to take into account the Meteor community as a whole and not the interest of a few in detriment of others.\n3. Be friendly and supportive, no one is perfect, and we all have limited time, especially in these difficult times.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2020 Leonardo Venturini\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n<img src=\"https://media.giphy.com/media/Pt2yOXUALOhpB5dpiM/giphy.gif\" alt=\"Meteor Devtool Evolved Gif\" />\n\n<p style=\"font-size: 30px\">\nMeteor Devtools Extension\n</p>\nBehold, the evolution of Meteor DevTools.</p>\n\nMeteor Devtools Evolved is currently available for Google Chrome and Mozilla Firefox.\n\n</div>\n\n<p align=\"center\" >\n    <a href=\"https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf\">\n    <img width=\"120\" src=\"https://img.shields.io/badge/%20-Chrome-orange?logo=google-chrome&logoColor=white\" alt=\"Download for Chrome\" />\n    </a>\n    <a href=\"https://addons.mozilla.org/en-US/firefox/addon/meteor-devtools-evolved/\">\n    <img width=\"110\" src=\"https://img.shields.io/badge/%20-Firefox-red?logo=mozilla&logoColor=white\" alt=\"Download for Firefox\" />\n    </a>\n</p>\n\n[Harder, Better, Faster, Stronger](https://www.youtube.com/watch?v=gAjR4_CbPpQ) :rocket:\n\nAre 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.\n\n:point_right: [Changelog](CHANGELOG.md)\n\n### Distributed Data Protocol (DDP)\n\nEverything 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.\n\n### Bookmarks\n\nThe 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.\n\n### Minimongo\n\nYou 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.\n\n---\n\n## Development\n\n> 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\".\n\nThe 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.\n\n> Anyone is welcome to contribute, more info [here](CONTRIBUTING.md).\n\n## Firefox\n\nThe Firefox port of the extension was a contribution made by [@nilooy](https://github.com/nilooy). Thank you!\n"
  },
  {
    "path": "devapp-2.0.0/.gitignore",
    "content": "node_modules/\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/.finished-upgraders",
    "content": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should check it into version control\n# with your project.\n\nnotices-for-0.9.0\nnotices-for-0.9.1\n0.9.4-platform-file\nnotices-for-facebook-graph-api-2\n1.2.0-standard-minifiers-package\n1.2.0-meteor-platform-split\n1.2.0-cordova-changes\n1.2.0-breaking-changes\n1.3.0-split-minifiers-package\n1.4.0-remove-old-dev-bundle-link\n1.4.1-add-shell-server-package\n1.4.3-split-account-service-packages\n1.5-add-dynamic-import-package\n1.7-split-underscore-from-meteor-base\n1.8.3-split-jquery-from-blaze\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/.gitignore",
    "content": "local\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/.id",
    "content": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this directory.\n# It can be used for purposes such as:\n#   - ensuring you don't accidentally deploy one app on top of another\n#   - providing package authors with aggregated statistics\n\nx58f9z1u8cbj.d63i3vx3cjzb\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/packages",
    "content": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into your repository.\n#\n# 'meteor add' and 'meteor remove' will edit this file for you,\n# but you can also edit it by hand.\n\nmeteor-base@1.4.0             # Packages every Meteor app needs to have\nmobile-experience@1.1.0       # Packages for a great mobile UX\nmongo@1.10.1                   # The database Meteor supports right now\nreactive-var@1.0.11            # Reactive variable for tracker\n\nstandard-minifier-css@1.7.2   # CSS minifier run for production mode\nstandard-minifier-js@2.6.0    # JS minifier run for production mode\nes5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers\necmascript@0.15.0              # Enable ECMAScript2015+ syntax in app code\ntypescript@4.1.2              # Enable TypeScript syntax in .ts and .tsx modules\nshell-server@0.5.0            # Server-side component of the `meteor shell` command\nhot-module-replacement@0.2.0  # Update client in development without reloading the page\n\ninsecure@1.0.7                # Allow all DB writes from clients (for prototyping)\nstatic-html             # Define static page content in .html files\nreact-meteor-data       # React higher-order component for reactively tracking Meteor data\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/platforms",
    "content": "server\nbrowser\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/release",
    "content": "METEOR@2.0\n"
  },
  {
    "path": "devapp-2.0.0/.meteor/versions",
    "content": "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@1.1.3\nboilerplate-generator@1.7.1\ncaching-compiler@1.2.2\ncaching-html-compiler@1.2.1\ncallback-hook@1.3.1\ncheck@1.3.1\nddp@1.4.0\nddp-client@2.4.1\nddp-common@1.4.0\nddp-server@2.3.3\ndiff-sequence@1.1.1\ndynamic-import@0.6.0\necmascript@0.15.1\necmascript-runtime@0.7.0\necmascript-runtime-client@0.11.1\necmascript-runtime-server@0.10.1\nejson@1.1.1\nes5-shim@4.8.0\nfetch@0.1.1\ngeojson-utils@1.0.10\nhot-code-push@1.0.4\nhot-module-replacement@0.2.1\nhtml-tools@1.1.3\nhtmljs@1.1.1\nid-map@1.1.1\ninsecure@1.0.7\ninter-process-messaging@0.1.1\nlaunch-screen@1.2.1\nlivedata@1.0.18\nlogging@1.2.0\nmeteor@1.9.3\nmeteor-base@1.4.0\nminifier-css@1.5.4\nminifier-js@2.6.1\nminimongo@1.6.2\nmobile-experience@1.1.0\nmobile-status-bar@1.1.0\nmodern-browsers@0.1.7\nmodules@0.16.0\nmodules-runtime@0.12.0\nmodules-runtime-hot@0.13.0\nmongo@1.10.1\nmongo-decimal@0.1.2\nmongo-dev-server@1.1.0\nmongo-id@1.0.8\nnpm-mongo@3.8.1\nordered-dict@1.1.0\npromise@0.11.2\nrandom@1.2.0\nreact-fast-refresh@0.1.1\nreact-meteor-data@2.5.1\nreactive-var@1.0.11\nreload@1.3.1\nretry@1.1.0\nroutepolicy@1.1.0\nshell-server@0.5.0\nsocket-stream-client@0.3.3\nspacebars-compiler@1.3.1\nstandard-minifier-css@1.7.3\nstandard-minifier-js@2.6.1\nstatic-html@1.3.2\ntemplating-tools@1.2.2\ntracker@1.2.0\ntypescript@4.1.2\nunderscore@1.0.10\nwebapp@1.10.1\nwebapp-hashing@1.1.0\n"
  },
  {
    "path": "devapp-2.0.0/client/main.css",
    "content": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-2.0.0/client/main.html",
    "content": "<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",
    "content": "import React from 'react'\nimport { Meteor } from 'meteor/meteor'\nimport { render } from 'react-dom'\nimport { App } from '../imports/ui/App'\n\nimport '../imports/api/links'\nimport '../imports/api/random'\n\nMeteor.startup(() => {\n  render(<App />, document.getElementById('react-target'))\n})\n"
  },
  {
    "path": "devapp-2.0.0/imports/api/links.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-2.0.0/imports/api/random.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const RandomCollection = new Mongo.Collection('random')\n"
  },
  {
    "path": "devapp-2.0.0/imports/ui/App.jsx",
    "content": "import React, { useEffect, useRef, useState } from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { RandomCollection } from '../api/random'\n\nexport const App = () => {\n  const [isSpamming, setSpamming] = useState(false)\n  const spammerRef = useRef(0)\n\n  const r1to100 = useTracker(() => {\n    const handle = Meteor.subscribe('random1to100')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r101to200 = useTracker(() => {\n    const handle = Meteor.subscribe('random101to200')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r201to300 = useTracker(() => {\n    const handle = Meteor.subscribe('random201to300')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r301to400 = useTracker(() => {\n    const handle = Meteor.subscribe('random301to400')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r401to500 = useTracker(() => {\n    const handle = Meteor.subscribe('random401to500')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r501to600 = useTracker(() => {\n    const handle = Meteor.subscribe('random501to600')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r601to700 = useTracker(() => {\n    const handle = Meteor.subscribe('random601to700')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r701to800 = useTracker(() => {\n    const handle = Meteor.subscribe('random701to800')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r801to900 = useTracker(() => {\n    const handle = Meteor.subscribe('random801to900')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r901to1000 = useTracker(() => {\n    const handle = Meteor.subscribe('random901to1000')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  useEffect(() => {\n    if (isSpamming && !spammerRef.current) {\n      spammerRef.current = setInterval(() => {\n        Meteor.call('echo', 'Echo')\n      }, 100)\n    } else {\n      if (spammerRef.current) {\n        clearInterval(spammerRef.current)\n        spammerRef.current = 0\n      }\n    }\n  }, [isSpamming])\n\n  return (\n    <div>\n      <h1>Welcome to Meteor!</h1>\n\n      <button\n        onClick={() => {\n          setSpamming(!isSpamming)\n        }}\n      >\n        {isSpamming ? 'Spam [On]' : 'Spam [Off]'}\n      </button>\n\n      <button\n        onClick={() => {\n          Meteor.call('echo', 'Echo')\n        }}\n      >\n        String\n      </button>\n\n      <button\n        onClick={() => {\n          Meteor.call('echo', {\n            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.',\n          })\n        }}\n      >\n        Object\n      </button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.0.0/imports/ui/Hello.jsx",
    "content": "import React, { useState } from 'react'\n\nexport const Hello = () => {\n  const [counter, setCounter] = useState(0)\n\n  const increment = () => {\n    setCounter(counter + 1)\n  }\n\n  return (\n    <div>\n      <button onClick={increment}>Click Me</button>\n      <p>You've pressed the button {counter} times.</p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.0.0/imports/ui/Info.jsx",
    "content": "import React from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/links'\n\nexport const Info = () => {\n  const links = useTracker(() => {\n    return LinksCollection.find().fetch()\n  })\n\n  return (\n    <div>\n      <h2>Learn Meteor!</h2>\n      <ul>\n        {links.map(link => (\n          <li key={link._id}>\n            <a href={link.url} target='_blank' rel='noreferrer'>\n              {link.title}\n            </a>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.0.0/package.json",
    "content": "{\n  \"name\": \"devapp-2.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once --driver-package meteortesting:mocha\",\n    \"test-app\": \"TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha\",\n    \"visualize\": \"meteor --production --extra-packages bundle-visualizer\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.11.2\",\n    \"meteor-node-stubs\": \"^1.0.1\",\n    \"react\": \"^16.13.1\",\n    \"react-dom\": \"^16.13.1\"\n  },\n  \"meteor\": {\n    \"mainModule\": {\n      \"client\": \"client/main.jsx\",\n      \"server\": \"server/main.js\"\n    },\n    \"testModule\": \"tests/main.js\"\n  }\n}\n"
  },
  {
    "path": "devapp-2.0.0/server/main.js",
    "content": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '../imports/api/links'\nimport { RandomCollection } from '../imports/api/random'\n\nfunction insertLink(title, url) {\n  LinksCollection.insert({ title, url, createdAt: new Date() })\n}\n\nMeteor.methods({\n  echo(echo) {\n    return echo\n  },\n})\n\nMeteor.startup(() => {\n  if (LinksCollection.find().count() === 0) {\n    insertLink(\n      'Do the Tutorial',\n      'https://www.meteor.com/tutorials/react/creating-an-app',\n    )\n\n    insertLink('Follow the Guide', 'http://guide.meteor.com')\n\n    insertLink('Read the Docs', 'https://docs.meteor.com')\n\n    insertLink('Discussions', 'https://forums.meteor.com')\n  }\n\n  RandomCollection.remove({})\n\n  let counter = 1\n\n  new Array(1000)\n    .fill(null)\n    .map(() => ({\n      name: 'Lorem Ipsum '.concat(String(counter)),\n      number: counter++,\n    }))\n    .forEach(item => {\n      RandomCollection.insert(item)\n    })\n})\n\nMeteor.publish('random1to100', function () {\n  return RandomCollection.find({\n    number: { $gte: 1, $lte: 100 },\n  })\n})\n\nMeteor.publish('random101to200', function () {\n  return RandomCollection.find({\n    number: { $gte: 101, $lte: 200 },\n  })\n})\n\nMeteor.publish('random201to300', function () {\n  return RandomCollection.find({\n    number: { $gte: 201, $lte: 300 },\n  })\n})\n\nMeteor.publish('random301to400', function () {\n  return RandomCollection.find({\n    number: { $gte: 301, $lte: 400 },\n  })\n})\n\nMeteor.publish('random401to500', function () {\n  return RandomCollection.find({\n    number: { $gte: 401, $lte: 500 },\n  })\n})\n\nMeteor.publish('random501to600', function () {\n  return RandomCollection.find({\n    number: { $gte: 501, $lte: 600 },\n  })\n})\n\nMeteor.publish('random601to700', function () {\n  return RandomCollection.find({\n    number: { $gte: 601, $lte: 700 },\n  })\n})\n\nMeteor.publish('random701to800', function () {\n  return RandomCollection.find({\n    number: { $gte: 701, $lte: 800 },\n  })\n})\n\nMeteor.publish('random801to900', function () {\n  return RandomCollection.find({\n    number: { $gte: 801, $lte: 900 },\n  })\n})\n\nMeteor.publish('random901to1000', function () {\n  return RandomCollection.find({\n    number: { $gte: 901, $lte: 1000 },\n  })\n})\n"
  },
  {
    "path": "devapp-2.0.0/tests/main.js",
    "content": "import assert from 'assert'\n\ndescribe('devapp-2.0.0', function () {\n  it('package.json has correct name', async function () {\n    const { name } = await import('../package.json')\n    assert.strictEqual(name, 'devapp-2.0.0')\n  })\n\n  if (Meteor.isClient) {\n    it('client is not server', function () {\n      assert.strictEqual(Meteor.isServer, false)\n    })\n  }\n\n  if (Meteor.isServer) {\n    it('server is not client', function () {\n      assert.strictEqual(Meteor.isClient, false)\n    })\n  }\n})\n"
  },
  {
    "path": "devapp-2.2.0/.gitignore",
    "content": "node_modules/\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/.finished-upgraders",
    "content": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should check it into version control\n# with your project.\n\nnotices-for-0.9.0\nnotices-for-0.9.1\n0.9.4-platform-file\nnotices-for-facebook-graph-api-2\n1.2.0-standard-minifiers-package\n1.2.0-meteor-platform-split\n1.2.0-cordova-changes\n1.2.0-breaking-changes\n1.3.0-split-minifiers-package\n1.4.0-remove-old-dev-bundle-link\n1.4.1-add-shell-server-package\n1.4.3-split-account-service-packages\n1.5-add-dynamic-import-package\n1.7-split-underscore-from-meteor-base\n1.8.3-split-jquery-from-blaze\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/.gitignore",
    "content": "local\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/.id",
    "content": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this directory.\n# It can be used for purposes such as:\n#   - ensuring you don't accidentally deploy one app on top of another\n#   - providing package authors with aggregated statistics\n\nkckjbl9hqpog.ffkb1f09s7ns\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/packages",
    "content": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into your repository.\n#\n# 'meteor add' and 'meteor remove' will edit this file for you,\n# but you can also edit it by hand.\n\nmeteor-base@1.4.0             # Packages every Meteor app needs to have\nmobile-experience@1.1.0       # Packages for a great mobile UX\nmongo@1.11.0                   # The database Meteor supports right now\nreactive-var@1.0.11            # Reactive variable for tracker\n\nstandard-minifier-css@1.7.2   # CSS minifier run for production mode\nstandard-minifier-js@2.6.0    # JS minifier run for production mode\nes5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers\necmascript@0.15.1              # Enable ECMAScript2015+ syntax in app code\ntypescript@4.2.2              # Enable TypeScript syntax in .ts and .tsx modules\nshell-server@0.5.0            # Server-side component of the `meteor shell` command\nhot-module-replacement@0.2.0  # Update client in development without reloading the page\n\ninsecure@1.0.7                # Allow all DB writes from clients (for prototyping)\nstatic-html             # Define static page content in .html files\nreact-meteor-data       # React higher-order component for reactively tracking Meteor data\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/platforms",
    "content": "server\nbrowser\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/release",
    "content": "METEOR@2.2\n"
  },
  {
    "path": "devapp-2.2.0/.meteor/versions",
    "content": "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@1.1.3\nboilerplate-generator@1.7.1\ncaching-compiler@1.2.2\ncaching-html-compiler@1.2.1\ncallback-hook@1.3.1\ncheck@1.3.1\nddp@1.4.0\nddp-client@2.4.1\nddp-common@1.4.0\nddp-server@2.3.3\ndiff-sequence@1.1.1\ndynamic-import@0.6.0\necmascript@0.15.1\necmascript-runtime@0.7.0\necmascript-runtime-client@0.11.1\necmascript-runtime-server@0.10.1\nejson@1.1.1\nes5-shim@4.8.0\nfetch@0.1.1\ngeojson-utils@1.0.10\nhot-code-push@1.0.4\nhot-module-replacement@0.2.1\nhtml-tools@1.1.3\nhtmljs@1.1.1\nid-map@1.1.1\ninsecure@1.0.7\ninter-process-messaging@0.1.1\nlaunch-screen@1.2.1\nlivedata@1.0.18\nlogging@1.2.0\nmeteor@1.9.3\nmeteor-base@1.4.0\nminifier-css@1.5.4\nminifier-js@2.6.1\nminimongo@1.6.2\nmobile-experience@1.1.0\nmobile-status-bar@1.1.0\nmodern-browsers@0.1.7\nmodules@0.16.0\nmodules-runtime@0.12.0\nmodules-runtime-hot@0.13.0\nmongo@1.11.1\nmongo-decimal@0.1.2\nmongo-dev-server@1.1.0\nmongo-id@1.0.8\nnpm-mongo@3.9.1\nordered-dict@1.1.0\npromise@0.11.2\nrandom@1.2.0\nreact-fast-refresh@0.1.1\nreact-meteor-data@2.5.1\nreactive-var@1.0.11\nreload@1.3.1\nretry@1.1.0\nroutepolicy@1.1.0\nshell-server@0.5.0\nsocket-stream-client@0.3.3\nspacebars-compiler@1.3.1\nstandard-minifier-css@1.7.3\nstandard-minifier-js@2.6.1\nstatic-html@1.3.2\ntemplating-tools@1.2.2\ntracker@1.2.0\ntypescript@4.2.2\nunderscore@1.0.10\nwebapp@1.10.1\nwebapp-hashing@1.1.0\n"
  },
  {
    "path": "devapp-2.2.0/client/main.css",
    "content": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-2.2.0/client/main.html",
    "content": "<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",
    "content": "import React from 'react'\nimport { Meteor } from 'meteor/meteor'\nimport { render } from 'react-dom'\nimport { App } from '../imports/ui/App'\n\nimport '../imports/api/links'\nimport '../imports/api/random'\n\nMeteor.startup(() => {\n  render(<App />, document.getElementById('react-target'))\n})\n"
  },
  {
    "path": "devapp-2.2.0/imports/api/links.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-2.2.0/imports/api/random.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const RandomCollection = new Mongo.Collection('random')\n"
  },
  {
    "path": "devapp-2.2.0/imports/ui/App.jsx",
    "content": "import React, { useEffect, useRef, useState } from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { RandomCollection } from '../api/random'\n\nexport const App = () => {\n  const [isSpamming, setSpamming] = useState(false)\n  const spammerRef = useRef(0)\n\n  const r1to100 = useTracker(() => {\n    const handle = Meteor.subscribe('random1to100')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r101to200 = useTracker(() => {\n    const handle = Meteor.subscribe('random101to200')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r201to300 = useTracker(() => {\n    const handle = Meteor.subscribe('random201to300')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r301to400 = useTracker(() => {\n    const handle = Meteor.subscribe('random301to400')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r401to500 = useTracker(() => {\n    const handle = Meteor.subscribe('random401to500')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r501to600 = useTracker(() => {\n    const handle = Meteor.subscribe('random501to600')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r601to700 = useTracker(() => {\n    const handle = Meteor.subscribe('random601to700')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r701to800 = useTracker(() => {\n    const handle = Meteor.subscribe('random701to800')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r801to900 = useTracker(() => {\n    const handle = Meteor.subscribe('random801to900')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r901to1000 = useTracker(() => {\n    const handle = Meteor.subscribe('random901to1000')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  useEffect(() => {\n    if (isSpamming && !spammerRef.current) {\n      spammerRef.current = setInterval(() => {\n        Meteor.call('echo', 'Echo')\n      }, 100)\n    } else {\n      if (spammerRef.current) {\n        clearInterval(spammerRef.current)\n        spammerRef.current = 0\n      }\n    }\n  }, [isSpamming])\n\n  return (\n    <div>\n      <h1>Welcome to Meteor!</h1>\n\n      <button\n        onClick={() => {\n          setSpamming(!isSpamming)\n        }}\n      >\n        {isSpamming ? 'Spam [On]' : 'Spam [Off]'}\n      </button>\n\n      <button\n        onClick={() => {\n          Meteor.call('echo', 'Echo')\n        }}\n      >\n        String\n      </button>\n\n      <button\n        onClick={() => {\n          Meteor.call('echo', {\n            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.',\n          })\n        }}\n      >\n        Object\n      </button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.2.0/imports/ui/Hello.jsx",
    "content": "import React, { useState } from 'react'\n\nexport const Hello = () => {\n  const [counter, setCounter] = useState(0)\n\n  const increment = () => {\n    setCounter(counter + 1)\n  }\n\n  return (\n    <div>\n      <button onClick={increment}>Click Me</button>\n      <p>You've pressed the button {counter} times.</p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.2.0/imports/ui/Info.jsx",
    "content": "import React from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/links'\n\nexport const Info = () => {\n  const links = useTracker(() => {\n    return LinksCollection.find().fetch()\n  })\n\n  return (\n    <div>\n      <h2>Learn Meteor!</h2>\n      <ul>\n        {links.map(link => (\n          <li key={link._id}>\n            <a href={link.url} target='_blank'>\n              {link.title}\n            </a>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.2.0/package.json",
    "content": "{\n  \"name\": \"devapp-2.2.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once --driver-package meteortesting:mocha\",\n    \"test-app\": \"TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha\",\n    \"visualize\": \"meteor --production --extra-packages bundle-visualizer\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.11.2\",\n    \"meteor-node-stubs\": \"^1.0.1\",\n    \"react\": \"^16.13.1\",\n    \"react-dom\": \"^16.13.1\"\n  },\n  \"meteor\": {\n    \"mainModule\": {\n      \"client\": \"client/main.jsx\",\n      \"server\": \"server/main.js\"\n    },\n    \"testModule\": \"tests/main.js\"\n  }\n}\n"
  },
  {
    "path": "devapp-2.2.0/server/main.js",
    "content": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '../imports/api/links'\nimport { RandomCollection } from '../imports/api/random'\n\nfunction insertLink(title, url) {\n  LinksCollection.insert({ title, url, createdAt: new Date() })\n}\n\nMeteor.methods({\n  echo(echo) {\n    return echo\n  },\n})\n\nMeteor.startup(() => {\n  if (LinksCollection.find().count() === 0) {\n    insertLink(\n      'Do the Tutorial',\n      'https://www.meteor.com/tutorials/react/creating-an-app',\n    )\n\n    insertLink('Follow the Guide', 'http://guide.meteor.com')\n\n    insertLink('Read the Docs', 'https://docs.meteor.com')\n\n    insertLink('Discussions', 'https://forums.meteor.com')\n  }\n\n  RandomCollection.remove({})\n\n  let counter = 1\n\n  new Array(1000)\n    .fill(null)\n    .map(() => ({\n      name: 'Lorem Ipsum '.concat(String(counter)),\n      number: counter++,\n    }))\n    .forEach(item => {\n      RandomCollection.insert(item)\n    })\n})\n\nMeteor.publish('random1to100', function () {\n  return RandomCollection.find({\n    number: { $gte: 1, $lte: 100 },\n  })\n})\n\nMeteor.publish('random101to200', function () {\n  return RandomCollection.find({\n    number: { $gte: 101, $lte: 200 },\n  })\n})\n\nMeteor.publish('random201to300', function () {\n  return RandomCollection.find({\n    number: { $gte: 201, $lte: 300 },\n  })\n})\n\nMeteor.publish('random301to400', function () {\n  return RandomCollection.find({\n    number: { $gte: 301, $lte: 400 },\n  })\n})\n\nMeteor.publish('random401to500', function () {\n  return RandomCollection.find({\n    number: { $gte: 401, $lte: 500 },\n  })\n})\n\nMeteor.publish('random501to600', function () {\n  return RandomCollection.find({\n    number: { $gte: 501, $lte: 600 },\n  })\n})\n\nMeteor.publish('random601to700', function () {\n  return RandomCollection.find({\n    number: { $gte: 601, $lte: 700 },\n  })\n})\n\nMeteor.publish('random701to800', function () {\n  return RandomCollection.find({\n    number: { $gte: 701, $lte: 800 },\n  })\n})\n\nMeteor.publish('random801to900', function () {\n  return RandomCollection.find({\n    number: { $gte: 801, $lte: 900 },\n  })\n})\n\nMeteor.publish('random901to1000', function () {\n  return RandomCollection.find({\n    number: { $gte: 901, $lte: 1000 },\n  })\n})\n"
  },
  {
    "path": "devapp-2.2.0/tests/main.js",
    "content": "import assert from 'assert'\n\ndescribe('devapp-2.2.0', function () {\n  it('package.json has correct name', async function () {\n    const { name } = await import('../package.json')\n    assert.strictEqual(name, 'devapp-2.2.0')\n  })\n\n  if (Meteor.isClient) {\n    it('client is not server', function () {\n      assert.strictEqual(Meteor.isServer, false)\n    })\n  }\n\n  if (Meteor.isServer) {\n    it('server is not client', function () {\n      assert.strictEqual(Meteor.isClient, false)\n    })\n  }\n})\n"
  },
  {
    "path": "devapp-2.2.4/.gitignore",
    "content": "node_modules/\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/.finished-upgraders",
    "content": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should check it into version control\n# with your project.\n\nnotices-for-0.9.0\nnotices-for-0.9.1\n0.9.4-platform-file\nnotices-for-facebook-graph-api-2\n1.2.0-standard-minifiers-package\n1.2.0-meteor-platform-split\n1.2.0-cordova-changes\n1.2.0-breaking-changes\n1.3.0-split-minifiers-package\n1.4.0-remove-old-dev-bundle-link\n1.4.1-add-shell-server-package\n1.4.3-split-account-service-packages\n1.5-add-dynamic-import-package\n1.7-split-underscore-from-meteor-base\n1.8.3-split-jquery-from-blaze\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/.gitignore",
    "content": "local\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/.id",
    "content": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this directory.\n# It can be used for purposes such as:\n#   - ensuring you don't accidentally deploy one app on top of another\n#   - providing package authors with aggregated statistics\n\nazmndnm89g3.mkgtn1ux8hf9\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/packages",
    "content": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into your repository.\n#\n# 'meteor add' and 'meteor remove' will edit this file for you,\n# but you can also edit it by hand.\n\nmeteor-base@1.4.0             # Packages every Meteor app needs to have\nmobile-experience@1.1.0       # Packages for a great mobile UX\nmongo@1.11.0                   # The database Meteor supports right now\nreactive-var@1.0.11            # Reactive variable for tracker\n\nstandard-minifier-css@1.7.2   # CSS minifier run for production mode\nstandard-minifier-js@2.6.0    # JS minifier run for production mode\nes5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers\necmascript@0.15.3              # Enable ECMAScript2015+ syntax in app code\ntypescript@4.3.5              # Enable TypeScript syntax in .ts and .tsx modules\nshell-server@0.5.0            # Server-side component of the `meteor shell` command\nhot-module-replacement@0.2.0  # Update client in development without reloading the page\n\ninsecure@1.0.7                # Allow all DB writes from clients (for prototyping)\nstatic-html             # Define static page content in .html files\nreact-meteor-data       # React higher-order component for reactively tracking Meteor data\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/platforms",
    "content": "server\nbrowser\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/release",
    "content": "METEOR@2.2.4\n"
  },
  {
    "path": "devapp-2.2.4/.meteor/versions",
    "content": "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@1.1.3\nboilerplate-generator@1.7.1\ncaching-compiler@1.2.2\ncaching-html-compiler@1.2.1\ncallback-hook@1.3.1\ncheck@1.3.1\nddp@1.4.0\nddp-client@2.4.1\nddp-common@1.4.0\nddp-server@2.3.3\ndiff-sequence@1.1.1\ndynamic-import@0.6.0\necmascript@0.15.3\necmascript-runtime@0.7.0\necmascript-runtime-client@0.11.1\necmascript-runtime-server@0.10.1\nejson@1.1.1\nes5-shim@4.8.0\nfetch@0.1.1\ngeojson-utils@1.0.10\nhot-code-push@1.0.4\nhot-module-replacement@0.2.1\nhtml-tools@1.1.3\nhtmljs@1.1.1\nid-map@1.1.1\ninsecure@1.0.7\ninter-process-messaging@0.1.1\nlaunch-screen@1.2.1\nlivedata@1.0.18\nlogging@1.2.0\nmeteor@1.9.3\nmeteor-base@1.4.0\nminifier-css@1.5.4\nminifier-js@2.6.1\nminimongo@1.6.2\nmobile-experience@1.1.0\nmobile-status-bar@1.1.0\nmodern-browsers@0.1.7\nmodules@0.16.0\nmodules-runtime@0.12.0\nmodules-runtime-hot@0.13.0\nmongo@1.11.1\nmongo-decimal@0.1.2\nmongo-dev-server@1.1.0\nmongo-id@1.0.8\nnpm-mongo@3.9.1\nordered-dict@1.1.0\npromise@0.11.2\nrandom@1.2.0\nreact-fast-refresh@0.1.1\nreact-meteor-data@2.5.1\nreactive-var@1.0.11\nreload@1.3.1\nretry@1.1.0\nroutepolicy@1.1.0\nshell-server@0.5.0\nsocket-stream-client@0.3.3\nspacebars-compiler@1.3.1\nstandard-minifier-css@1.7.3\nstandard-minifier-js@2.6.1\nstatic-html@1.3.2\ntemplating-tools@1.2.2\ntracker@1.2.0\ntypescript@4.3.5\nunderscore@1.0.10\nwebapp@1.10.1\nwebapp-hashing@1.1.0\n"
  },
  {
    "path": "devapp-2.2.4/client/main.css",
    "content": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-2.2.4/client/main.html",
    "content": "<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",
    "content": "import React from 'react'\nimport { Meteor } from 'meteor/meteor'\nimport { render } from 'react-dom'\nimport { App } from '../imports/ui/App'\n\nimport '../imports/api/links'\nimport '../imports/api/random'\n\nMeteor.startup(() => {\n  render(<App />, document.getElementById('react-target'))\n})\n"
  },
  {
    "path": "devapp-2.2.4/imports/api/links.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-2.2.4/imports/api/random.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const RandomCollection = new Mongo.Collection('random')\n"
  },
  {
    "path": "devapp-2.2.4/imports/ui/App.jsx",
    "content": "import React, { useEffect, useRef, useState } from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { RandomCollection } from '../api/random'\n\nexport const App = () => {\n  const [isSpamming, setSpamming] = useState(false)\n  const spammerRef = useRef(0)\n\n  const r1to100 = useTracker(() => {\n    const handle = Meteor.subscribe('random1to100')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r101to200 = useTracker(() => {\n    const handle = Meteor.subscribe('random101to200')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r201to300 = useTracker(() => {\n    const handle = Meteor.subscribe('random201to300')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r301to400 = useTracker(() => {\n    const handle = Meteor.subscribe('random301to400')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r401to500 = useTracker(() => {\n    const handle = Meteor.subscribe('random401to500')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r501to600 = useTracker(() => {\n    const handle = Meteor.subscribe('random501to600')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r601to700 = useTracker(() => {\n    const handle = Meteor.subscribe('random601to700')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r701to800 = useTracker(() => {\n    const handle = Meteor.subscribe('random701to800')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r801to900 = useTracker(() => {\n    const handle = Meteor.subscribe('random801to900')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  const r901to1000 = useTracker(() => {\n    const handle = Meteor.subscribe('random901to1000')\n    return {\n      isLoading: !handle.ready(),\n      docs: RandomCollection.find({}).fetch(),\n    }\n  }, [])\n\n  useEffect(() => {\n    if (isSpamming && !spammerRef.current) {\n      spammerRef.current = setInterval(() => {\n        Meteor.call('echo', 'Echo')\n      }, 100)\n    } else {\n      if (spammerRef.current) {\n        clearInterval(spammerRef.current)\n        spammerRef.current = 0\n      }\n    }\n  }, [isSpamming])\n\n  return (\n    <div>\n      <h1>Welcome to Meteor!</h1>\n\n      <button\n        onClick={() => {\n          setSpamming(!isSpamming)\n        }}\n      >\n        {isSpamming ? 'Spam [On]' : 'Spam [Off]'}\n      </button>\n\n      <button\n        onClick={() => {\n          Meteor.call('echo', 'Echo')\n        }}\n      >\n        String\n      </button>\n\n      <button\n        onClick={() => {\n          Meteor.call('echo', {\n            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.',\n          })\n        }}\n      >\n        Object\n      </button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.2.4/imports/ui/Hello.jsx",
    "content": "import React, { useState } from 'react'\n\nexport const Hello = () => {\n  const [counter, setCounter] = useState(0)\n\n  const increment = () => {\n    setCounter(counter + 1)\n  }\n\n  return (\n    <div>\n      <button onClick={increment}>Click Me</button>\n      <p>You've pressed the button {counter} times.</p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.2.4/imports/ui/Info.jsx",
    "content": "import React from 'react'\nimport { useTracker } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/links'\n\nexport const Info = () => {\n  const links = useTracker(() => {\n    return LinksCollection.find().fetch()\n  })\n\n  return (\n    <div>\n      <h2>Learn Meteor!</h2>\n      <ul>\n        {links.map(link => (\n          <li key={link._id}>\n            <a href={link.url} target='_blank' rel='noreferrer'>\n              {link.title}\n            </a>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-2.2.4/package.json",
    "content": "{\n  \"name\": \"devapp-2.2.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once --driver-package meteortesting:mocha\",\n    \"test-app\": \"TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha\",\n    \"visualize\": \"meteor --production --extra-packages bundle-visualizer\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.11.2\",\n    \"meteor-node-stubs\": \"^1.0.1\",\n    \"react\": \"^16.13.1\",\n    \"react-dom\": \"^16.13.1\"\n  },\n  \"meteor\": {\n    \"mainModule\": {\n      \"client\": \"client/main.jsx\",\n      \"server\": \"server/main.js\"\n    },\n    \"testModule\": \"tests/main.js\"\n  }\n}\n"
  },
  {
    "path": "devapp-2.2.4/server/main.js",
    "content": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '../imports/api/links'\nimport { RandomCollection } from '../imports/api/random'\n\nfunction insertLink(title, url) {\n  LinksCollection.insert({ title, url, createdAt: new Date() })\n}\n\nMeteor.methods({\n  echo(echo) {\n    return echo\n  },\n})\n\nMeteor.startup(() => {\n  if (LinksCollection.find().count() === 0) {\n    insertLink(\n      'Do the Tutorial',\n      'https://www.meteor.com/tutorials/react/creating-an-app',\n    )\n\n    insertLink('Follow the Guide', 'http://guide.meteor.com')\n\n    insertLink('Read the Docs', 'https://docs.meteor.com')\n\n    insertLink('Discussions', 'https://forums.meteor.com')\n  }\n\n  RandomCollection.remove({})\n\n  let counter = 1\n\n  new Array(1000)\n    .fill(null)\n    .map(() => ({\n      name: 'Lorem Ipsum '.concat(String(counter)),\n      number: counter++,\n    }))\n    .forEach(item => {\n      RandomCollection.insert(item)\n    })\n})\n\nMeteor.publish('random1to100', function () {\n  return RandomCollection.find({\n    number: { $gte: 1, $lte: 100 },\n  })\n})\n\nMeteor.publish('random101to200', function () {\n  return RandomCollection.find({\n    number: { $gte: 101, $lte: 200 },\n  })\n})\n\nMeteor.publish('random201to300', function () {\n  return RandomCollection.find({\n    number: { $gte: 201, $lte: 300 },\n  })\n})\n\nMeteor.publish('random301to400', function () {\n  return RandomCollection.find({\n    number: { $gte: 301, $lte: 400 },\n  })\n})\n\nMeteor.publish('random401to500', function () {\n  return RandomCollection.find({\n    number: { $gte: 401, $lte: 500 },\n  })\n})\n\nMeteor.publish('random501to600', function () {\n  return RandomCollection.find({\n    number: { $gte: 501, $lte: 600 },\n  })\n})\n\nMeteor.publish('random601to700', function () {\n  return RandomCollection.find({\n    number: { $gte: 601, $lte: 700 },\n  })\n})\n\nMeteor.publish('random701to800', function () {\n  return RandomCollection.find({\n    number: { $gte: 701, $lte: 800 },\n  })\n})\n\nMeteor.publish('random801to900', function () {\n  return RandomCollection.find({\n    number: { $gte: 801, $lte: 900 },\n  })\n})\n\nMeteor.publish('random901to1000', function () {\n  return RandomCollection.find({\n    number: { $gte: 901, $lte: 1000 },\n  })\n})\n"
  },
  {
    "path": "devapp-2.2.4/tests/main.js",
    "content": "import assert from 'assert'\n\ndescribe('devapp-2.2.4', function () {\n  it('package.json has correct name', async function () {\n    const { name } = await import('../package.json')\n    assert.strictEqual(name, 'devapp-2.2.4')\n  })\n\n  if (Meteor.isClient) {\n    it('client is not server', function () {\n      assert.strictEqual(Meteor.isServer, false)\n    })\n  }\n\n  if (Meteor.isServer) {\n    it('server is not client', function () {\n      assert.strictEqual(Meteor.isClient, false)\n    })\n  }\n})\n"
  },
  {
    "path": "devapp-3.4/.gitignore",
    "content": "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",
    "content": "# This file contains information which helps Meteor properly upgrade your\n# app when you run 'meteor update'. You should check it into version control\n# with your project.\n\nnotices-for-0.9.0\nnotices-for-0.9.1\n0.9.4-platform-file\nnotices-for-facebook-graph-api-2\n1.2.0-standard-minifiers-package\n1.2.0-meteor-platform-split\n1.2.0-cordova-changes\n1.2.0-breaking-changes\n1.3.0-split-minifiers-package\n1.4.0-remove-old-dev-bundle-link\n1.4.1-add-shell-server-package\n1.4.3-split-account-service-packages\n1.5-add-dynamic-import-package\n1.7-split-underscore-from-meteor-base\n1.8.3-split-jquery-from-blaze\n"
  },
  {
    "path": "devapp-3.4/.meteor/.gitignore",
    "content": "local\n"
  },
  {
    "path": "devapp-3.4/.meteor/.id",
    "content": "# This file contains a token that is unique to your project.\n# Check it into your repository along with the rest of this directory.\n# It can be used for purposes such as:\n#   - ensuring you don't accidentally deploy one app on top of another\n#   - providing package authors with aggregated statistics\n\nhquoz8fwpx2o.w95c0f55ay3\n"
  },
  {
    "path": "devapp-3.4/.meteor/packages",
    "content": "# Meteor packages used by this project, one per line.\n# Check this file (and the other files in this directory) into your repository.\n#\n# 'meteor add' and 'meteor remove' will edit this file for you,\n# but you can also edit it by hand.\n\nmeteor-base@1.5.2             # Packages every Meteor app needs to have\nmobile-experience@1.1.2       # Packages for a great mobile UX\nmongo@2.2.0                   # The database Meteor supports right now\nreactive-var@1.0.13            # Reactive variable for tracker\n\nstandard-minifier-css@1.10.0   # CSS minifier run for production mode\nstandard-minifier-js@3.2.0    # JS minifier run for production mode\nes5-shim@4.8.1                # ECMAScript 5 compatibility for older browsers\necmascript@0.17.0              # Enable ECMAScript2015+ syntax in app code\ntypescript@5.9.3              # Enable TypeScript syntax in .ts and .tsx modules\nshell-server@0.7.0            # Server-side component of the `meteor shell` command\nhot-module-replacement@0.5.4  # Update client in development without reloading the page\n\n\nstatic-html@1.5.0             # Define static page content in .html files\nreact-meteor-data       # React higher-order component for reactively tracking Meteor data\n\nrspack                  # Integrate Rspack into Meteor for client and server app bundling\n"
  },
  {
    "path": "devapp-3.4/.meteor/platforms",
    "content": "server\nbrowser\n"
  },
  {
    "path": "devapp-3.4/.meteor/release",
    "content": "METEOR@3.4\n"
  },
  {
    "path": "devapp-3.4/.meteor/versions",
    "content": "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-generator@2.1.0\ncaching-compiler@2.0.1\ncallback-hook@1.6.1\ncheck@1.5.0\ncore-runtime@1.0.0\nddp@1.4.2\nddp-client@3.1.1\nddp-common@1.4.4\nddp-server@3.1.2\ndiff-sequence@1.1.3\ndynamic-import@0.7.4\necmascript@0.17.0\necmascript-runtime@0.8.3\necmascript-runtime-client@0.12.3\necmascript-runtime-server@0.11.1\nejson@1.1.5\nes5-shim@4.8.1\nfacts-base@1.0.2\nfetch@0.1.6\ngeojson-utils@1.0.12\nhot-code-push@1.0.5\nhot-module-replacement@0.5.4\nid-map@1.2.0\ninter-process-messaging@0.1.2\nlaunch-screen@2.0.1\nlogging@1.3.6\nmeteor@2.2.0\nmeteor-base@1.5.2\nminifier-css@2.0.1\nminifier-js@3.1.0\nminimongo@2.0.5\nmobile-experience@1.1.2\nmobile-status-bar@1.1.1\nmodern-browsers@0.2.3\nmodules@0.20.3\nmodules-runtime@0.13.2\nmodules-runtime-hot@0.14.3\nmongo@2.2.0\nmongo-decimal@0.2.0\nmongo-dev-server@1.1.1\nmongo-id@1.0.9\nnpm-mongo@6.16.1\nordered-dict@1.2.0\npromise@1.0.0\nrandom@1.2.2\nreact-fast-refresh@0.3.0\nreact-meteor-data@4.0.1\nreactive-var@1.0.13\nreload@1.3.2\nretry@1.1.1\nroutepolicy@1.1.2\nrspack@1.0.0\nshell-server@0.7.0\nsocket-stream-client@0.6.1\nstandard-minifier-css@1.10.0\nstandard-minifier-js@3.2.0\nstatic-html@1.5.0\nstatic-html-tools@1.0.0\ntools-core@1.0.0\ntracker@1.3.4\ntypescript@5.9.3\nwebapp@2.1.0\nwebapp-hashing@1.1.2\nzodern:types@1.0.13\n"
  },
  {
    "path": "devapp-3.4/.swcrc",
    "content": "{\n  \"jsc\": {\n    \"transform\": {\n      \"react\": {\n        \"runtime\": \"automatic\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "devapp-3.4/client/main.css",
    "content": "body {\n  padding: 10px;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "devapp-3.4/client/main.html",
    "content": "<head>\n  <title>devapp-3.4</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <link\n    href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\"\n    rel=\"stylesheet\"\n  />\n</head>\n\n<body>\n  <div id=\"react-target\"></div>\n</body>\n"
  },
  {
    "path": "devapp-3.4/client/main.jsx",
    "content": "import { createRoot } from 'react-dom/client'\nimport { Meteor } from 'meteor/meteor'\nimport { App } from '/imports/ui/App'\nimport '/imports/ui/styles.css'\n\nMeteor.startup(() => {\n  const container = document.getElementById('react-target')\n  const root = createRoot(container)\n  root.render(<App />)\n})\n"
  },
  {
    "path": "devapp-3.4/imports/api/links.js",
    "content": "import { Mongo } from 'meteor/mongo'\n\nexport const LinksCollection = new Mongo.Collection('links')\n"
  },
  {
    "path": "devapp-3.4/imports/ui/App.jsx",
    "content": "import { Counter } from './Counter.jsx'\nimport { Header } from './Header.jsx'\nimport { Info } from './Info.jsx'\n\nexport const App = () => (\n  <div className='page'>\n    <Header />\n    <main className='main'>\n      <Counter />\n      <Info />\n    </main>\n  </div>\n)\n"
  },
  {
    "path": "devapp-3.4/imports/ui/Counter.jsx",
    "content": "import { useState } from 'react'\n\nexport const Counter = () => {\n  const [counter, setCounter] = useState(0)\n\n  const increment = () => {\n    setCounter(counter + 1)\n  }\n\n  return (\n    <div className='counter-card card'>\n      <div className='counter-content'>\n        <button className='button' onClick={increment}>\n          Click Me\n        </button>\n        <p className='counter-text'>\n          You've pressed the button{' '}\n          <span className='counter-value'>{counter}</span>{' '}\n          {counter === 1 ? 'time' : 'times'}.\n        </p>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-3.4/imports/ui/Header.jsx",
    "content": "import MeteorLogo from './meteor-logo.svg'\n\nexport const Header = () => {\n  return (\n    <div className='header'>\n      <nav className='nav container'>\n        <div className='logo-container'>\n          <MeteorLogo className='logo' />\n        </div>\n        <h1 className='page-title'>Welcome to Meteor!</h1>\n      </nav>\n    </div>\n  )\n}\n"
  },
  {
    "path": "devapp-3.4/imports/ui/Info.jsx",
    "content": "import { useFind, useSubscribe } from 'meteor/react-meteor-data'\nimport { LinksCollection } from '../api/links'\n\nexport const Info = () => {\n  const isLoading = useSubscribe('links')\n  const links = useFind(() => LinksCollection.find())\n\n  if (isLoading()) {\n    return <div>Loading...</div>\n  }\n\n  return (\n    <section>\n      <h2 className='section-title'>Learn Meteor!</h2>\n      <ul className='resources-grid'>\n        {links.map(link => (\n          <li className='section' key={link._id}>\n            <a href={link.url} className='resource-link' target='_blank'>\n              <div className='resource-card card'>\n                <div className='resource-content'>\n                  <span className='resource-title'>{link.title}</span>\n                </div>\n              </div>\n            </a>\n          </li>\n        ))}\n      </ul>\n    </section>\n  )\n}\n"
  },
  {
    "path": "devapp-3.4/imports/ui/styles.css",
    "content": "/* this file is imported in client/main.jsx */\n\n:root {\n  /* Colors */\n  --color-background: hsl(210, 20%, 98%);\n  --color-foreground: hsl(220, 20%, 15%);\n  --color-card: hsl(0, 0%, 100%);\n  --color-primary: hsl(4, 70%, 55%);\n  --color-primary-hover: hsl(4, 70%, 45%);\n  --color-muted: hsl(220, 10%, 50%);\n  --color-border: hsl(220, 14%, 90%);\n\n  /* Shadows */\n  --shadow-card:\n    0 1px 3px 0 hsl(220 20% 15% / 0.04), 0 1px 2px -1px hsl(220 20% 15% / 0.04);\n  --shadow-card-hover:\n    0 10px 15px -3px hsl(220 20% 15% / 0.08),\n    0 4px 6px -4px hsl(220 20% 15% / 0.04);\n\n  /* Spacing */\n  --spacing-xs: 0.25rem;\n  --spacing-sm: 0.5rem;\n  --spacing-md: 1rem;\n  --spacing-lg: 1.5rem;\n  --spacing-xl: 2rem;\n  --spacing-2xl: 3rem;\n\n  /* Border radius */\n  --radius: 0.75rem;\n  --radius-sm: 0.5rem;\n\n  /* Transitions */\n  --transition-fast: 150ms ease;\n  --transition-normal: 200ms ease;\n  --transition-slow: 250ms ease;\n}\n\n/* ============ Reset & Base Styles ============ */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n\nbody {\n  font-family:\n    'Inter',\n    -apple-system,\n    BlinkMacSystemFont,\n    'Segoe UI',\n    Roboto,\n    sans-serif;\n  background-color: var(--color-background);\n  color: var(--color-foreground);\n  line-height: 1.5;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\na {\n  text-decoration: none;\n  color: inherit;\n}\n\n/* ============ Layout ============ */\n.page {\n  min-height: 100vh;\n  background-color: var(--color-background);\n}\n\n.container {\n  width: 100%;\n  max-width: 1280px;\n  margin: 0 auto;\n  padding-left: var(--spacing-md);\n  padding-right: var(--spacing-md);\n}\n\n@media (min-width: 768px) {\n  .container {\n    padding-left: var(--spacing-lg);\n    padding-right: var(--spacing-lg);\n  }\n}\n\n/* ============ Header / Navigation ============ */\n.header {\n  border-bottom: 1px solid var(--color-border);\n  background-color: var(--color-card);\n  border-radius: var(--radius);\n}\n\n.nav {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  height: 4rem;\n}\n\n.logo-container {\n  display: flex;\n  align-items: center;\n  gap: var(--spacing-sm);\n}\n\n.logo {\n  width: 4rem;\n  height: 4rem;\n}\n\n.logo-text {\n  font-weight: 600;\n  color: var(--color-foreground);\n  display: none;\n}\n\n@media (min-width: 640px) {\n  .logo-text {\n    display: inline;\n  }\n}\n\n.page-title {\n  font-size: 1.25rem;\n  font-weight: 700;\n  color: var(--color-foreground);\n  letter-spacing: -0.025em;\n}\n\n@media (min-width: 768px) {\n  .page-title {\n    font-size: 1.5rem;\n  }\n}\n\n.nav-link {\n  font-size: 0.875rem;\n  font-weight: 500;\n  color: var(--color-primary);\n  transition: opacity var(--transition-fast);\n}\n\n.nav-link:hover {\n  opacity: 0.8;\n}\n\n/* ============ Main Content ============ */\n.main {\n  padding-top: var(--spacing-xl);\n  padding-bottom: var(--spacing-xl);\n}\n\n@media (min-width: 768px) {\n  .main {\n    padding-top: var(--spacing-2xl);\n    padding-bottom: var(--spacing-2xl);\n  }\n}\n\n/* ============ Card Component ============ */\n.card {\n  background-color: var(--color-card);\n  border-radius: var(--radius);\n  box-shadow: var(--shadow-card);\n  border: 1px solid var(--color-border);\n}\n\n/* ============ Counter Section ============ */\n.counter-card {\n  padding: var(--spacing-lg);\n  margin-bottom: 2.5rem;\n}\n\n@media (min-width: 768px) {\n  .counter-card {\n    padding: var(--spacing-xl);\n    margin-bottom: var(--spacing-2xl);\n  }\n}\n\n.counter-content {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: var(--spacing-md);\n}\n\n@media (min-width: 640px) {\n  .counter-content {\n    flex-direction: row;\n  }\n}\n\n.counter-text {\n  color: var(--color-muted);\n  text-align: center;\n}\n\n@media (min-width: 640px) {\n  .counter-text {\n    text-align: left;\n  }\n}\n\n.counter-value {\n  font-weight: 600;\n  color: var(--color-foreground);\n}\n\n/* ============ Button Component ============ */\n.button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 120px;\n  padding: 0.625rem 1.5rem;\n  font-size: 0.875rem;\n  font-weight: 500;\n  font-family: inherit;\n  color: white;\n  background-color: var(--color-primary);\n  border: none;\n  border-radius: var(--radius-sm);\n  cursor: pointer;\n  transition:\n    background-color var(--transition-normal),\n    transform var(--transition-fast);\n}\n\n.button:hover {\n  background-color: var(--color-primary-hover);\n}\n\n.button:active {\n  transform: scale(0.98);\n}\n\n.button:focus-visible {\n  outline: 2px solid var(--color-primary);\n  outline-offset: 2px;\n}\n\n/* ============ Resources Section ============ */\n.section-title {\n  font-size: 1.5rem;\n  font-weight: 700;\n  color: var(--color-foreground);\n  margin-bottom: var(--spacing-lg);\n  letter-spacing: -0.025em;\n}\n\n.resources-grid {\n  list-style-type: none;\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: var(--spacing-md);\n}\n\n@media (min-width: 640px) {\n  .resources-grid {\n    grid-template-columns: repeat(2, 1fr);\n  }\n}\n\n/* ============ Resource Card ============ */\n.resource-link {\n  display: block;\n}\n\n.resource-card {\n  padding: 1.25rem;\n  transition:\n    box-shadow var(--transition-slow),\n    transform var(--transition-slow);\n}\n\n.resource-card:hover {\n  box-shadow: var(--shadow-card-hover);\n  transform: translateY(-2px);\n}\n\n.resource-content {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.resource-title {\n  font-weight: 500;\n  color: var(--color-foreground);\n  transition: color var(--transition-fast);\n}\n\n.resource-link:hover .resource-title {\n  color: var(--color-primary);\n}\n\n.resource-icon {\n  width: 1rem;\n  height: 1rem;\n  color: var(--color-muted);\n  opacity: 0;\n  transition:\n    opacity var(--transition-fast),\n    color var(--transition-fast);\n}\n\n.resource-link:hover .resource-icon {\n  opacity: 1;\n  color: var(--color-primary);\n}\n"
  },
  {
    "path": "devapp-3.4/package.json",
    "content": "{\n  \"name\": \"devapp-3.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"meteor run\",\n    \"test\": \"meteor test --once --driver-package meteortesting:mocha\",\n    \"test-app\": \"TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha\",\n    \"visualize\": \"meteor --production --extra-packages bundle-visualizer\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.23.5\",\n    \"@swc/helpers\": \"^0.5.17\",\n    \"meteor-node-stubs\": \"^1.2.12\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@meteorjs/rspack\": \"^1.0.0\",\n    \"@rsdoctor/rspack-plugin\": \"^1.2.3\",\n    \"@rspack/cli\": \"^1.7.1\",\n    \"@rspack/core\": \"^1.7.1\",\n    \"@rspack/plugin-react-refresh\": \"^1.4.3\",\n    \"@svgr/webpack\": \"^8.1.0\",\n    \"react-refresh\": \"^0.17.0\"\n  },\n  \"meteor\": {\n    \"mainModule\": {\n      \"client\": \"client/main.jsx\",\n      \"server\": \"server/main.js\"\n    },\n    \"testModule\": \"tests/main.js\",\n    \"modern\": true\n  }\n}\n"
  },
  {
    "path": "devapp-3.4/rspack.config.js",
    "content": "const { defineConfig } = require('@meteorjs/rspack')\n\n/**\n * Rspack configuration for Meteor projects.\n *\n * Provides typed flags on the `Meteor` object, such as:\n * - `Meteor.isClient` / `Meteor.isServer`\n * - `Meteor.isDevelopment` / `Meteor.isProduction`\n * - …and other flags available\n *\n * Use these flags to adjust your build settings based on environment.\n */\nmodule.exports = defineConfig(Meteor => {\n  return {\n    module: {\n      rules: [\n        // Add support for importing SVGs as React components\n        {\n          test: /\\.svg$/i,\n          issuer: /\\.[jt]sx?$/,\n          use: ['@svgr/webpack'],\n        },\n      ],\n    },\n  }\n})\n"
  },
  {
    "path": "devapp-3.4/server/main.js",
    "content": "import { Meteor } from 'meteor/meteor'\nimport { LinksCollection } from '/imports/api/links'\nimport { Random } from 'meteor/random'\n\nasync function insertLink({ title, url }) {\n  await LinksCollection.insertAsync({ title, url, createdAt: new Date() })\n}\n\nMeteor.startup(async () => {\n  // If the Links collection is empty, add some data.\n  if ((await LinksCollection.find().countAsync()) === 0) {\n    await insertLink({\n      title: 'Do the Tutorial',\n      url: 'https://docs.meteor.com/tutorials/react/',\n    })\n\n    await insertLink({\n      title: 'Follow the Guide',\n      url: 'https://docs.meteor.com/tutorials/application-structure/',\n    })\n\n    await insertLink({\n      title: 'Read the Docs',\n      url: 'https://docs.meteor.com',\n    })\n\n    await insertLink({\n      title: 'Discussions',\n      url: 'https://forums.meteor.com',\n    })\n\n    await insertLink({\n      title: 'Join us on Discord',\n      url: 'https://discord.gg/6mS3wHNg',\n    })\n\n    await insertLink({\n      title: 'Deploying in Galaxy',\n      url: 'https://www.meteor.com/hosting',\n    })\n  }\n\n  // We publish the entire Links collection to all clients.\n  // In order to be fetched in real-time to the clients\n  Meteor.publish('links', function () {\n    return LinksCollection.find()\n  })\n})\n\nMeteor.methods({\n  about() {\n    return `This is a Meteor application running React with React Router. this is a generated id: ${Random.id()}`\n  },\n})\n"
  },
  {
    "path": "devapp-3.4/tests/main.js",
    "content": "import assert from 'assert'\n\ndescribe('devapp-3.4', function () {\n  it('package.json has correct name', async function () {\n    const { name } = await import('../package.json')\n    assert.strictEqual(name, 'devapp-3.4')\n  })\n\n  if (Meteor.isClient) {\n    it('client is not server', function () {\n      assert.strictEqual(Meteor.isServer, false)\n    })\n  }\n\n  if (Meteor.isServer) {\n    it('server is not client', function () {\n      assert.strictEqual(Meteor.isClient, false)\n    })\n  }\n})\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import js from '@eslint/js'\nimport typescript from '@typescript-eslint/eslint-plugin'\nimport typescriptParser from '@typescript-eslint/parser'\nimport react from 'eslint-plugin-react'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport prettier from 'eslint-plugin-prettier'\nimport unicorn from 'eslint-plugin-unicorn'\nimport globals from 'globals'\n\nexport default [\n  {\n    ignores: [\n      'node_modules/**',\n      'extension/**',\n      'devapp-*/**',\n      '.yarn/**',\n      'webpack/**',\n    ],\n  },\n  // Base config for all files\n  {\n    files: ['**/*.{js,jsx,mjs,cjs}'],\n    plugins: {\n      react,\n      'react-hooks': reactHooks,\n      prettier,\n      unicorn,\n    },\n    languageOptions: {\n      ecmaVersion: 2020,\n      sourceType: 'module',\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n        ...globals.mocha,\n        Meteor: 'readonly',\n        Helene: 'readonly',\n      },\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n    },\n    rules: {\n      ...js.configs.recommended.rules,\n      ...react.configs.recommended.rules,\n      ...unicorn.configs.recommended.rules,\n      'no-console': 0,\n      'react/prop-types': 0,\n      'react/jsx-curly-spacing': 0,\n      'react/display-name': 0,\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': 0,\n      'no-inner-declarations': 0,\n      'react/no-unescaped-entities': 0,\n      'react/react-in-jsx-scope': 0,\n      // Unicorn adjustments for this project\n      'unicorn/prevent-abbreviations': 0,\n      'unicorn/filename-case': 0,\n      'unicorn/no-null': 0,\n      'unicorn/prefer-module': 0,\n      'unicorn/prefer-node-protocol': 0,\n      'prettier/prettier': 'error',\n    },\n  },\n  // TypeScript specific config\n  {\n    files: ['**/*.{ts,tsx}'],\n    plugins: {\n      '@typescript-eslint': typescript,\n      react,\n      'react-hooks': reactHooks,\n      prettier,\n      unicorn,\n    },\n    languageOptions: {\n      ecmaVersion: 2020,\n      sourceType: 'module',\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n        ...globals.mocha,\n        Meteor: 'readonly',\n        Helene: 'readonly',\n      },\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n    },\n    rules: {\n      ...js.configs.recommended.rules,\n      ...typescript.configs.recommended.rules,\n      ...react.configs.recommended.rules,\n      ...unicorn.configs.recommended.rules,\n      'no-console': 0,\n      'react/prop-types': 0,\n      'react/jsx-curly-spacing': 0,\n      'react/display-name': 0,\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': 0,\n      'no-inner-declarations': 0,\n      'react/no-unescaped-entities': 0,\n      'react/react-in-jsx-scope': 0,\n      '@typescript-eslint/no-non-null-assertion': 0,\n      '@typescript-eslint/no-explicit-any': 0,\n      '@typescript-eslint/no-empty-interface': 0,\n      '@typescript-eslint/explicit-module-boundary-types': 0,\n      '@typescript-eslint/no-unused-vars': 0,\n      '@typescript-eslint/no-this-alias': 0,\n      '@typescript-eslint/ban-ts-comment': 0,\n      '@typescript-eslint/no-namespace': 0,\n      'no-undef': 0, // TypeScript handles this\n      // Unicorn adjustments for this project\n      'unicorn/prevent-abbreviations': 0,\n      'unicorn/filename-case': 0,\n      'unicorn/no-null': 0,\n      'unicorn/prefer-module': 0,\n      'unicorn/prefer-node-protocol': 0,\n      'prettier/prettier': 'error',\n    },\n  },\n]\n"
  },
  {
    "path": "extension/devtools-panel.html",
    "content": "<!doctype html>\n<html data-theme=\"corporate\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\"\n    />\n    <title>Panel</title>\n  </head>\n  <body>\n    <div id=\"panel\" class=\"bp4-dark\"></div>\n    <script src=\"/dist/bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "extension/devtools.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\"\n    />\n    <title>Developer Tools</title>\n  </head>\n  <body>\n    <!-- The sole purpose of this file is to load the JavaScript file as Chrome does not render it -->\n\n    <script src=\"/dist/devtools.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "extension/manifest-v2.json",
    "content": "{\n  \"manifest_version\": 2,\n  \"name\": \"Meteor DevTools Evolved\",\n  \"description\": \"The Meteor framework development tool belt, evolved.\",\n  \"version\": \"1.8.1\",\n  \"author\": \"Leonardo Venturini\",\n  \"icons\": {\n    \"16\": \"icons/meteor-16.png\",\n    \"48\": \"icons/meteor-48.png\",\n    \"128\": \"icons/meteor-128.png\"\n  },\n  \"browser_action\": {\n    \"default_title\": \"Meteor\"\n  },\n  \"background\": {\n    \"scripts\": [\"/dist/background.js\"],\n    \"persistent\": false\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"/dist/content.js\"],\n      \"run_at\": \"document_start\",\n      \"all_frames\": true\n    }\n  ],\n  \"permissions\": [\n    \"https://api.github.com/*\",\n    \"https://www.google-analytics.com/*\",\n    \"tabs\"\n  ],\n  \"content_security_policy\": \"script-src 'self'; object-src 'self'\",\n  \"web_accessible_resources\": [\"/dist/inject.js\"],\n  \"devtools_page\": \"devtools.html\"\n}\n"
  },
  {
    "path": "extension/manifest-v3.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"Meteor DevTools Evolved\",\n  \"description\": \"The Meteor framework development tool belt, evolved.\",\n  \"version\": \"1.8.1\",\n  \"author\": \"Leonardo Venturini\",\n  \"icons\": {\n    \"16\": \"icons/meteor-16.png\",\n    \"48\": \"icons/meteor-48.png\",\n    \"128\": \"icons/meteor-128.png\"\n  },\n  \"action\": {\n    \"default_title\": \"Meteor\",\n    \"default_icon\": \"icons/meteor-48.png\"\n  },\n  \"background\": {\n    \"service_worker\": \"/dist/background.js\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"/dist/content.js\"],\n      \"run_at\": \"document_start\",\n      \"all_frames\": true\n    }\n  ],\n  \"host_permissions\": [\n    \"https://api.github.com/*\",\n    \"https://www.google-analytics.com/*\"\n  ],\n  \"content_security_policy\": {\n    \"extension_pages\": \"script-src 'self'; object-src 'self'\"\n  },\n  \"web_accessible_resources\": [\n    {\n      \"resources\": [\"/dist/inject.js\"],\n      \"matches\": [\"*://*/*\"]\n    }\n  ],\n  \"devtools_page\": \"devtools.html\"\n}\n"
  },
  {
    "path": "extension/options.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\"\n    />\n    <title>Options</title>\n  </head>\n  <body>\n    <div id=\"options\" class=\"bp4-dark\"></div>\n    <script src=\"/dist/bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "extension/popup.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\"\n    />\n    <title>Popup</title>\n  </head>\n  <body>\n    <div id=\"popup\" class=\"bp4-dark\"></div>\n    <script src=\"/dist/bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "lint-staged.js",
    "content": "module.exports = {\n  '*.{js,jsx,ts,tsx}': [\n    'eslint',\n    'react-scripts test --bail --watchAll=false --findRelatedTests --passWithNoTests',\n    () => 'tsc-files --noEmit',\n  ],\n  '*.{js,jsx,ts,tsx,json,css,js}': ['prettier --write'],\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"meteor-devtools-evolved\",\n  \"version\": \"1.8.1\",\n  \"description\": \"Meteor DevTools Evolved\",\n  \"repository\": \"https://github.com/leonardoventurini/meteor-devtools-evolved\",\n  \"packageManager\": \"yarn@4.12.0\",\n  \"keywords\": [\n    \"meteor\",\n    \"ddp\",\n    \"devtools\"\n  ],\n  \"scripts\": {\n    \"setup\": \"cd devapp-3.4 && npm install && cd ../ && yarn\",\n    \"devapp\": \"cd devapp-3.4 && npm start\",\n    \"build:chrome\": \"webpack --config webpack/chrome.prod.js\",\n    \"build:firefox\": \"webpack --config webpack/firefox.prod.js\",\n    \"dev:chrome\": \"run-p build:chrome devapp open:chrome\",\n    \"dev:firefox\": \"run-p build:firefox devapp open:firefox\",\n    \"dev\": \"yarn dev:chrome\",\n    \"wait:firefox\": \"wait-on extension/firefox/manifest.json http://localhost:2100\",\n    \"wait:chrome\": \"wait-on extension/chrome/manifest.json http://localhost:2100\",\n    \"open:firefox\": \"yarn wait:firefox && web-ext run --start-url \\\"http://localhost:2100\\\"  --source-dir ./extension/firefox/ --browser-console\",\n    \"open:chrome\": \"yarn wait:chrome && web-ext run -t chromium --start-url \\\"http://localhost:2100\\\" --source-dir ./extension/chrome/ --browser-console\",\n    \"clean\": \"rimraf extension/firefox extension/chrome\",\n    \"lint\": \"eslint .\",\n    \"audit\": \"yarn audit --all --recursive --severity high\"\n  },\n  \"author\": \"Leonardo Venturini\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@babel/core\": \"^7.9.0\",\n    \"@babel/preset-env\": \"^7.9.0\",\n    \"@babel/preset-react\": \"^7.9.1\",\n    \"@blueprintjs/core\": \"4.14.1\",\n    \"@blueprintjs/icons\": \"4.12.1\",\n    \"@blueprintjs/popover2\": \"^1.11.1\",\n    \"@heroicons/react\": \"^2.0.13\",\n    \"@types/chrome\": \"0.0.178\",\n    \"@types/classnames\": \"^2.2.10\",\n    \"@types/luxon\": \"^2.0.5\",\n    \"@types/meteor\": \"^2.0.4\",\n    \"@types/react\": \"^17.0.27\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"@types/react-json-tree\": \"^0.13.0\",\n    \"@types/react-window\": \"^1.8.1\",\n    \"@types/react-window-infinite-loader\": \"^1.0.3\",\n    \"@types/styled-components\": \"^5.1.0\",\n    \"babel-loader\": \"^9.1.0\",\n    \"classnames\": \"2.3.1\",\n    \"clean-webpack-plugin\": \"^4.0.0\",\n    \"css-loader\": \"^6.3.0\",\n    \"d3-collection\": \"^1.0.7\",\n    \"d3-hierarchy\": \"^3.0.1\",\n    \"d3-selection\": \"^3.0.0\",\n    \"d3-shape\": \"^3.0.1\",\n    \"daisyui\": \"^2.15.2\",\n    \"dexie\": \"3.2.2\",\n    \"lodash.debounce\": \"^4.0.8\",\n    \"lodash.memoize\": \"^4.1.2\",\n    \"lodash.sortby\": \"^4.7.0\",\n    \"lodash.throttle\": \"^4.1.1\",\n    \"luxon\": \"2.5.2\",\n    \"mobx\": \"6.4.0\",\n    \"mobx-react-lite\": \"3.3.0\",\n    \"normalize.css\": \"8.0.1\",\n    \"polished\": \"4.1.4\",\n    \"postcss-loader\": \"^7.0.0\",\n    \"pretty-bytes\": \"6.0.0\",\n    \"react\": \"17.0.2\",\n    \"react-dom\": \"17.0.2\",\n    \"react-is\": \"17.0.2\",\n    \"react-singleton-hook\": \"^3.2.1\",\n    \"react-window\": \"1.8.6\",\n    \"react-window-infinite-loader\": \"1.0.7\",\n    \"sass\": \"^1.51.0\",\n    \"sass-loader\": \"^12.1.0\",\n    \"style-loader\": \"^3.3.0\",\n    \"styled-components\": \"5.3.3\",\n    \"tailwindcss\": \"^3.0.24\",\n    \"terser-webpack-plugin\": \"^5.2.4\",\n    \"ts-loader\": \"^9.2.6\",\n    \"tslib\": \"^2.3.1\",\n    \"typescript\": \"^4.4.3\",\n    \"uuid\": \"^8.3.2\",\n    \"webpack\": \"^5.76.0\",\n    \"webpack-cli\": \"^4.9.0\",\n    \"webpack-merge\": \"^5.8.0\"\n  },\n  \"volta\": {\n    \"node\": \"24.13.0\",\n    \"yarn\": \"4.12.0\"\n  },\n  \"resolutions\": {\n    \"@babel/traverse\": \"^7.23.2\",\n    \"axios\": \"^1.6.0\",\n    \"braces\": \"^3.0.3\",\n    \"cross-spawn\": \"^7.0.5\",\n    \"fast-json-patch\": \"^3.1.1\",\n    \"form-data\": \"^4.0.0\",\n    \"http-cache-semantics\": \"^4.1.1\",\n    \"json5\": \"^2.2.3\",\n    \"jsonwebtoken\": \"^9.0.0\",\n    \"jws\": \"^4.0.0\",\n    \"loader-utils\": \"^3.2.1\",\n    \"node-forge\": \"^1.3.2\",\n    \"qs\": \"^6.14.1\",\n    \"semver\": \"^7.5.4\",\n    \"sha.js\": \"^2.4.12\",\n    \"ws\": \"^8.17.1\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.0.0\",\n    \"@types/webextension-polyfill\": \"^0.9.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.0.0\",\n    \"@typescript-eslint/parser\": \"^8.0.0\",\n    \"concurrently\": \"^7.2.2\",\n    \"copy-webpack-plugin\": \"^11.0.0\",\n    \"eslint\": \"^9.0.0\",\n    \"eslint-plugin-prettier\": \"^5.0.0\",\n    \"eslint-plugin-react\": \"^7.37.0\",\n    \"eslint-plugin-react-hooks\": \"^5.0.0\",\n    \"eslint-plugin-unicorn\": \"^56.0.0\",\n    \"globals\": \"^15.0.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^3.0.0\",\n    \"prettier-plugin-tailwindcss\": \"^0.6.0\",\n    \"wait-on\": \"^6.0.1\",\n    \"web-ext\": \"^7.1.0\",\n    \"webextension-polyfill\": \"^0.9.0\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n  },\n}\n"
  },
  {
    "path": "src/Analytics.ts",
    "content": "import { exists } from './Utils'\nimport { v4 as uuid } from 'uuid'\nimport { isString } from './Utils/StringUtils'\n\nconst GA_HOST = 'https://www.google-analytics.com'\n\ntype UUID = string\n\ntype RequestObject = {\n  method?: string\n  body?: string\n  headers?: Record<string, string>\n}\n\ntype EventOptions = {\n  clientId?: UUID\n  label?: string\n  value?: number\n}\n\ntype EventParams = {\n  ec?: string\n  ea?: string\n  el?: string\n  ev?: number\n}\n\ntype PageViewParams = {\n  dp?: string\n  dh?: string\n  dt?: string\n  sc?: number\n}\n\ntype ScreenParams = {\n  an?: string\n  av?: string\n  cd?: string\n  aiid?: string\n  aid?: string\n}\n\ntype TransactionOptions = {\n  affiliation?: string\n  revenue?: number\n  shipping?: number\n  tax?: number\n  currencyCode?: string\n}\n\ntype TransactionParams = {\n  ti?: string\n  ta?: string\n  tr?: number\n  ts?: number\n  tt?: number\n  cu?: string\n}\n\ntype TimingOptions = {\n  label?: string\n  dns?: number\n  pageDownTime?: number\n  redirectTime?: number\n  tcpConnectionTime?: number\n  serverResponseTime?: number\n}\n\ntype TimingParams = {\n  utc?: string\n  utv?: string\n  utt?: number\n  dns?: number\n  utl?: string\n  pdt?: number\n  rrt?: number\n  tcp?: number\n  srt?: number\n}\n\ntype AnalyticsOptions = {\n  userAgent?: string\n  debug?: boolean\n  version?: number\n  clientId?: string\n}\n\nexport class Analytics {\n  clientId = uuid()\n  customParams = {}\n  globalDebug = false\n  globalUserAgent = ''\n  globalBaseURL = GA_HOST\n  globalDebugURL = '/debug'\n  globalCollectURL = '/collect'\n  globalBatchURL = '/batch'\n  globalTrackingID: string\n  globalVersion = 1\n\n  constructor(trackingId, options: AnalyticsOptions = {}) {\n    const { clientId, userAgent, debug = false, version = 1 } = options\n\n    if (clientId) this.clientId = clientId\n    if (userAgent) this.globalUserAgent = userAgent\n\n    this.globalDebug = debug\n    this.globalTrackingID = trackingId\n    this.globalVersion = version\n    this.customParams = {}\n  }\n\n  set(key: string, value = null) {\n    if (value === null) {\n      delete this.customParams[key]\n    } else {\n      this.customParams[key] = value\n    }\n  }\n\n  pageView(\n    hostname: string = location?.hostname,\n    path: string = location?.pathname,\n    title: string = document?.title,\n    sessionDuration?: number,\n  ) {\n    const params: PageViewParams = {\n      dh: hostname,\n      dp: path,\n      dt: title,\n    }\n\n    if (exists(sessionDuration)) {\n      params.sc = sessionDuration\n    }\n\n    return this.send('pageview', params)\n  }\n\n  event(category?: string, action?: string, options: EventOptions = {}) {\n    const { label, value } = options\n\n    const params: EventParams = { ec: category, ea: action }\n\n    if (label) params.el = label\n    if (value) params.ev = value\n\n    this.send('event', params).catch(console.error)\n  }\n\n  screen(\n    appName: string,\n    appVersion: string,\n    appId: string,\n    appInstallerId: string,\n    screenName: string,\n  ) {\n    const params: ScreenParams = {\n      an: appName,\n      av: appVersion,\n      aid: appId,\n      aiid: appInstallerId,\n      cd: screenName,\n    }\n\n    return this.send('screenview', params)\n  }\n\n  transaction(transactionId: UUID, options: TransactionOptions = {}) {\n    const { affiliation, revenue, shipping, tax, currencyCode } = options\n    const params: TransactionParams = { ti: transactionId }\n\n    if (affiliation) params.ta = affiliation\n    if (revenue) params.tr = revenue\n    if (shipping) params.ts = shipping\n    if (tax) params.tt = tax\n    if (currencyCode) params.cu = currencyCode\n\n    return this.send('transaction', params)\n  }\n\n  social(socialAction: string, socialNetwork: string, socialTarget: string) {\n    const params = { sa: socialAction, sn: socialNetwork, st: socialTarget }\n\n    return this.send('social', params)\n  }\n\n  exception(description: string, fatal: number, clientId: UUID) {\n    const params = { exd: description, exf: fatal }\n\n    return this.send('exception', params)\n  }\n\n  timingTrk(\n    timingCategory: string,\n    timingVariable: string,\n    timingTime: number,\n    options: TimingOptions,\n  ) {\n    const {\n      label,\n      dns,\n      pageDownTime,\n      redirectTime,\n      tcpConnectionTime,\n      serverResponseTime,\n    } = options\n\n    const params: TimingParams = {\n      utc: timingCategory,\n      utv: timingVariable,\n      utt: timingTime,\n    }\n\n    if (label) params.utl = label\n    if (dns) params.dns = dns\n    if (pageDownTime) params.pdt = pageDownTime\n    if (redirectTime) params.rrt = redirectTime\n    if (tcpConnectionTime) params.tcp = tcpConnectionTime\n    if (serverResponseTime) params.srt = serverResponseTime\n\n    return this.send('timing', params)\n  }\n\n  send(hitType: string, params: Record<string, any>) {\n    const payload = {\n      v: this.globalVersion,\n      tid: this.globalTrackingID,\n      cid: this.clientId,\n      t: hitType,\n    }\n\n    if (params) Object.assign(payload, params)\n\n    if (Object.keys(this.customParams).length > 0) {\n      Object.assign(payload, this.customParams)\n    }\n\n    let url = `${this.globalBaseURL}${this.globalCollectURL}`\n\n    if (this.globalDebug) {\n      url = `${this.globalBaseURL}${this.globalDebugURL}${this.globalCollectURL}`\n    }\n\n    const requestObject: RequestObject = {\n      method: 'post',\n      body: Object.keys(payload)\n        .map(key => `${encodeURI(key)}=${encodeURI(payload[key])}`)\n        .join('&'),\n    }\n\n    if (this.globalUserAgent && isString(this.globalUserAgent)) {\n      requestObject.headers = { 'User-Agent': this.globalUserAgent }\n    }\n\n    return fetch(url, requestObject)\n      .then(res => {\n        let response = {}\n\n        response =\n          res.headers.get('content-type') === 'image/gif'\n            ? res.text()\n            : res.json()\n\n        if (res.status === 200) {\n          return response\n        }\n\n        throw new Error(response as string)\n      })\n      .then((json: any) => {\n        if (this.globalDebug && json.hitParsingResult[0].valid) {\n          return { clientId: payload.cid }\n        }\n\n        return { clientId: payload.cid }\n      })\n      .catch(error => new Error(error))\n  }\n}\n"
  },
  {
    "path": "src/App.tsx",
    "content": "import { FocusStyleManager } from '@blueprintjs/core'\nimport React from 'react'\nimport { render } from 'react-dom'\nimport { Options } from './Pages/Options'\nimport { Panel } from './Pages/Panel'\nimport { Popup } from './Pages/Popup'\n\nimport './Styles/Tailwind.css'\nimport './Styles/App.scss'\n\nFocusStyleManager.onlyShowFocusOnTabs()\n\nconst panelElement = document.querySelector('#panel')\nconst optionsElement = document.querySelector('#options')\nconst popupElement = document.querySelector('#popup')\n\nif (panelElement) render(<Panel />, panelElement)\nif (optionsElement) render(<Options />, optionsElement)\nif (popupElement) render(<Popup />, popupElement)\n"
  },
  {
    "path": "src/AppToaster.jsx",
    "content": "import { Position, Toaster } from '@blueprintjs/core'\n\nexport const AppToaster = Toaster.create({\n  className: 'app-toaster',\n  position: Position.TOP,\n})\n"
  },
  {
    "path": "src/Bridge.ts",
    "content": "import { detectType } from '@/Pages/Panel/DDP/FilterConstants'\nimport prettyBytes from 'pretty-bytes'\nimport { PanelStore } from '@/Stores/PanelStore'\nimport { DateTime } from 'luxon'\nimport { StringUtils } from '@/Utils/StringUtils'\nimport browser from 'webextension-polyfill'\n\nexport const syncSubscriptions = () =>\n  Bridge.sendContentMessage({\n    eventType: 'sync-subscriptions',\n    data: null,\n  })\n\nexport const syncStats = () =>\n  Bridge.sendContentMessage({\n    eventType: 'stats',\n    data: null,\n  })\n\nexport const clearCache = () =>\n  Bridge.sendContentMessage({\n    eventType: 'cache:clear',\n    data: null,\n  })\n\nexport const Bridge = new (class {\n  private handlers: Partial<Record<EventType, MessageHandler>> = {}\n\n  register(eventType: EventType, handler: MessageHandler) {\n    this.handlers[eventType] = handler\n  }\n\n  handle(message: Message<any>) {\n    if (message.eventType in this.handlers) {\n      const handler = this.handlers[message.eventType]\n\n      if (handler) handler(message)\n    }\n  }\n\n  sendContentMessage(message: Message<any>) {\n    const payload: IMessagePayload<any> = {\n      ...message,\n      source: 'meteor-devtools-evolved',\n    }\n\n    if (browser && browser.devtools) {\n      browser.devtools.inspectedWindow.eval(\n        `__meteor_devtools_evolved_receiveMessage(${JSON.stringify(payload)})`,\n      )\n    }\n  }\n\n  chrome() {\n    const backgroundConnection = browser.runtime.connect({\n      name: 'panel',\n    })\n\n    backgroundConnection.postMessage({\n      name: 'init',\n      tabId: browser.devtools.inspectedWindow.tabId,\n    })\n\n    backgroundConnection.onMessage.addListener((message: Message<any>) =>\n      Bridge.handle(message),\n    )\n  }\n\n  init() {\n    console.log('Setting up bridge...')\n\n    if (!browser || !browser.devtools) return\n\n    // FIXME : Need to confirm if using `chrome` instead of `browser` breaking any communication\n    this.chrome()\n\n    syncStats()\n  }\n})()\n\nBridge.register('ddp-event', (message: Message<DDPLog>) => {\n  const size = StringUtils.getSize(message.data.content)\n  const parsedContent = JSON.parse(message.data.content)\n  const filterType = detectType(parsedContent)\n\n  const log = {\n    ...message.data,\n    parsedContent,\n    timestampPretty: message.data.timestamp\n      ? DateTime.fromMillis(message.data.timestamp).toFormat('HH:mm:ss.SSS')\n      : '',\n    timestampLong: message.data.timestamp\n      ? DateTime.fromMillis(message.data.timestamp).toLocaleString(\n          DateTime.DATETIME_FULL,\n        )\n      : '',\n    size,\n    sizePretty: prettyBytes(size),\n    filterType,\n  }\n\n  if (filterType === 'subscription') {\n    syncSubscriptions()\n  }\n\n  PanelStore.ddpStore.pushItem(log)\n})\n\nBridge.register(\n  'minimongo-get-collections',\n  (message: Message<RawCollections>) => {\n    PanelStore.minimongoStore.setCollections(message.data)\n  },\n)\n\nBridge.register('sync-subscriptions', (message: Message<any>) => {\n  PanelStore.syncSubscriptions(JSON.parse(message.data.subscriptions))\n})\n\nBridge.register('stats', (message: Message<any>) => {\n  console.log(message.data)\n\n  PanelStore.setGitCommitHash(message.data.gitCommitHash)\n})\n\nBridge.register('meteor-data-performance', (message: Message<CallData>) => {\n  PanelStore.performanceStore.push(message.data)\n})\n"
  },
  {
    "path": "src/Browser/Background.ts",
    "content": "import browser from 'webextension-polyfill'\n\ntype Connection = Map<number, any>\n\ndeclare global {\n  interface Window {\n    connections: Connection\n  }\n}\n\nconst Cache = new Map<number, string[]>()\n\nconst connections: Connection = new Map()\n\nglobalThis.connections = connections\n\nconst panelListener = () => {\n  browser.runtime.onConnect.addListener(port => {\n    console.debug('runtime.onConnect', port)\n\n    port.onMessage.addListener(request => {\n      console.debug('port.onMessage', request)\n\n      if (request.name === 'init') {\n        connections.set(request.tabId, port)\n\n        // Pick things from cache and send it to the panel.\n        if (Cache.has(request.tabId)) {\n          for (const message of Cache.get(request.tabId)) {\n            port.postMessage(message)\n          }\n        }\n\n        port.onDisconnect.addListener(() => {\n          connections.delete(request.tabId)\n        })\n      }\n    })\n  })\n}\n\nconst tabRemovalListener = () => {\n  browser.tabs.onRemoved.addListener(tabId => {\n    console.debug('tabs.onRemoved', tabId)\n\n    if (connections.has(tabId)) {\n      connections.delete(tabId)\n      Cache.delete(tabId)\n    }\n  })\n}\n\n// For cross-browser support\nconst action = browser.browserAction || browser.action\n\naction.onClicked.addListener(e => {\n  console.debug('action.onClicked', e)\n\n  browser.tabs\n    .create({\n      url: 'http://cloud.meteor.com/?utm_source=chrome_extension&utm_medium=extension&utm_campaign=meteor_devtools_evolved',\n    })\n\n    .catch(console.error)\n})\n\nconst handleConsole = (\n  tabId: number,\n  { data: { type, message } }: Message<{ type: ConsoleType; message: string }>,\n) => {\n  if (type in console) {\n    console[type](`[${tabId}]`, message)\n  } else {\n    console.warn('Wrong console type.')\n  }\n}\n\nconst contentListener = () => {\n  // @ts-ignore\n  browser.runtime.onMessage.addListener((request, sender, sendResponse) => {\n    setTimeout(() => {\n      const tabId = sender?.tab?.id\n\n      if (!tabId) return\n\n      // The message event has to from the panel to the content and then through here.\n      if (request?.eventType === 'cache:clear') {\n        console.debug('clear cache')\n        Cache.delete(tabId)\n        return\n      }\n\n      if (request?.eventType === 'console') {\n        handleConsole(tabId, request)\n        return\n      }\n\n      if (Cache.has(tabId)) {\n        const entry = Cache.get(tabId)\n\n        if (entry.length >= 10_000) {\n          entry.shift()\n        }\n\n        entry.push(request)\n      } else {\n        Cache.set(tabId, [request])\n      }\n\n      if (connections.has(tabId)) {\n        connections.get(tabId).postMessage(request)\n      }\n    }, 0)\n\n    sendResponse()\n  })\n}\n\nconst tabListener = () => {\n  const tabEvent = {\n    'create-tab': request =>\n      browser.tabs\n        .create({\n          url: request.data.url,\n        })\n        .catch(console.error),\n  }\n  /**\n   * @issue https://stackoverflow.com/a/73836810/10567157\n   */\n  chrome.runtime.onMessage.addListener(\n    function (request, sender, sendResponse) {\n      sendResponse({ foo: true })\n\n      if (request.source !== 'meteor-devtools-evolved') return true\n\n      tabEvent[request.eventType]?.(request)\n\n      return true\n    },\n  )\n}\n\npanelListener()\ntabRemovalListener()\ncontentListener()\ntabListener()\n"
  },
  {
    "path": "src/Browser/Content.ts",
    "content": "import browser from 'webextension-polyfill'\n\nconst messageHandler = (event: MessageEvent) => {\n  // Only accept messages from same frame\n  if (event.source !== globalThis) return\n\n  // Only accept messages that we know are ours\n  if (event.data.source !== 'meteor-devtools-evolved') return\n\n  browser.runtime.sendMessage(event.data).catch(() => {\n    // Cleans up and prevent \"context invalidated\" errors.\n    window.removeEventListener('message', messageHandler)\n  })\n}\n\nwindow.addEventListener('message', messageHandler)\n\nconst url = browser.runtime.getURL('/dist/inject.js')\nconst script = document.createElement('script')\nscript.setAttribute('type', 'text/javascript')\nscript.setAttribute('src', url)\ndocument.documentElement.prepend(script)\n"
  },
  {
    "path": "src/Browser/DevTools.ts",
    "content": "import browser from 'webextension-polyfill'\nimport { checkFirefoxBrowser } from '@/Utils'\n\nconst isFirefox = checkFirefoxBrowser()\n\nbrowser.devtools.panels.create(\n  `${isFirefox ? '' : '☄️'} Meteor`,\n  '',\n  'devtools-panel.html',\n)\n"
  },
  {
    "path": "src/Browser/Inject.ts",
    "content": "import { DDPInjector } from '@/Injectors/DDPInjector'\nimport {\n  MinimongoInjector,\n  updateCollections,\n} from '@/Injectors/MinimongoInjector'\nimport { MeteorAdapter } from '@/Injectors/MeteorAdapter'\n\nconst isFrame = (function () {\n  try {\n    return globalThis.self !== window.top\n  } catch {\n    return true\n  }\n})()\n\nconst PARENTHESIS_REGEX = /(\\S*) \\(([^)]+)\\)/\n\nexport const sendMessage = (eventType: EventType, data: object) => {\n  window.postMessage(\n    {\n      eventType,\n      data,\n      source: 'meteor-devtools-evolved',\n    } as Message<object>,\n    '*',\n  )\n}\n\nconst warning = (message: string) => {\n  sendMessage('console', {\n    type: 'info',\n    message,\n  } as { type: ConsoleType; message: string })\n}\n\n/**\n * @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.\n */\nconst getStackTrace = (stackTraceLimit: number) => {\n  const originalStackTraceLimit = Error.stackTraceLimit\n\n  try {\n    Error.stackTraceLimit = stackTraceLimit\n    const error = new Error('Stack trace')\n\n    if (!error.stack) return []\n\n    return error?.stack\n      ?.split('\\n')\n      .map(trace => {\n        const matches = PARENTHESIS_REGEX.exec(trace)\n\n        if (!matches) return null\n\n        return {\n          callee: matches?.[1],\n          url: matches?.[2],\n        }\n      })\n      .filter(Boolean)\n  } finally {\n    Error.stackTraceLimit = originalStackTraceLimit\n  }\n}\n\nexport const sendLogMessage = (message: DDPLog) => {\n  const stackTrace = getStackTrace(15)\n\n  if (stackTrace && stackTrace.length > 0) {\n    stackTrace.splice(0, 2)\n  }\n\n  sendMessage('ddp-event', {\n    ...message,\n    trace: stackTrace,\n    host: location.host,\n  })\n\n  if (\n    message.content !== '{\"msg\":\"ping\"}' &&\n    message.content !== '{\"msg\":\"pong\"}'\n  )\n    updateCollections()\n}\n\ntype MessageHandler = (message: Message<any>) => void\ntype Registration = {\n  eventType: EventType\n  handler: MessageHandler\n}\n\ninterface IRegistry {\n  subscriptions: Registration[]\n\n  register(eventType: EventType, handler: MessageHandler): void\n\n  run(message: Message<any>): void\n}\n\nexport const Registry: IRegistry = {\n  subscriptions: [],\n\n  register(eventType: EventType, handler: MessageHandler) {\n    this.subscriptions.push({\n      eventType,\n      handler,\n    })\n  },\n\n  run(message: IMessagePayload<any>) {\n    for (const { eventType, handler } of this.subscriptions) {\n      if (\n        message.source === 'meteor-devtools-evolved' &&\n        eventType === message.eventType\n      ) {\n        handler(message)\n      }\n    }\n  },\n}\n\nexport function injectAll() {\n  if (!globalThis.__meteor_devtools_evolved) {\n    if (isFrame) return false\n\n    warning(\n      isFrame\n        ? `Initializing from iframe \"${location.href}\"...`\n        : 'Initializing on the main page...',\n    )\n\n    let attempts = 100\n    let interval = null\n\n    function inject() {\n      --attempts\n\n      if (typeof Meteor === 'object' && !globalThis.__meteor_devtools_evolved) {\n        globalThis.__meteor_devtools_evolved = true\n\n        DDPInjector()\n        MinimongoInjector()\n        MeteorAdapter()\n\n        globalThis.__meteor_devtools_evolved_receiveMessage =\n          Registry.run.bind(Registry)\n\n        warning(`Initialized. Attempts: ${100 - attempts}.`)\n      }\n\n      if (attempts === 0) {\n        clearInterval(interval)\n\n        if (!globalThis.Meteor) {\n          warning(\n            isFrame\n              ? `Unable to find Meteor on iframe \"${location.href}\"`\n              : 'Unable to find Meteor on the main page.',\n          )\n        }\n      }\n    }\n\n    inject()\n\n    interval = globalThis.setInterval(inject, 10)\n  }\n}\n\ninjectAll()\n"
  },
  {
    "path": "src/Browser/MeteorLibrary.ts",
    "content": "import { JSONUtils } from '@/Utils/JSONUtils'\nimport { mapValues, omit } from '@/Utils/Objects'\n\nexport const getSubscriptions = () => {\n  const payload = mapValues(\n    Meteor?.connection?._subscriptions ?? {},\n    (value: any) => omit(value, ['connection', 'readyDeps']),\n  )\n\n  return JSONUtils.stringify(payload)\n}\n"
  },
  {
    "path": "src/Components/Button.tsx",
    "content": "import React, { ButtonHTMLAttributes, FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { Icon, IconName, Intent } from '@blueprintjs/core'\nimport { centerItems, truncate } from '@/Styles/Mixins'\nimport classnames from 'classnames'\nimport { isNumber, isString } from 'lodash'\nimport { Popover2 } from '@blueprintjs/popover2'\n\nconst ButtonWrapper = styled.button`\n  ${centerItems};\n\n  cursor: pointer;\n  position: relative;\n  overflow: hidden;\n  background: transparent;\n  border: none;\n  color: #eee;\n  font-size: 1rem;\n  padding: 0 8px;\n\n  .icon + span {\n    margin-left: 4px;\n  }\n\n  &.warning {\n    background-color: rgba(217, 130, 43, 0.25);\n    color: #ffb366;\n\n    &:hover {\n      background-color: rgba(217, 130, 43, 0.25);\n    }\n\n    &:active {\n      background-color: rgba(217, 130, 43, 0.1);\n    }\n  }\n\n  &:hover:not([disabled], .warning) {\n    background-color: rgba(0, 0, 0, 0.2);\n  }\n\n  &[disabled] {\n    cursor: not-allowed;\n  }\n\n  &.shine {\n    &:before {\n      content: '';\n      display: block;\n      position: absolute;\n      background: rgba(255, 255, 255, 0.5);\n      width: 60px;\n      height: 100%;\n      left: 0;\n      top: 0;\n      opacity: 0.5;\n      filter: blur(30px);\n      transform: translateX(-100px) skewX(-15deg);\n    }\n\n    &:after {\n      content: '';\n      display: block;\n      position: absolute;\n      background: rgba(255, 255, 255, 0.2);\n      width: 30px;\n      height: 100%;\n      left: 30px;\n      top: 0;\n      opacity: 0;\n      filter: blur(5px);\n      transform: translateX(-100px) skewX(-15deg);\n    }\n\n    &:hover:before {\n      transform: translateX(300px) skewX(-15deg);\n      opacity: 0.6;\n      transition: 1.5s;\n    }\n\n    &:hover:after {\n      transform: translateX(300px) skewX(-15deg);\n      opacity: 1;\n      transition: 1.5s;\n    }\n  }\n\n  .button-wrapper {\n    display: flex;\n    align-items: center;\n    width: 100%;\n\n    span.content {\n      flex-grow: 1;\n      ${truncate};\n      text-align: left;\n    }\n\n    span.subtitle {\n      flex-shrink: 0;\n      flex-grow: 1;\n      font-size: 10px;\n      color: #ccc;\n      margin-left: auto;\n      text-align: right;\n    }\n  }\n`\n\ninterface Props extends ButtonHTMLAttributes<HTMLButtonElement> {\n  icon?: IconName | JSX.Element\n  intent?: Intent\n  shine?: boolean\n  active?: boolean\n  subtitle?: string\n}\n\nexport const Button: FunctionComponent<Props> = ({\n  icon,\n  children,\n  intent,\n  className,\n  shine,\n  active,\n  subtitle,\n  title,\n  ...rest\n}) => {\n  const classes = classnames(\n    {\n      shine,\n      active,\n      warning: intent === 'warning',\n    },\n    className,\n    'h-full',\n  )\n\n  if (title) {\n    return (\n      <Popover2\n        content={<div className='p-4'>{title}</div>}\n        interactionKind='hover'\n        className='inline-flex items-center'\n      >\n        <ButtonWrapper className={classes} {...rest}>\n          <div className='button-wrapper'>\n            {icon &&\n              (isString(icon) ? (\n                <Icon icon={icon} className='icon' iconSize={12} />\n              ) : (\n                icon\n              ))}\n            {(children || isNumber(children)) && (\n              <span className='content'>{children}</span>\n            )}\n            {(subtitle || isNumber(subtitle)) && (\n              <span className='subtitle'>{subtitle}</span>\n            )}\n          </div>\n        </ButtonWrapper>\n      </Popover2>\n    )\n  }\n\n  return (\n    <ButtonWrapper className={classes} {...rest}>\n      <div className='button-wrapper'>\n        {icon &&\n          (isString(icon) ? (\n            <Icon icon={icon} className='icon' iconSize={12} />\n          ) : (\n            icon\n          ))}\n        {(children || isNumber(children)) && (\n          <span className='content'>{children}</span>\n        )}\n        {(subtitle || isNumber(subtitle)) && (\n          <span className='subtitle'>{subtitle}</span>\n        )}\n      </div>\n    </ButtonWrapper>\n  )\n}\n"
  },
  {
    "path": "src/Components/Field.tsx",
    "content": "import React, { FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { centerItems } from '@/Styles/Mixins'\nimport { Icon, IconName } from '@blueprintjs/core'\nimport { exists } from '@/Utils'\nimport classnames from 'classnames'\n\nconst Wrapper = styled.span`\n  ${centerItems};\n  height: 100%;\n  padding: 0 8px;\n\n  .icon + span {\n    margin-left: 4px;\n  }\n\n  &.warning {\n    background-color: rgba(217, 130, 43, 0.25);\n    color: #ffb366;\n  }\n`\n\ninterface Props {\n  icon?: IconName\n  intent?: 'warning'\n  className?: string\n}\n\nexport const Field: FunctionComponent<Props> = ({\n  children,\n  icon,\n  className,\n  intent,\n}) => {\n  const classes = classnames(\n    {\n      warning: intent === 'warning',\n    },\n    className,\n  )\n\n  return (\n    <Wrapper className={classes}>\n      {icon && <Icon icon={icon} className='icon' iconSize={12} />}\n      {exists(children) && <span>{children}</span>}\n    </Wrapper>\n  )\n}\n"
  },
  {
    "path": "src/Components/PopoverButton.tsx",
    "content": "import React, { FunctionComponent } from 'react'\nimport { IconName } from '@blueprintjs/core'\nimport { Button } from '@/Components/Button'\nimport styled from 'styled-components'\nimport { Popover2, Popover2Props } from '@blueprintjs/popover2'\n\ninterface WrapperProps {\n  height: number\n}\n\nconst Wrapper = styled.span`\n  button.popover-button {\n    display: inline-block;\n    height: ${(props: WrapperProps) => props.height}px;\n  }\n`\n\ninterface Props extends Popover2Props {\n  icon: IconName\n  height?: number\n}\n\nexport const PopoverButton: FunctionComponent<Props> = ({\n  icon,\n  children,\n  height = 28,\n  ...rest\n}) => (\n  <Wrapper height={height}>\n    <Popover2 {...rest}>\n      <Button icon={icon} className='popover-button'>\n        {children}\n      </Button>\n    </Popover2>\n  </Wrapper>\n)\n"
  },
  {
    "path": "src/Components/Separator.tsx",
    "content": "import React, { FunctionComponent } from 'react'\nimport styled from 'styled-components'\n\ninterface WrapperProps {\n  horizontal?: boolean\n}\n\nconst Wrapper = styled.div`\n  width: ${({ horizontal }: WrapperProps) => (horizontal ? undefined : '1px')};\n  height: ${({ horizontal }: WrapperProps) => (horizontal ? '1px' : undefined)};\n  margin: 0 3px;\n  background-color: rgba(0, 0, 0, 0.05);\n`\n\nexport const Separator: FunctionComponent<WrapperProps> = props => (\n  <Wrapper {...props} />\n)\n"
  },
  {
    "path": "src/Components/StatusBar.tsx",
    "content": "import React, { FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { NAVBAR_HEIGHT } from '@/Styles/Constants'\nimport { lighten } from 'polished'\nimport { centerItems } from '@/Styles/Mixins'\n\nconst backgroundColor = '#202b33'\n\nconst Wrapper = styled.div`\n  user-select: none;\n  display: flex;\n  box-sizing: border-box;\n  flex-direction: row;\n  height: ${NAVBAR_HEIGHT}px;\n  width: 100%;\n\n  background-color: ${backgroundColor};\n\n  button {\n    height: 100%;\n    flex: 1 1 auto;\n\n    &:hover {\n      background-color: ${lighten(0.05, backgroundColor)};\n    }\n  }\n\n  .left-group,\n  .right-group {\n    ${centerItems};\n  }\n\n  .right-group {\n    margin-left: auto;\n  }\n\n  & > * + * {\n    margin-left: 8px;\n  }\n`\n\nexport const StatusBar: FunctionComponent = ({ children }) => (\n  <Wrapper>{children}</Wrapper>\n)\n"
  },
  {
    "path": "src/Components/TabBar.tsx",
    "content": "import React, { FunctionComponent, useState } from 'react'\nimport styled from 'styled-components'\nimport { IconName, Menu, MenuItem, Position } from '@blueprintjs/core'\nimport classnames from 'classnames'\nimport { Button } from './Button'\nimport { lighten } from 'polished'\nimport { NAVBAR_HEIGHT } from '@/Styles/Constants'\nimport { useBreakpoints } from '@/Utils/Hooks/useBreakpoints'\nimport { Popover2 } from '@blueprintjs/popover2'\n\nconst backgroundColor = '#202b33'\n\nconst TabBarWrapper = styled.div`\n  user-select: none;\n  display: flex;\n  box-sizing: border-box;\n  flex-direction: row;\n  height: ${NAVBAR_HEIGHT}px;\n  width: 100%;\n  border-bottom: 1px solid ${lighten(0.1, backgroundColor)};\n\n  background-color: ${backgroundColor};\n\n  button.mde-tab {\n    &.active {\n      background-color: ${lighten(0.1, backgroundColor)};\n    }\n\n    &:hover:not(.active) {\n      background-color: ${lighten(0.05, backgroundColor)};\n    }\n  }\n\n  .right-menu {\n    display: flex;\n    flex-direction: row;\n    margin-left: auto;\n\n    button.menu-item {\n      &:hover {\n        background-color: ${lighten(0.05, backgroundColor)};\n      }\n\n      .bp3-icon {\n        margin-bottom: 2px;\n      }\n    }\n  }\n`\n\nexport interface ITab {\n  key: string\n  content: JSX.Element | string\n  icon: IconName\n  shine?: boolean\n  handler?: () => void\n}\n\nexport interface IMenuItem {\n  key: string\n  content?: JSX.Element | string\n  icon?: IconName | JSX.Element\n  shine?: boolean\n  handler: () => void\n\n  title?: string\n}\n\ninterface Props {\n  tabs: ITab[]\n  menu?: IMenuItem[]\n  onChange?: (key: string) => void\n}\n\nexport const TabBar: FunctionComponent<Props> = ({ tabs, menu, onChange }) => {\n  const [activeKey, setKey] = useState(tabs[0].key)\n\n  const { navigationCollapse } = useBreakpoints()\n\n  const rightMenu = navigationCollapse ? (\n    <Popover2\n      content={\n        <Menu>\n          {menu?.map(item => (\n            <MenuItem\n              key={item.key}\n              icon={item.icon}\n              text={item.content}\n              onClick={item.handler}\n            />\n          ))}\n        </Menu>\n      }\n      position={Position.BOTTOM_LEFT}\n    >\n      <Button icon='menu' style={{ height: 28 }} />\n    </Popover2>\n  ) : (\n    menu?.map(item => (\n      <Button\n        key={item.key}\n        className='menu-item'\n        onClick={item.handler}\n        icon={item.icon}\n        shine={item.shine}\n        title={item.title}\n      >\n        {item.content}\n      </Button>\n    ))\n  )\n\n  return (\n    <TabBarWrapper>\n      {tabs.map(tab => (\n        <Button\n          key={tab.key}\n          onClick={() => {\n            setKey(tab.key)\n            if (onChange) onChange(tab.key)\n            if (tab.handler) tab.handler()\n          }}\n          className={classnames('mde-tab', {\n            active: activeKey === tab.key,\n          })}\n          icon={tab.icon}\n          shine={tab.shine}\n        >\n          {tab.content}\n        </Button>\n      ))}\n\n      <div className='right-menu'>{rightMenu}</div>\n    </TabBarWrapper>\n  )\n}\n"
  },
  {
    "path": "src/Components/TextInput.tsx",
    "content": "import React, { FunctionComponent, InputHTMLAttributes } from 'react'\nimport styled from 'styled-components'\nimport { Icon, IconName } from '@blueprintjs/core'\nimport { centerItems } from '@/Styles/Mixins'\n\nconst Wrapper = styled.div`\n  ${centerItems};\n  height: 100%;\n  padding: 0 8px;\n  background-color: rgba(0, 0, 0, 0.2);\n\n  .icon {\n    margin-right: 6px;\n  }\n\n  input[type='text'] {\n    border: none;\n    background: transparent;\n    height: 100%;\n\n    color: #eee;\n\n    ::placeholder {\n      color: #aaa;\n    }\n  }\n`\n\ninterface Props extends InputHTMLAttributes<HTMLInputElement> {\n  icon?: IconName\n}\n\nexport const TextInput: FunctionComponent<Props> = ({ icon, ...rest }) => (\n  <Wrapper>\n    <Icon icon={icon} iconSize={12} className='icon' />\n    <input type='text' {...rest} />\n  </Wrapper>\n)\n"
  },
  {
    "path": "src/Constants.ts",
    "content": "export const DEFAULT_OFFSET = 50\n\nexport const DEVELOPMENT = process.env.MODE === 'development'\n\nexport enum PanelPage {\n  DDP = 'ddp',\n  BOOKMARKS = 'bookmarks',\n  MINIMONGO = 'minimongo',\n  SUBSCRIPTIONS = 'subscriptions',\n  PERFORMANCE = 'performance',\n}\n"
  },
  {
    "path": "src/Database/PanelDatabase.ts",
    "content": "import Dexie from 'dexie'\nimport { toJS } from 'mobx'\n\nclass Database extends Dexie {\n  bookmarks: Dexie.Table<Bookmark, string>\n  data: Dexie.Table<Record<string, any>, string>\n\n  constructor() {\n    super('MeteorToolsDatabase')\n\n    this.version(1).stores({\n      bookmarks: 'id, timestamp, log',\n    })\n\n    this.version(2).stores({\n      data: 'id',\n    })\n\n    this.bookmarks = this.table('bookmarks')\n    this.data = this.table('data')\n  }\n\n  add(log: DDPLog) {\n    return this.bookmarks.add({\n      id: log.id,\n      timestamp: Date.now(),\n      log: toJS(log),\n    })\n  }\n\n  get(key: string) {\n    return this.bookmarks.get(key)\n  }\n\n  remove(key: string) {\n    return this.bookmarks.delete(key)\n  }\n\n  getAll() {\n    return this.bookmarks.toArray()\n  }\n\n  async getSettings() {\n    return (await this.data.get('settings')) ?? {}\n  }\n\n  async saveSettings(settings: ISettings) {\n    return (await this.data.get('settings'))\n      ? this.data.update('settings', settings)\n      : this.data.add({\n          id: 'settings',\n          ...settings,\n        })\n  }\n}\n\nexport const PanelDatabase = new Database()\n"
  },
  {
    "path": "src/Injectors/DDPInjector.ts",
    "content": "import { sendLogMessage } from '@/Browser/Inject'\n\ntype MessageCallback = (message: DDPLog) => void\n\nconst generateId = () => (Date.now() + Math.random()).toString(36)\n\nconst injectOutboundInterceptor = (callback: MessageCallback) => {\n  const send = Meteor.connection._stream.send\n\n  Meteor.connection._stream.send = function (...args) {\n    send.apply(this, args)\n\n    callback({\n      id: generateId(),\n      content: args[0],\n      isOutbound: true,\n      timestamp: Date.now(),\n    })\n  }\n}\n\nconst injectInboundInterceptor = (callback: MessageCallback) => {\n  Meteor.connection._stream.on('message', (...args) => {\n    callback({\n      id: generateId(),\n      content: args[0],\n      isInbound: true,\n      timestamp: Date.now(),\n    })\n  })\n}\n\nexport const DDPInjector = () => {\n  injectOutboundInterceptor(sendLogMessage)\n  injectInboundInterceptor(sendLogMessage)\n}\n"
  },
  {
    "path": "src/Injectors/MeteorAdapter.ts",
    "content": "import { Registry, sendMessage } from '@/Browser/Inject'\nimport { getSubscriptions } from '@/Browser/MeteorLibrary'\nimport { JSONUtils } from '@/Utils/JSONUtils'\n\nexport const MeteorAdapter = () => {\n  Registry.register('ddp-run-method', (message: Message<any>) => {\n    const { method, params } = message.data\n\n    Meteor.call(method, ...params)\n  })\n\n  Registry.register('sync-subscriptions', () => {\n    sendMessage('sync-subscriptions', {\n      subscriptions: getSubscriptions(),\n    })\n  })\n\n  Registry.register('stats', () => {\n    sendMessage('stats', {\n      gitCommitHash: Meteor.gitCommitHash,\n    })\n  })\n\n  Registry.register('cache:clear', () => {\n    sendMessage('cache:clear', {})\n  })\n\n  const prototype = Mongo.Collection.prototype\n\n  for (const [key, val] of Object.entries(prototype)) {\n    if (\n      ['find', 'findOne', 'insert', 'update', 'upsert', 'remove'].includes(\n        key,\n      ) &&\n      typeof val === 'function'\n    ) {\n      const original = prototype[key]\n\n      prototype[key] = function (...args) {\n        const startMs = Date.now()\n        const result = original.apply(this, args)\n\n        sendMessage('meteor-data-performance', {\n          collectionName: this._name,\n          key,\n          args: JSON.stringify(args, JSONUtils.getCircularReplacer()),\n          runtime: Date.now() - startMs,\n        })\n\n        return result\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/Injectors/MinimongoInjector.ts",
    "content": "import { warning } from '@/Log'\nimport { Registry, sendMessage } from '@/Browser/Inject'\nimport throttle from 'lodash.throttle'\n\nfunction cloneDeep(obj: any) {\n  return structuredClone(obj)\n}\n\nfunction isArray(obj: any) {\n  return Array.isArray(obj)\n}\n\nconst cleanup = (object: any) => {\n  if (typeof object !== 'object') return object\n\n  const clonedObject = cloneDeep(object)\n\n  if (!clonedObject) return clonedObject\n\n  for (const key of Object.keys(clonedObject)) {\n    if (!clonedObject[key]) {\n      return\n    }\n\n    if (typeof clonedObject[key] === 'object') {\n      if (isArray(clonedObject[key])) {\n        clonedObject[key] = clonedObject[key].map((item: any) => cleanup(item))\n        return\n      }\n\n      if (clonedObject[key] instanceof Date) {\n        clonedObject[key] = `[Object::${\n          clonedObject[key].constructor.name\n        }] ${clonedObject[key].toISOString()}`\n        return\n      }\n\n      if (clonedObject[key].constructor.name !== 'Object') {\n        if (typeof clonedObject[key].toString === 'function') {\n          clonedObject[key] = `[Object::${\n            clonedObject[key].constructor.name\n          }] ${clonedObject[key].toString()}`\n          return\n        } else {\n          clonedObject[key] = `[Object::${clonedObject[key].constructor.name}]`\n          return\n        }\n      }\n\n      clonedObject[key] = cleanup(clonedObject[key])\n    }\n  }\n\n  return clonedObject\n}\n\nconst getDocs = (collection: any) => {\n  return collection._docs._map instanceof Map\n    ? collection._docs._map?.values() || []\n    : Object.values(collection._docs._map || {})\n}\n\nconst getCollections = () => {\n  const collections = Meteor.connection._mongo_livedata_collections\n\n  if (!collections) {\n    warning(\n      'Collections not initialized in the client yet. Possibly forgotten to be imported.',\n    )\n    return\n  }\n\n  const data = Object.fromEntries(\n    Object.values(collections).map((collection: any) => [\n      collection.name,\n      [...getDocs(collection)].map(item => cleanup(item)),\n    ]),\n  )\n\n  sendMessage('minimongo-get-collections', data as any)\n}\n\nexport const updateCollections = throttle(getCollections, 1000, {\n  leading: true,\n  trailing: true,\n})\n\nexport const MinimongoInjector = () => {\n  Registry.register('minimongo-get-collections', () => {\n    getCollections()\n  })\n}\n"
  },
  {
    "path": "src/Log.ts",
    "content": "export const warning = (message: string) => {\n  console.log(\n    '%c'.concat('Meteor DevTools Evolved: ').concat(message),\n    'color: #bada55',\n  )\n}\n"
  },
  {
    "path": "src/Pages/Options.tsx",
    "content": "import React, { FunctionComponent } from 'react'\n\nexport const Options: FunctionComponent = () => {\n  return (\n    <div>\n      <h1>Options</h1>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/Bookmarks/Bookmarks.tsx",
    "content": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from 'mobx-react-lite'\nimport React, { FunctionComponent } from 'react'\nimport { DDPContainer } from '@/Pages/Panel/DDP/DDPContainer'\nimport { BookmarksStatus } from './BookmarksStatus'\n\ninterface Props {\n  isVisible: boolean\n}\n\nexport const Bookmarks: FunctionComponent<Props> = observer(({ isVisible }) => {\n  const store = usePanelStore()\n  const bookmarkStore = store.bookmarkStore\n\n  return (\n    <Hideable isVisible={isVisible}>\n      <DDPContainer isVisible={isVisible} source={bookmarkStore} />\n\n      <BookmarksStatus />\n    </Hideable>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/Bookmarks/BookmarksStatus.tsx",
    "content": "import { observer } from 'mobx-react-lite'\nimport React, { FormEvent, FunctionComponent, useCallback } from 'react'\n\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { StatusBar } from '@/Components/StatusBar'\nimport { DDPFilterMenu } from '@/Pages/Panel/DDP/DDPFilterMenu'\nimport { Position } from '@blueprintjs/core/lib/esm/common/position'\nimport { TextInput } from '@/Components/TextInput'\nimport { PopoverButton } from '@/Components/PopoverButton'\nimport { Field } from '@/Components/Field'\nimport { exists } from '@/Utils'\n\nexport const BookmarksStatus: FunctionComponent = observer(() => {\n  const store = usePanelStore()\n  const { bookmarkStore, settingStore } = store\n\n  const activeFilters = store.settingStore.activeFilters\n  const setFilter = useCallback(\n    (type, isEnabled) => settingStore.setFilter(type, isEnabled),\n    [settingStore],\n  )\n  const collectionLength = bookmarkStore.collection.length\n  const { pagination } = bookmarkStore\n\n  return (\n    <StatusBar>\n      <div className='left-group'>\n        <PopoverButton\n          icon='filter'\n          height={28}\n          content={\n            <DDPFilterMenu\n              setFilter={setFilter}\n              activeFilters={activeFilters}\n            />\n          }\n          position={Position.RIGHT_TOP}\n        >\n          Filter\n        </PopoverButton>\n\n        <TextInput\n          icon='search'\n          placeholder='Search...'\n          onChange={(event: FormEvent<HTMLInputElement>) =>\n            pagination.setSearch(event.currentTarget.value)\n          }\n        />\n\n        <Field icon='eye-open'>{pagination.length}</Field>\n      </div>\n\n      <div className='right-group'>\n        {exists(collectionLength) && (\n          <Field intent='warning' icon='inbox'>\n            {collectionLength}\n          </Field>\n        )}\n      </div>\n    </StatusBar>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDP.tsx",
    "content": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from 'mobx-react-lite'\nimport React, { FunctionComponent } from 'react'\nimport { DDPStatus } from './DDPStatus'\nimport { DDPContainer } from '@/Pages/Panel/DDP/DDPContainer'\n\ninterface Props {\n  isVisible: boolean\n}\n\nexport const DDP: FunctionComponent<Props> = observer(({ isVisible }) => {\n  const store = usePanelStore()\n  const ddpStore = store.ddpStore\n\n  return (\n    <Hideable isVisible={isVisible}>\n      <DDPContainer isVisible={isVisible} source={ddpStore} />\n\n      <DDPStatus />\n    </Hideable>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPContainer.tsx",
    "content": "import React, { FunctionComponent, useRef } from 'react'\nimport { DDPLog } from '@/Pages/Panel/DDP/DDPLog'\nimport { FixedSizeList } from 'react-window'\nimport { observer } from 'mobx-react-lite'\nimport { DDPStore } from '@/Stores/Panel/DDPStore'\nimport { BookmarkStore } from '@/Stores/Panel/BookmarkStore'\nimport { useDimensions } from '@/Utils/Hooks/useDimensions'\nimport { usePanelStore } from '@/Stores/PanelStore'\n\ninterface Props {\n  source: DDPStore | BookmarkStore\n  isVisible: boolean\n}\n\nexport const DDPContainer: FunctionComponent<Props> = observer(\n  ({ source, isVisible }) => {\n    const store = usePanelStore()\n    const contentRef = useRef<HTMLDivElement>(null)\n\n    const { width, height } = useDimensions(contentRef, [isVisible])\n\n    const Row: FunctionComponent<any> = observer(({ data, index, style }) => {\n      const item = (data as any).items[index]\n      const log = 'log' in item ? item.log : item\n\n      return (\n        <DDPLog\n          key={log.id}\n          style={style}\n          log={log}\n          isNew={'newLogs' in source && source.newLogs.includes(log.id)}\n          isStarred={store.bookmarkStore.bookmarkIds.includes(log.id)}\n        />\n      )\n    })\n\n    const list = (\n      <FixedSizeList\n        height={height}\n        width={width}\n        itemCount={source.filtered.length}\n        itemSize={28}\n        itemData={{ items: source.filtered }}\n      >\n        {Row}\n      </FixedSizeList>\n    )\n\n    return (\n      <div className='mde-content mde-ddp' ref={contentRef}>\n        {source.filtered.length > 0 ? list : null}\n      </div>\n    )\n  },\n)\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPFilterMenu.tsx",
    "content": "import { Switch } from '@blueprintjs/core'\nimport { observer } from 'mobx-react-lite'\nimport React, { FormEvent, FunctionComponent } from 'react'\nimport { FilterCriteria } from './FilterConstants'\n\ninterface Props {\n  activeFilters: FilterTypeMap<boolean>\n  setFilter: (filter: FilterType, isEnabled: boolean) => void\n}\n\nexport const DDPFilterMenu: FunctionComponent<Props> = observer(\n  ({ activeFilters, setFilter }) => {\n    const filters = Object.keys(FilterCriteria).map(filter => (\n      <Switch\n        key={filter}\n        checked={activeFilters[filter as FilterType]}\n        label={filter.charAt(0).toUpperCase() + filter.slice(1)}\n        onChange={(event: FormEvent<HTMLInputElement>) =>\n          setFilter(filter as FilterType, event.currentTarget.checked)\n        }\n      />\n    ))\n\n    return <div style={{ padding: 10 }}>{filters}</div>\n  },\n)\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLog.tsx",
    "content": "import { Tag, Tooltip } from '@blueprintjs/core'\nimport classnames from 'classnames'\nimport React, { CSSProperties, FunctionComponent } from 'react'\nimport { DDPLogDirection } from './DDPLogDirection'\nimport { DDPLogPreview } from './DDPLogPreview'\nimport { DateTime } from 'luxon'\nimport styled from 'styled-components'\nimport { truncate } from '@/Styles/Mixins'\nimport { DDPLogMenu } from '@/Pages/Panel/DDP/DDPLogMenu'\n\ninterface Props {\n  log: DDPLog\n  style: CSSProperties\n  isNew: boolean\n  isStarred: boolean\n}\n\nconst DDPLogWrapper = styled.div`\n  display: flex;\n  align-items: center;\n  flex-direction: row;\n  justify-content: space-between;\n  padding: 5px 15px;\n\n  transition: background-color 0.5s ease;\n\n  &.m-new {\n    background-color: #30594d;\n  }\n\n  &.m-starred {\n    background-color: #304066;\n  }\n\n  div + div {\n    margin-left: 10px;\n  }\n\n  .time {\n    font-size: 11px;\n    font-family: inherit;\n  }\n\n  .content {\n    display: flex;\n    flex: 1;\n    align-items: center;\n    min-width: 0;\n\n    .content-icon {\n      margin-right: 10px;\n    }\n\n    .content-preview {\n      flex: 0 1 auto;\n      min-width: 0;\n\n      code {\n        font-family: monospace;\n        ${truncate}\n      }\n    }\n  }\n\n  &:hover {\n    background-color: #394b59;\n  }\n`\n\nexport const DDPLog: FunctionComponent<Props> = ({\n  log,\n  style,\n  isNew,\n  isStarred,\n}) => {\n  const classes = classnames(\n    {\n      'm-new': isNew,\n      'm-starred': isStarred,\n    },\n    'group',\n  )\n\n  return (\n    <DDPLogWrapper className={classes} style={style}>\n      <div className='time'>\n        <Tooltip\n          content={\n            log.timestampLong ||\n            (log.timestamp\n              ? DateTime.fromMillis(log.timestamp).toLocaleString()\n              : '')\n          }\n          hoverOpenDelay={800}\n          position='top'\n        >\n          <small>{log.timestampPretty}</small>\n        </Tooltip>\n      </div>\n      <div className='direction'>\n        <DDPLogDirection\n          isOutbound={log.isOutbound}\n          isInbound={log.isInbound}\n        />\n      </div>\n      <div className='content'>\n        <DDPLogPreview\n          parsedContent={log.parsedContent}\n          preview={log.preview}\n          filterType={log.filterType}\n        />\n      </div>\n\n      <DDPLogMenu log={log} />\n\n      <div className='size'>\n        <Tag minimal>{log.sizePretty}</Tag>\n      </div>\n    </DDPLogWrapper>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLogDirection.tsx",
    "content": "import { Icon } from '@blueprintjs/core'\nimport React, { FunctionComponent } from 'react'\n\ninterface Prop {\n  isOutbound?: boolean\n  isInbound?: boolean\n}\n\nexport const DDPLogDirection: FunctionComponent<Prop> = ({\n  isOutbound,\n  isInbound,\n}) => {\n  if (isOutbound && isInbound) return <Icon icon='full-circle' iconSize={12} />\n\n  if (isOutbound)\n    return <Icon icon='arrow-top-right' intent='danger' iconSize={12} />\n\n  if (isInbound)\n    return <Icon icon='arrow-bottom-left' intent='success' iconSize={12} />\n\n  return <Icon icon='warning-sign' intent='warning' iconSize={12} />\n}\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLogMenu.tsx",
    "content": "import { Icon } from '@blueprintjs/core'\nimport { PanelPage } from '@/Constants'\nimport { Bridge } from '@/Bridge'\nimport React, { FunctionComponent } from 'react'\nimport { usePanelStore } from '@/Stores/PanelStore'\n\ninterface Props {\n  log: DDPLog\n}\n\nexport const DDPLogMenu: FunctionComponent<Props> = ({ log }) => {\n  const store = usePanelStore()\n\n  return (\n    <div className='menu invisible flex flex-row gap-2 group-hover:visible'>\n      <Icon\n        icon='eye-open'\n        onClick={() => log.trace && store.setActiveStackTrace(log.trace)}\n        style={{ cursor: 'pointer' }}\n      />\n      <Icon\n        icon={\n          store.bookmarkStore.bookmarkIds.includes(log.id)\n            ? 'star'\n            : 'star-empty'\n        }\n        onClick={() =>\n          store.bookmarkStore.bookmarkIds.includes(log.id)\n            ? store.bookmarkStore.remove(log)\n            : store.bookmarkStore.add(log)\n        }\n        style={{ cursor: 'pointer' }}\n      />\n      {log.parsedContent?.msg === 'method' && (\n        <Icon\n          icon='play'\n          onClick={() => {\n            store.setSelectedTabId(PanelPage.DDP)\n\n            Bridge.sendContentMessage({\n              eventType: 'ddp-run-method',\n              data: log.parsedContent,\n            })\n          }}\n          style={{ cursor: 'pointer' }}\n        />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPLogPreview.tsx",
    "content": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Icon, IconName, Tag, Tooltip } from '@blueprintjs/core'\nimport React, { FunctionComponent } from 'react'\n\nconst getTag = (icon: IconName, title: string) => (\n  <Tooltip\n    content={title}\n    hoverOpenDelay={800}\n    position='top'\n    className='content-icon'\n  >\n    <Icon\n      icon={icon}\n      style={{\n        color: '#8a9ba8',\n      }}\n      iconSize={12}\n    />\n  </Tooltip>\n)\n\nconst getTypeTag = (filterType?: FilterType | null) => {\n  switch (filterType) {\n    case 'heartbeat': {\n      return getTag('heart', 'Heartbeat')\n    }\n    case 'connection': {\n      return getTag('globe-network', 'Connection')\n    }\n    case 'collection': {\n      return getTag('database', 'Collection')\n    }\n    case 'subscription': {\n      return getTag('feed-subscribed', 'Subscription')\n    }\n    case 'method': {\n      return getTag('derive-column', 'Method')\n    }\n    default: {\n      return getTag('warning-sign', 'Unknown')\n    }\n  }\n}\n\nexport const DDPLogPreview: FunctionComponent<Partial<DDPLog>> = ({\n  filterType,\n  parsedContent,\n  preview,\n}) => {\n  const store = usePanelStore()\n\n  return (\n    <>\n      {getTypeTag(filterType)}\n      <Tag\n        interactive\n        minimal\n        onClick={() => {\n          if (parsedContent) store.setActiveObject(parsedContent)\n        }}\n        className='content-preview'\n        intent={parsedContent?.error ? 'danger' : 'none'}\n      >\n        <small>\n          <code>{preview}</code>\n        </small>\n      </Tag>\n    </>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/DDP/DDPStatus.tsx",
    "content": "import { Spinner, Tag, Tooltip } from '@blueprintjs/core'\nimport { isNumber } from 'lodash'\nimport { observer } from 'mobx-react-lite'\nimport React, { FormEvent, FunctionComponent, useCallback } from 'react'\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { StatusBar } from '@/Components/StatusBar'\nimport { DDPFilterMenu } from '@/Pages/Panel/DDP/DDPFilterMenu'\nimport { Position } from '@blueprintjs/core/lib/esm/common/position'\nimport { TextInput } from '@/Components/TextInput'\nimport { PopoverButton } from '@/Components/PopoverButton'\nimport { Button } from '@/Components/Button'\nimport prettyBytes from 'pretty-bytes'\nimport { Field } from '@/Components/Field'\nimport { StringUtils } from '@/Utils/StringUtils'\nimport { AppToaster } from '@/AppToaster'\n\nexport const DDPStatus: FunctionComponent = observer(() => {\n  const store = usePanelStore()\n  const { ddpStore, settingStore } = store\n\n  const activeFilters = settingStore.activeFilters\n  const setFilter = useCallback(\n    (type, isEnabled) => settingStore.setFilter(type, isEnabled),\n    [settingStore],\n  )\n  const collectionLength = ddpStore.collection.length\n  const { inboundBytes, outboundBytes, isLoading, pagination } = ddpStore\n\n  return (\n    <StatusBar>\n      <div className='left-group'>\n        <PopoverButton\n          icon='filter'\n          height={28}\n          content={\n            <DDPFilterMenu\n              setFilter={setFilter}\n              activeFilters={activeFilters}\n            />\n          }\n          position={Position.RIGHT_TOP}\n        >\n          Filter\n        </PopoverButton>\n\n        <TextInput\n          icon='search'\n          placeholder='Search...'\n          onChange={(event: FormEvent<HTMLInputElement>) =>\n            pagination.setSearch(event.currentTarget.value)\n          }\n        />\n\n        <Field icon='eye-open'>{pagination.length}</Field>\n      </div>\n\n      <div className='right-group'>\n        {isLoading && (\n          <Field>\n            <Spinner size={12} intent='warning' />\n          </Field>\n        )}\n\n        {store.gitCommitHash ? (\n          <Tooltip\n            content='Git Commit Hash'\n            hoverOpenDelay={800}\n            position='top'\n          >\n            <Tag\n              minimal\n              interactive\n              onClick={() => {\n                StringUtils.toClipboard(store.gitCommitHash as string)\n                AppToaster.show({\n                  icon: 'tick',\n                  message: 'Copied to Clipboard',\n                  intent: 'success',\n                  timeout: 1000,\n                })\n              }}\n              style={{ marginRight: 4 }}\n            >\n              {store.gitCommitHash.slice(0, 8)}\n            </Tag>\n          </Tooltip>\n        ) : null}\n\n        {!!inboundBytes && (\n          <Field icon='cloud-download'>{prettyBytes(inboundBytes)}</Field>\n        )}\n\n        {!!outboundBytes && (\n          <Field icon='cloud-upload'>{prettyBytes(outboundBytes)}</Field>\n        )}\n\n        {isNumber(collectionLength) && (\n          <Button\n            intent='warning'\n            onClick={() => ddpStore.clearLogs()}\n            icon='inbox'\n          >\n            {collectionLength}\n          </Button>\n        )}\n      </div>\n    </StatusBar>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/DDP/FilterConstants.ts",
    "content": "export const FilterCriteria: FilterTypeMap<string[]> = {\n  heartbeat: ['ping', 'pong'],\n  subscription: ['sub', 'unsub', 'nosub', 'ready'],\n  collection: ['added', 'removed', 'changed'],\n  method: ['method', 'result', 'updated'],\n  connection: ['connect', 'connected', 'failed'],\n}\n\nexport const FilterCriteriaMap: {\n  [key: string]: FilterType\n} = Object.fromEntries(\n  Object.entries(FilterCriteria).flatMap(([key, matchers]) =>\n    matchers.map(matcher => [matcher, key]),\n  ),\n)\n\nexport const detectType = (content?: DDPLogContent) => {\n  if (content && content.msg && content.msg in FilterCriteriaMap) {\n    return FilterCriteriaMap[content.msg]\n  }\n\n  return null\n}\n"
  },
  {
    "path": "src/Pages/Panel/DrawerJSON.tsx",
    "content": "import { ObjectTreerinator } from '@/Utils/ObjectTreerinator'\nimport { Button, Classes, Drawer } from '@blueprintjs/core'\nimport React, { FunctionComponent } from 'react'\nimport { StringUtils } from '@/Utils/StringUtils'\nimport { Popover2 } from '@blueprintjs/popover2'\n\ninterface Props {\n  title: string | null\n  viewableObject: ViewableObject\n  onClose(): void\n}\n\nexport const DrawerJSON: FunctionComponent<Props> = ({\n  title,\n  viewableObject,\n  onClose,\n}) => {\n  return (\n    <Drawer\n      icon='document'\n      title={title ?? 'JSON'}\n      isOpen={!!viewableObject}\n      onClose={onClose}\n      size='72%'\n    >\n      <div className={Classes.DRAWER_BODY}>\n        <div className={Classes.DIALOG_BODY}>\n          {!!viewableObject && <ObjectTreerinator object={viewableObject} />}\n        </div>\n      </div>\n      <div className={Classes.DRAWER_FOOTER}>\n        <Popover2\n          position='top'\n          content={<div style={{ padding: '0.5rem' }}>Copied</div>}\n        >\n          <Button\n            onClick={() =>\n              StringUtils.toClipboard(JSON.stringify(viewableObject, null, 2))\n            }\n            icon='clipboard'\n            minimal\n          >\n            Copy\n          </Button>\n        </Popover2>\n      </div>\n    </Drawer>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/DrawerStackTrace.tsx",
    "content": "import { Classes, Drawer } from '@blueprintjs/core'\nimport { Tooltip2 } from '@blueprintjs/popover2'\nimport classnames from 'classnames'\nimport React, { FunctionComponent } from 'react'\n\ninterface Props {\n  activeStackTrace: StackTrace[] | null\n\n  onClose(): void\n}\n\nexport const DrawerStackTrace: FunctionComponent<Props> = ({\n  activeStackTrace,\n  onClose,\n}) => (\n  <Drawer\n    icon='document'\n    title='Stack Trace'\n    isOpen={!!activeStackTrace}\n    onClose={onClose}\n    size='72%'\n  >\n    <div className={Classes.DRAWER_BODY}>\n      <div className={classnames(Classes.DIALOG_BODY, 'mde-stack-trace')}>\n        {activeStackTrace?.map((stack: StackTrace, index: number) => {\n          const text = (\n            <div>\n              <em>{stack?.callee?.trim() || 'Anonymous'}</em>\n            </div>\n          )\n\n          return (\n            <pre key={index}>\n              {stack?.url ? (\n                <Tooltip2 content={stack.url.trim()}>\n                  <a\n                    href={stack.url.trim()}\n                    target='_blank'\n                    rel='noopener noreferrer'\n                  >\n                    {text}\n                  </a>\n                </Tooltip2>\n              ) : (\n                text\n              )}\n            </pre>\n          )\n        })}\n      </div>\n    </div>\n  </Drawer>\n)\n"
  },
  {
    "path": "src/Pages/Panel/HelpDrawer.tsx",
    "content": "import { Classes, Drawer, DrawerSize, Icon } from '@blueprintjs/core'\nimport React, { FunctionComponent } from 'react'\nimport { GridItem, PartnersGrid } from './PartnersGrid'\nimport AuthorLogo from '@/Assets/leonardoventurini.png'\nimport MeteorCloudLogo from '@/Assets/meteor-cloud-logo.png'\n\nconst people: GridItem[] = [\n  {\n    name: 'Leonardo Venturini',\n    title: 'Senior Software Engineer',\n    role: 'Author',\n    email: 'leonardo@techster.tech',\n    imageUrl: AuthorLogo,\n    description:\n      'If you need help with extension related issues or general Node.js or Meteor consulting',\n    slack: 'https://meteor-community.slack.com/archives/DRKE6HDD5/',\n    linkedin: 'https://www.linkedin.com/in/leonardo-venturini/',\n    website: 'https://leonardoventurini.tech/',\n  },\n]\n\nconst orgs: GridItem[] = [\n  {\n    name: 'Galaxy',\n    title: 'Organization',\n    role: 'Partner',\n    website: 'https://social.meteor.com/devtools-evolved/',\n    imageUrl: MeteorCloudLogo,\n    description:\n      'If you want a full service cloud offering for deploying, hosting, and scaling your apps with zero DevOps',\n  },\n]\n\ninterface Props {\n  isHelpDrawerVisible: boolean\n\n  onClose(): void\n}\n\nconst YEAR = new Date().getFullYear()\n\nexport const HelpDrawer: FunctionComponent<Props> = ({\n  isHelpDrawerVisible,\n  onClose,\n}) => {\n  return (\n    <Drawer\n      title={\n        <div className='flex items-center gap-2'>\n          <Icon icon='help' /> Help\n        </div>\n      }\n      isOpen={isHelpDrawerVisible}\n      onClose={onClose}\n      size={DrawerSize.LARGE}\n    >\n      <div className={Classes.DRAWER_BODY}>\n        <div className={Classes.DIALOG_BODY}>\n          <div className='mb-4 w-full space-y-8 text-lg'>\n            <div className='section'>\n              <h2 className='section-title'>Extension</h2>\n              <PartnersGrid items={people} />\n            </div>\n\n            <div className='section'>\n              <h2 className='section-title'>Meteor & Development</h2>\n              <PartnersGrid items={orgs} />\n            </div>\n\n            <div className='section'>\n              <h2 className='section-title'>Basics</h2>\n              <p>\n                <em>Behold, the evolution of Meteor DevTools.</em>\n              </p>\n              <p>\n                <a\n                  href='https://github.com/leonardoventurini/meteor-devtools-evolved/blob/development/CHANGELOG.md'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  Change Log\n                </a>\n              </p>\n              <p>\n                The extension initializes with the page content, which means you\n                have to refresh the page after installation, also it needs the\n                devtools panel to be opened at least once in the current tab for\n                any messages to be processed.\n              </p>\n              <p>\n                Other than that you can just explore the extension at your\n                leisure. It should be easy enough.\n              </p>\n            </div>\n\n            <div className='section'>\n              <h2 className='section-title'>Feedback</h2>\n              <p>\n                Any feedback you might have can be addressed directly at our{' '}\n                <a\n                  href='https://github.com/leonardoventurini/meteor-devtools-evolved/issues'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  GitHub Issues\n                </a>{' '}\n                page, that way we can discuss and transition into development\n                more easily. You can also reach the author on the{' '}\n                <a\n                  href='https://join.slack.com/t/meteor-community/shared_invite/zt-a9lwcfb7-~UwR3Ng6whEqRxcP5rORZw'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  Meteor Community Slack\n                </a>{' '}\n                or the{' '}\n                <a\n                  href='https://forums.meteor.com/u/leonardoventurini'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  Meteor Forums\n                </a>\n                .\n              </p>\n              <p>\n                Starring the project is the easiest way to support the work and\n                be part of our community of{' '}\n                <a\n                  href='https://github.com/leonardoventurini/meteor-devtools-evolved/stargazers'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  stargazers\n                </a>\n                .\n              </p>\n              <p>Let&apos;s make Meteor great again.</p>\n            </div>\n\n            <div className='section'>\n              <h2 className='section-title'>Firefox</h2>\n              <p>\n                The Firefox port of the extension was a contribution made by{' '}\n                <a\n                  href='https://github.com/nilooy'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  RF Niloy\n                </a>\n                . Thank you!\n              </p>\n            </div>\n\n            <div className='section'>\n              <h2 className='section-title'>License</h2>\n              <p>The MIT License (MIT)</p>\n              <p>\n                Copyright (c) {YEAR}{' '}\n                <a\n                  href='https://leonardoventurini.tech'\n                  target='_blank'\n                  rel='noopener noreferrer'\n                >\n                  Leonardo Venturini\n                </a>\n              </p>\n              <p>\n                Permission is hereby granted, free of charge, to any person\n                obtaining a copy of this software and associated documentation\n                files (the \"Software\"), to deal in the Software without\n                restriction, including without limitation the rights to use,\n                copy, modify, merge, publish, distribute, sublicense, and/or\n                sell copies of the Software, and to permit persons to whom the\n                Software is furnished to do so, subject to the following\n                conditions:\n              </p>\n              <p>\n                The above copyright notice and this permission notice shall be\n                included in all copies or substantial portions of the Software.\n              </p>\n              <p>\n                THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n                EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n                OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n                NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n                HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n                WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n                FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n                OTHER DEALINGS IN THE SOFTWARE.\n              </p>\n            </div>\n          </div>\n        </div>\n      </div>\n    </Drawer>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/Minimongo/Minimongo.tsx",
    "content": "import { MinimongoNavigator } from '@/Pages/Panel/Minimongo/MinimongoNavigator'\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from 'mobx-react-lite'\nimport React, { FunctionComponent } from 'react'\nimport { MinimongoContainer } from '@/Pages/Panel/Minimongo/MinimongoContainer'\nimport styled from 'styled-components'\nimport { MinimongoStatus } from '@/Pages/Panel/Minimongo/MinimongoStatus'\nimport { Button } from '@/Components/Button'\nimport prettyBytes from 'pretty-bytes'\n\ninterface Props {\n  isVisible: boolean\n}\n\nconst Wrapper = styled.div`\n  display: flex;\n  flex-direction: row;\n  height: 100%;\n\n  .sidebar {\n    display: flex;\n    height: 100%;\n    width: 222px;\n    overflow-y: auto;\n    font-size: 11px;\n    font-family: monospace;\n\n    nav {\n      display: flex;\n      flex: 1;\n      flex-direction: column;\n      width: 100%;\n\n      button {\n        flex: 0 0 20px;\n        width: 100%;\n\n        &.active {\n          background: rgba(255, 255, 255, 0.15);\n        }\n\n        &:hover:not(.active) {\n          background: rgba(255, 255, 255, 0.1);\n        }\n      }\n    }\n  }\n\n  .container {\n    height: 100%;\n    min-width: 0;\n    flex-grow: 1;\n    flex-shrink: 1;\n\n    .row {\n      display: flex;\n      align-items: center;\n      padding: 5px 15px;\n\n      & > * + * {\n        margin-left: 8px;\n      }\n    }\n  }\n`\n\nexport const Minimongo: FunctionComponent<Props> = observer(({ isVisible }) => {\n  const { minimongoStore } = usePanelStore()\n\n  const isActiveCollectionMissing =\n    minimongoStore.activeCollection &&\n    !(minimongoStore.activeCollection in minimongoStore.collections)\n\n  if (isActiveCollectionMissing) {\n    minimongoStore.setActiveCollection(null)\n  }\n\n  return (\n    <Hideable isVisible={isVisible}>\n      <div className={'mde-content'}>\n        <Wrapper>\n          <div className='sidebar'>\n            <nav>\n              {minimongoStore.collectionNames.length > 0 &&\n                minimongoStore.collectionNames.map(key => (\n                  <Button\n                    key={key}\n                    active={minimongoStore.activeCollection === key}\n                    onClick={() => minimongoStore.setActiveCollection(key)}\n                    subtitle={`${\n                      minimongoStore.getMetadata(key)?.collectionSizePretty\n                    } (${minimongoStore.collections[key]?.length ?? 0})`}\n                    title={key}\n                  >\n                    {key}\n                  </Button>\n                ))}\n\n              <Button\n                active={!minimongoStore.activeCollection}\n                onClick={() => minimongoStore.setActiveCollection(null)}\n                subtitle={`${prettyBytes(minimongoStore.totalSize)} (${\n                  minimongoStore.totalDocuments\n                })`}\n              >\n                All Documents\n              </Button>\n            </nav>\n          </div>\n          <MinimongoContainer isVisible={isVisible} />\n        </Wrapper>\n      </div>\n\n      <MinimongoStatus />\n\n      <MinimongoNavigator />\n    </Hideable>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoContainer.tsx",
    "content": "import React, { CSSProperties, FunctionComponent, useRef } from 'react'\nimport { areEqual, FixedSizeList } from 'react-window'\nimport { observer } from 'mobx-react-lite'\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { MinimongoRow } from '@/Pages/Panel/Minimongo/MinimongoRow'\nimport { useDimensions } from '@/Utils/Hooks/useDimensions'\n\ninterface Props {\n  isVisible: boolean\n}\n\nexport const MinimongoContainer: FunctionComponent<Props> = observer(\n  ({ isVisible }) => {\n    const contentRef = useRef<HTMLDivElement>(null)\n\n    const store = usePanelStore()\n\n    const { activeCollectionDocuments, activeCollection } = store.minimongoStore\n\n    const { width, height } = useDimensions(contentRef, [isVisible])\n\n    interface IRow {\n      data: { items: IDocumentWrapper[] }\n      index: number\n      style: CSSProperties\n    }\n\n    const Row: FunctionComponent<any> = React.memo(\n      ({ data, index, style }: IRow) => {\n        const item = data.items![index]\n\n        return (\n          <MinimongoRow\n            style={style}\n            key={item.document._id}\n            item={item}\n            onClick={() => store.setActiveObject(item.document)}\n            onCollectionClick={() =>\n              store.minimongoStore.setActiveCollection(item.collectionName)\n            }\n            isAllVisible={!activeCollection}\n          />\n        )\n      },\n      areEqual,\n    )\n\n    return (\n      <div className='container' ref={contentRef}>\n        <FixedSizeList\n          height={height}\n          width={width}\n          itemCount={activeCollectionDocuments.filtered.length}\n          itemSize={28}\n          itemData={{ items: activeCollectionDocuments.filtered }}\n        >\n          {Row}\n        </FixedSizeList>\n      </div>\n    )\n  },\n)\n"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoNavigator.tsx",
    "content": "import {\n  Button,\n  Classes,\n  Dialog,\n  InputGroup,\n  Menu,\n  MenuItem,\n  NonIdealState,\n} from '@blueprintjs/core'\nimport React, { FormEvent, FunctionComponent } from 'react'\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { observer } from 'mobx-react-lite'\n\nexport const MinimongoNavigator: FunctionComponent = observer(() => {\n  const { minimongoStore } = usePanelStore()\n\n  const setActiveCollection = (collectionName: string | null) => {\n    minimongoStore.setActiveCollection(collectionName)\n    minimongoStore.setNavigatorVisible(false)\n  }\n\n  return (\n    <Dialog\n      icon='database'\n      onClose={() => {\n        minimongoStore.setNavigatorVisible(false)\n        minimongoStore.setSearch('')\n      }}\n      title='Collections'\n      isOpen={minimongoStore.isNavigatorVisible}\n    >\n      <div\n        className={Classes.DIALOG_BODY}\n        style={{ height: '50vh', overflowY: 'scroll' }}\n      >\n        <Menu>\n          {minimongoStore.filteredCollectionNames.length > 0 ? (\n            minimongoStore.filteredCollectionNames.map(key => (\n              <MenuItem\n                key={key}\n                icon='database'\n                text={`${key} (${minimongoStore.collections[key]?.length ?? 0})`}\n                active={minimongoStore.activeCollection === key}\n                onClick={() => setActiveCollection(key)}\n              />\n            ))\n          ) : (\n            <div style={{ marginTop: 50, marginBottom: 50 }}>\n              <NonIdealState icon='search' title='No Results' />\n            </div>\n          )}\n        </Menu>\n      </div>\n      <div className={Classes.DIALOG_FOOTER}>\n        <div style={{ display: 'flex' }}>\n          <div style={{ flexGrow: 1, marginRight: 8 }}>\n            <InputGroup\n              leftIcon='search'\n              placeholder='Search...'\n              className={Classes.FILL}\n              onChange={(event: FormEvent<HTMLInputElement>) =>\n                minimongoStore.setSearch(event.currentTarget.value)\n              }\n            />\n          </div>\n\n          <Button\n            icon='asterisk'\n            onClick={() => setActiveCollection(null)}\n            active={minimongoStore.activeCollection === null}\n          >\n            Everything\n          </Button>\n        </div>\n      </div>\n    </Dialog>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoRow.tsx",
    "content": "import { StringUtils } from '@/Utils/StringUtils'\nimport { Tag } from '@blueprintjs/core'\nimport React, { CSSProperties, FunctionComponent } from 'react'\nimport styled from 'styled-components'\nimport { truncate } from '@/Styles/Mixins'\n\nconst Wrapper = styled.div`\n  &,\n  & code {\n    font-family: monospace;\n    font-size: 12px;\n  }\n\n  .collection {\n    ${truncate};\n    cursor: pointer;\n    flex: 0 0 auto;\n  }\n\n  .preview {\n    ${truncate};\n    flex: 0 1 auto;\n  }\n`\n\ninterface Props {\n  item: IDocumentWrapper\n  style: CSSProperties\n  onClick: () => void\n  onCollectionClick: () => void\n  isAllVisible: boolean\n}\n\nexport const MinimongoRow: FunctionComponent<Props> = ({\n  item,\n  style,\n  onClick,\n  onCollectionClick,\n  isAllVisible,\n}) => {\n  return (\n    <Wrapper className='row' style={style}>\n      {isAllVisible && (\n        <Tag\n          className='collection'\n          style={{ cursor: 'pointer' }}\n          minimal\n          onClick={() => onCollectionClick()}\n        >\n          {item.collectionName}\n        </Tag>\n      )}\n      <Tag className='preview' minimal interactive onClick={() => onClick()}>\n        <code>{StringUtils.truncate(item._string, 256)}</code>\n      </Tag>\n    </Wrapper>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/Minimongo/MinimongoStatus.tsx",
    "content": "import React, { FormEvent, FunctionComponent } from 'react'\nimport { StatusBar } from '@/Components/StatusBar'\nimport { Button } from '@/Components/Button'\nimport { TextInput } from '@/Components/TextInput'\nimport { Field } from '@/Components/Field'\nimport { observer } from 'mobx-react-lite'\nimport { usePanelStore } from '@/Stores/PanelStore'\n\nexport const MinimongoStatus: FunctionComponent = observer(() => {\n  const { minimongoStore } = usePanelStore()\n\n  return (\n    <StatusBar>\n      <div className='left-group'>\n        <Button\n          icon={minimongoStore.activeCollection ? 'database' : 'asterisk'}\n          onClick={() => minimongoStore.setNavigatorVisible(true)}\n          disabled={minimongoStore.collectionNames.length === 0}\n        >\n          {minimongoStore.activeCollection || 'Everything'}\n        </Button>\n\n        {minimongoStore.activeCollection && (\n          <Button\n            icon='asterisk'\n            onClick={() => minimongoStore.setActiveCollection(null)}\n          >\n            Clear\n          </Button>\n        )}\n\n        <TextInput\n          icon='search'\n          placeholder='Search...'\n          onChange={(event: FormEvent<HTMLInputElement>) =>\n            minimongoStore.activeCollectionDocuments.pagination.setSearch(\n              event.currentTarget.value,\n            )\n          }\n        />\n\n        <Field icon='eye-open'>\n          {minimongoStore.activeCollectionDocuments.pagination.length}\n        </Field>\n      </div>\n    </StatusBar>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/Navigation.tsx",
    "content": "import { PanelPage } from '@/Constants'\nimport React, { FunctionComponent, useEffect } from 'react'\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { observer } from 'mobx-react-lite'\nimport { Bridge, syncSubscriptions } from '@/Bridge'\nimport { IMenuItem, ITab, TabBar } from '@/Components/TabBar'\nimport { Tag } from '@blueprintjs/core'\nimport { isNumber } from 'lodash'\nimport { useAnalytics } from '@/Utils/Hooks/useAnalytics'\nimport { openTab } from '@/Utils/BackgroundEvents'\n\nexport const Navigation: FunctionComponent = observer(() => {\n  const panelStore = usePanelStore()\n  const analytics = useAnalytics()\n\n  useEffect(() => {\n    setTimeout(() => {\n      panelStore.settingStore.updateRepositoryData()\n    }, 2000)\n  }, [])\n\n  const { repositoryData } = panelStore.settingStore\n\n  const tabs: ITab[] = [\n    {\n      key: PanelPage.DDP,\n      content: 'DDP',\n      icon: 'changes',\n    },\n    {\n      key: PanelPage.BOOKMARKS,\n      content: 'Bookmarks',\n      icon: 'star',\n    },\n    {\n      key: PanelPage.MINIMONGO,\n      content: 'Minimongo',\n      icon: 'database',\n      handler: () => {\n        // Fetch collection data from the page.\n        Bridge.sendContentMessage({\n          eventType: 'minimongo-get-collections',\n          data: null,\n        })\n      },\n    },\n    {\n      key: PanelPage.SUBSCRIPTIONS,\n      content: 'Subscriptions',\n      icon: 'feed-subscribed',\n      handler: () => {\n        syncSubscriptions()\n      },\n    },\n    {\n      key: PanelPage.PERFORMANCE,\n      content: 'Performance',\n      icon: 'lightning',\n    },\n  ]\n\n  const menu: IMenuItem[] = [\n    {\n      key: 'help',\n      icon: 'help',\n      content: 'Help',\n      shine: true,\n      handler: () => {\n        panelStore.setHelpDrawerVisible(true)\n        analytics?.event('navigation', 'click', { label: 'partners' })\n      },\n    },\n    {\n      key: 'reload',\n      icon: 'refresh',\n      content: 'Reload',\n      handler: () => location.reload(),\n      shine: true,\n    },\n  ]\n\n  if (repositoryData) {\n    menu.unshift({\n      key: 'feedback',\n      icon: 'issue',\n      content: <strong>Issues</strong>,\n      handler: () => {\n        openTab([...repositoryData.html_url, '/issues'])\n        analytics?.event('navigation', 'click', { label: 'feedback' })\n      },\n      shine: true,\n    })\n\n    menu.unshift({\n      key: 'star',\n      icon: 'star',\n      content: (\n        <>\n          <strong>Star</strong>\n          {isNumber(repositoryData.stargazers_count) ? (\n            <Tag minimal round style={{ marginLeft: '.5rem' }}>\n              {repositoryData.stargazers_count}\n            </Tag>\n          ) : null}\n        </>\n      ),\n      shine: true,\n      handler: () => {\n        openTab([...repositoryData.html_url, '/stargazers'])\n\n        analytics?.event('navigation', 'click', { label: 'star' })\n      },\n    })\n  }\n\n  menu.unshift({\n    key: 'sponsor',\n    content: <strong>❤️ Sponsor</strong>,\n    shine: true,\n    title: 'If you find this extension useful, please consider sponsoring',\n    handler: () => {\n      openTab('https://github.com/sponsors/leonardoventurini')\n      analytics?.event('navigation', 'click', { label: 'sponsor' })\n    },\n  })\n\n  return (\n    <div className='mde-navbar'>\n      <TabBar\n        tabs={tabs}\n        menu={menu}\n        onChange={key => panelStore.setSelectedTabId(key)}\n      />\n    </div>\n  )\n})\n"
  },
  {
    "path": "src/Pages/Panel/PartnersGrid.tsx",
    "content": "import React from 'react'\nimport {\n  ChatBubbleLeftIcon,\n  EnvelopeIcon,\n  LinkIcon,\n} from '@heroicons/react/20/solid'\nimport classnames from 'classnames'\n\nexport type GridItem = {\n  name: string\n  title: string\n  role: string\n  email?: string\n  imageUrl?: string\n  website?: string\n\n  slack?: string\n\n  linkedin?: string\n\n  description?: string\n}\n\nexport function PartnersGrid({ items, className = '' }) {\n  return (\n    <ul\n      role='list'\n      className={classnames(\n        'grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3',\n        className,\n      )}\n    >\n      {items.map(person => (\n        <li\n          key={person.name}\n          className='col-span-1 flex flex-col divide-y divide-gray-200 rounded-lg border border-gray-200 bg-white'\n        >\n          <div className='flex w-full items-center justify-between space-x-6 p-6'>\n            <div className='flex-1 truncate'>\n              <div className='flex items-center space-x-3'>\n                <h3 className='truncate text-sm font-medium text-gray-900'>\n                  {person.name}\n                </h3>\n                <span className='inline-block flex-shrink-0 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800'>\n                  {person.role}\n                </span>\n              </div>\n              <p className='mt-1 truncate text-sm text-gray-500'>\n                {person.title}\n              </p>\n            </div>\n            {person.imageUrl ? (\n              <img\n                className='h-10 w-10 flex-shrink-0 rounded-full bg-white'\n                src={person.imageUrl}\n                alt=''\n              />\n            ) : null}\n          </div>\n          {person.description ? (\n            <div className='grow p-3 text-base text-gray-700'>\n              {person.description}\n            </div>\n          ) : null}\n          <div>\n            <div className='-mt-px flex divide-x divide-gray-200'>\n              {person.email ? (\n                <div className='flex w-0 flex-1'>\n                  <a\n                    href={`mailto:${person.email}`}\n                    className='relative -mr-px inline-flex w-0 flex-1 items-center justify-center rounded-bl-lg border border-transparent py-4 text-sm font-medium text-gray-700 hover:text-gray-500'\n                    target='_blank'\n                    rel='noreferrer'\n                  >\n                    <EnvelopeIcon\n                      className='h-5 w-5 text-gray-400'\n                      aria-hidden='true'\n                    />\n                    <span className='ml-3'>Email</span>\n                  </a>\n                </div>\n              ) : null}\n              {person.website ? (\n                <div className='flex w-0 flex-1'>\n                  <a\n                    href={person.website}\n                    className='relative -mr-px inline-flex w-0 flex-1 items-center justify-center rounded-bl-lg border border-transparent py-4 text-sm font-medium text-gray-700 hover:text-gray-500'\n                    target='_blank'\n                    rel='noreferrer'\n                  >\n                    <LinkIcon\n                      className='h-5 w-5 text-gray-400'\n                      aria-hidden='true'\n                    />\n                    <span className='ml-3'>Website</span>\n                  </a>\n                </div>\n              ) : null}\n\n              {person.slack ? (\n                <div className='flex w-0 flex-1'>\n                  <a\n                    href={person.slack}\n                    className='relative -mr-px inline-flex w-0 flex-1 items-center justify-center rounded-bl-lg border border-transparent py-4 text-sm font-medium text-gray-700 hover:text-gray-500'\n                    target='_blank'\n                    rel='noreferrer'\n                  >\n                    <ChatBubbleLeftIcon\n                      className='h-5 w-5 text-gray-400'\n                      aria-hidden='true'\n                    />\n                    <span className='ml-3'>Slack</span>\n                  </a>\n                </div>\n              ) : null}\n\n              {person.linkedin ? (\n                <div className='flex w-0 flex-1'>\n                  <a\n                    href={person.linkedin}\n                    className='relative -mr-px inline-flex w-0 flex-1 items-center justify-center rounded-bl-lg border border-transparent py-4 text-sm font-medium text-gray-700 hover:text-gray-500'\n                    target='_blank'\n                    rel='noreferrer'\n                  >\n                    <svg\n                      xmlns='http://www.w3.org/2000/svg'\n                      viewBox='0 0 24 24'\n                      className='inline-block h-4 w-4 fill-gray-400'\n                    >\n                      <path d='M4.98 3.5c0 1.381-1.11 2.5-2.48 2.5s-2.48-1.119-2.48-2.5c0-1.38 1.11-2.5 2.48-2.5s2.48 1.12 2.48 2.5zm.02 4.5h-5v16h5v-16zm7.982 0h-4.968v16h4.969v-8.399c0-4.67 6.029-5.052 6.029 0v8.399h4.988v-10.131c0-7.88-8.922-7.593-11.018-3.714v-2.155z' />\n                    </svg>\n                    <span className='ml-3'>LinkedIn</span>\n                  </a>\n                </div>\n              ) : null}\n            </div>\n          </div>\n        </li>\n      ))}\n    </ul>\n  )\n}\n"
  },
  {
    "path": "src/Pages/Panel/Performance/Performance.tsx",
    "content": "import React, { PropsWithChildren } from 'react'\nimport { Hideable } from '@/Utils/Hideable'\nimport { HTMLTable, Tag } from '@blueprintjs/core'\nimport { usePanelStore } from '@/Stores/PanelStore'\nimport { observer } from 'mobx-react-lite'\nimport styled from 'styled-components'\nimport { StatusBar } from '@/Components/StatusBar'\nimport { Button } from '@/Components/Button'\n\ntype Props = { isVisible: boolean }\n\nconst Wrapper = styled.div`\n  overflow-y: auto !important;\n\n  table,\n  tbody {\n    width: 100%;\n    max-width: 100%;\n  }\n\n  table,\n  tbody,\n  tr,\n  td,\n  td span {\n    font-size: 11px !important;\n  }\n`\n\nexport const Performance = observer(\n  ({ isVisible }: PropsWithChildren<Props>) => {\n    const panelStore = usePanelStore()\n    const { renderData } = panelStore.performanceStore\n\n    return (\n      <Hideable isVisible={isVisible}>\n        <Wrapper className='mde-content'>\n          <HTMLTable condensed interactive>\n            <thead>\n              <tr>\n                <th>Collection</th>\n                <th>Method</th>\n                <th>Arguments</th>\n                <th>Total</th>\n                <th>Average</th>\n                <th>Calls</th>\n              </tr>\n            </thead>\n            <tbody>\n              {renderData.map(data => (\n                <tr key={data.key}>\n                  <td>\n                    <Tag minimal>{data.collectionName}</Tag>\n                  </td>\n                  <td>\n                    <Tag minimal>{data.method}</Tag>\n                  </td>\n                  <td>\n                    <Tag style={{ maxWidth: '50vw' }} minimal>\n                      {data.args}\n                    </Tag>\n                  </td>\n                  <td>\n                    <Tag minimal>{Math.round(data.runtime)} ms</Tag>\n                  </td>\n                  <td>\n                    <Tag minimal>{data.averageRuntime.toFixed(3)} ms</Tag>\n                  </td>\n                  <td>\n                    <Tag minimal>{data.calls}x</Tag>\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </HTMLTable>\n        </Wrapper>\n\n        <StatusBar>\n          <div className='right-group'>\n            <Button\n              intent='warning'\n              onClick={() => panelStore.performanceStore.clear()}\n              icon='inbox'\n            >\n              {panelStore.performanceStore.callMap.size ?? 0}\n            </Button>\n          </div>\n        </StatusBar>\n      </Hideable>\n    )\n  },\n)\n"
  },
  {
    "path": "src/Pages/Panel/Subscriptions/Subscriptions.tsx",
    "content": "import { usePanelStore } from '@/Stores/PanelStore'\nimport { Hideable } from '@/Utils/Hideable'\nimport { observer } from 'mobx-react-lite'\nimport React, { FormEvent, FunctionComponent } from 'react'\nimport { HTMLTable, Tag } from '@blueprintjs/core'\nimport styled from 'styled-components'\nimport { sortBy } from 'lodash'\nimport { useInterval } from '@/Utils/Hooks/useInterval'\nimport { syncSubscriptions } from '@/Bridge'\nimport { StatusBar } from '@/Components/StatusBar'\nimport { Field } from '@/Components/Field'\nimport { TextInput } from '@/Components/TextInput'\n\ninterface Props {\n  isVisible: boolean\n}\n\nconst Wrapper = styled.div`\n  overflow-y: auto !important;\n\n  table,\n  tbody {\n    width: 100%;\n    max-width: 100%;\n  }\n\n  tbody {\n    font-family: monospace;\n  }\n\n  table,\n  tbody,\n  tr,\n  td,\n  td span {\n    font-size: 11px !important;\n  }\n`\n\nexport const Subscriptions: FunctionComponent<Props> = observer(\n  ({ isVisible }) => {\n    useInterval(() => isVisible && syncSubscriptions(), 5000)\n\n    const panelStore = usePanelStore()\n\n    const subscriptions = sortBy(\n      panelStore.subscriptionStore.subsWithMeta,\n      'meta.init.timestamp',\n    )\n\n    return (\n      <Hideable isVisible={isVisible}>\n        <Wrapper className='mde-content'>\n          <HTMLTable condensed interactive>\n            <thead>\n              <tr>\n                <th>ID</th>\n                <th>Name</th>\n                <th>Params</th>\n                <th>Active</th>\n                <th>Ready</th>\n                <th>Duration</th>\n              </tr>\n            </thead>\n            <tbody>\n              {subscriptions.map(subscription => {\n                const duration =\n                  panelStore.ddpStore.getSubscriptionDuration(subscription)\n\n                return (\n                  <tr\n                    key={subscription.id}\n                    onClick={() =>\n                      panelStore.setActiveObject(\n                        {\n                          params: subscription.params,\n                        },\n                        `${subscription.name} [${subscription.id}]`,\n                      )\n                    }\n                  >\n                    <td>\n                      <Tag minimal>{subscription.id}</Tag>\n                    </td>\n                    <td>\n                      <Tag style={{ maxWidth: '25vw' }} minimal>\n                        {subscription.name}\n                      </Tag>\n                    </td>\n                    <td>\n                      <Tag style={{ maxWidth: '25vw' }} minimal>\n                        {JSON.stringify(subscription.params)}\n                      </Tag>\n                    </td>\n                    <td>\n                      <Tag\n                        minimal\n                        intent={subscription.inactive ? 'warning' : 'success'}\n                      >\n                        {JSON.stringify(!subscription.inactive)}\n                      </Tag>\n                    </td>\n                    <td>\n                      <Tag\n                        minimal\n                        intent={subscription.ready ? 'success' : 'warning'}\n                      >\n                        {JSON.stringify(subscription.ready)}\n                      </Tag>\n                    </td>\n                    <td>\n                      <Tag minimal>{duration}</Tag>\n                    </td>\n                  </tr>\n                )\n              })}\n            </tbody>\n          </HTMLTable>\n        </Wrapper>\n\n        <StatusBar>\n          <TextInput\n            icon='search'\n            placeholder='Search...'\n            onChange={(event: FormEvent<HTMLInputElement>) =>\n              panelStore.subscriptionStore.pagination.setSearch(\n                event.currentTarget.value,\n              )\n            }\n          />\n\n          <div className='right-group'>\n            <Field icon='feed-subscribed'>{subscriptions.length}</Field>\n          </div>\n        </StatusBar>\n      </Hideable>\n    )\n  },\n)\n"
  },
  {
    "path": "src/Pages/Panel.tsx",
    "content": "import { PanelStoreProvider, usePanelStore } from '@/Stores/PanelStore'\nimport { observer } from 'mobx-react-lite'\nimport React, { FunctionComponent, useEffect, useRef } from 'react'\nimport { Bookmarks } from './Panel/Bookmarks/Bookmarks'\nimport { DDP } from './Panel/DDP/DDP'\nimport { DrawerJSON } from './Panel/DrawerJSON'\nimport { DrawerStackTrace } from './Panel/DrawerStackTrace'\nimport { Minimongo } from './Panel/Minimongo/Minimongo'\nimport { Navigation } from './Panel/Navigation'\nimport { Bridge } from '@/Bridge'\nimport { PanelPage } from '@/Constants'\nimport { Subscriptions } from '@/Pages/Panel/Subscriptions/Subscriptions'\nimport styled from 'styled-components'\nimport {\n  MIN_LAYOUT_WIDTH,\n  NAVBAR_HEIGHT,\n  STATUS_HEIGHT,\n} from '@/Styles/Constants'\nimport { Performance } from '@/Pages/Panel/Performance/Performance'\nimport { useAnalytics } from '@/Utils/Hooks/useAnalytics'\nimport { HelpDrawer } from './Panel/HelpDrawer'\n\nBridge.init()\n\nconst Layout = styled.div`\n  display: flex;\n  flex-direction: column;\n\n  position: relative;\n\n  padding-top: ${NAVBAR_HEIGHT}px;\n  padding-bottom: ${STATUS_HEIGHT}px;\n  max-height: 100vh;\n\n  min-width: ${MIN_LAYOUT_WIDTH}px;\n\n  .mde-navbar {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n  }\n\n  .mde-layout__tab-panel {\n    position: relative;\n\n    .mde-content {\n      height: calc(100vh - ${NAVBAR_HEIGHT + STATUS_HEIGHT}px);\n      padding: 0;\n      overflow: hidden;\n    }\n  }\n`\n\nconst PanelObserverComponent: FunctionComponent = observer(() => {\n  const store = usePanelStore()\n  const panelRef = useRef<HTMLDivElement>(null)\n  const analytics = useAnalytics()\n\n  useEffect(() => {\n    analytics?.pageView().catch(console.error)\n  }, [analytics])\n\n  return (\n    <Layout>\n      <DrawerJSON\n        title={store.activeObjectTitle}\n        viewableObject={store.activeObject}\n        onClose={() => {\n          store.setActiveObject(null, null)\n        }}\n      />\n\n      <DrawerStackTrace\n        activeStackTrace={store.activeStackTrace}\n        onClose={() => store.setActiveStackTrace(null)}\n      />\n\n      <HelpDrawer\n        isHelpDrawerVisible={store.isHelpDrawerVisible}\n        onClose={() => store.setHelpDrawerVisible(false)}\n      />\n\n      <Navigation />\n\n      <div className='mde-layout__tab-panel' ref={panelRef}>\n        <DDP isVisible={store.selectedTabId === PanelPage.DDP} />\n        <Bookmarks isVisible={store.selectedTabId === PanelPage.BOOKMARKS} />\n        <Minimongo isVisible={store.selectedTabId === PanelPage.MINIMONGO} />\n        <Performance\n          isVisible={store.selectedTabId === PanelPage.PERFORMANCE}\n        />\n        <Subscriptions\n          isVisible={store.selectedTabId === PanelPage.SUBSCRIPTIONS}\n        />\n      </div>\n    </Layout>\n  )\n})\n\nexport const Panel = () => (\n  <PanelStoreProvider>\n    <PanelObserverComponent />\n  </PanelStoreProvider>\n)\n"
  },
  {
    "path": "src/Pages/Popup.tsx",
    "content": "import React, { FunctionComponent } from 'react'\n\nexport const Popup: FunctionComponent = () => (\n  <div>\n    <h1>Popup</h1>\n  </div>\n)\n"
  },
  {
    "path": "src/Stores/Common/Searchable.ts",
    "content": "import { DEFAULT_OFFSET } from '@/Constants'\nimport { calculatePagination } from '@/Utils/Pagination'\nimport debounce from 'lodash.debounce'\nimport { action, computed, observable, runInAction } from 'mobx'\n\ntype BufferCallback<T> = ((buffer: T[]) => void) | null\ntype FilterFunction<T> = ((collection: T[], search: string) => T[]) | null\n\nexport abstract class Searchable<T> {\n  bufferCallback: BufferCallback<T> = null\n  filterFunction: FilterFunction<T> = null\n\n  lastPush: number = 0\n  loadingTimeout: ReturnType<typeof setTimeout> | null = null\n\n  buffer: T[] = []\n\n  @observable.shallow collection: T[] = []\n\n  @observable currentPage: number = 1\n  @observable search: string = ''\n  @observable isLoading: boolean = false\n\n  @action\n  setCollection(collection: T[]) {\n    this.collection = collection\n  }\n\n  pushItem(log: T) {\n    this.lastPush = Date.now()\n\n    if (!this.isLoading) {\n      runInAction(() => {\n        this.isLoading = true\n      })\n    }\n\n    this.buffer.push(log)\n\n    this.submitLogs()\n\n    this.setLoadingState(false)\n  }\n\n  submitLogs = debounce(\n    action(() => {\n      this._submitLogs()\n    }),\n    100,\n    {\n      maxWait: 1000,\n    },\n  )\n\n  @action\n  _submitLogs() {\n    if (this.bufferCallback) {\n      this.bufferCallback(this.buffer)\n    }\n\n    console.log('submitted')\n\n    this.collection.unshift(...this.buffer.reverse())\n\n    this.buffer = []\n  }\n\n  setSearch = debounce(\n    action((search: string) => {\n      this.search = search\n      this.currentPage = 1\n    }),\n    250,\n  )\n\n  setLoadingState(isLoading: boolean) {\n    if (this.loadingTimeout) {\n      clearTimeout(this.loadingTimeout)\n    }\n\n    this.loadingTimeout = setTimeout(\n      action(() => {\n        this.isLoading = isLoading\n\n        console.log('loading:false')\n      }),\n      250,\n    )\n  }\n\n  @action\n  setCurrentPage(currentPage: number) {\n    this.currentPage = currentPage\n  }\n\n  @computed\n  get filtered() {\n    return this.filterFunction\n      ? this.filterFunction(this.collection, this.search)\n      : this.collection\n  }\n\n  @computed\n  get pagination() {\n    return calculatePagination(\n      DEFAULT_OFFSET,\n      this.filtered.length,\n      this.currentPage,\n      this.setSearch.bind(this),\n      this.setCurrentPage.bind(this),\n    )\n  }\n\n  @computed\n  get paginated() {\n    return this.filtered.slice(this.pagination.start, this.pagination.end)\n  }\n}\n"
  },
  {
    "path": "src/Stores/Panel/BookmarkStore.ts",
    "content": "import { PanelDatabase } from '@/Database/PanelDatabase'\nimport { action, computed, makeObservable, observable, runInAction } from 'mobx'\nimport { Searchable } from '../Common/Searchable'\nimport { PanelStore } from '@/Stores/PanelStore'\n\nexport class BookmarkStore extends Searchable<Bookmark> {\n  constructor() {\n    super()\n    makeObservable(this)\n  }\n\n  @observable.shallow bookmarkIds: (string | undefined)[] = []\n\n  async sync() {\n    const collection = await PanelDatabase.getAll()\n\n    runInAction(() => {\n      this.collection = collection\n      this.bookmarkIds = this.collection.map(\n        (bookmark: Bookmark) => bookmark.id,\n      )\n    })\n  }\n\n  @action\n  async remove(log: DDPLog) {\n    if (log.timestamp) {\n      await PanelDatabase.remove(log.id)\n      await this.sync()\n    }\n  }\n\n  @action\n  async add(log: DDPLog) {\n    const key = await PanelDatabase.add(log)\n    const bookmark = await PanelDatabase.get(key)\n\n    if (bookmark) {\n      runInAction(() => {\n        this.collection.push(bookmark)\n        this.bookmarkIds.push(bookmark.log.id)\n      })\n    }\n  }\n\n  filterFunction = (collection: Bookmark[], search: string) =>\n    collection\n      .filter(\n        bookmark => !this.filterRegularExpression.test(bookmark.log.content),\n      )\n      .filter(\n        bookmark =>\n          !search ||\n          bookmark.log.content.toLowerCase().includes(search.toLowerCase()),\n      )\n\n  @computed\n  get filterRegularExpression() {\n    return new RegExp(\n      `\"msg\":\"(${PanelStore.settingStore.activeFilterBlacklist.join('|')})\"`,\n    )\n  }\n}\n"
  },
  {
    "path": "src/Stores/Panel/DDPStore.ts",
    "content": "import debounce from 'lodash.debounce'\nimport { action, computed, makeObservable, observable, runInAction } from 'mobx'\nimport { Searchable } from '../Common/Searchable'\nimport { PanelStore } from '@/Stores/PanelStore'\nimport { generatePreview } from '@/Utils/MessageFormatter'\nimport { clearCache } from '@/Bridge'\n\nexport class DDPStore extends Searchable<DDPLog> {\n  @observable inboundBytes = 0\n  @observable outboundBytes = 0\n  @observable newLogs: string[] = []\n\n  constructor() {\n    super()\n    makeObservable(this)\n  }\n\n  bufferCallback = (buffer: DDPLog[]) => {\n    this.buffer = buffer.map((log: DDPLog) => ({\n      ...log,\n      preview: generatePreview(\n        log.content,\n        log.parsedContent as DDPLogContent,\n        log.filterType,\n      ),\n    }))\n\n    this.newLogs.push(...buffer.map(({ id }) => id))\n\n    this.inboundBytes += buffer\n      .filter(log => log.isInbound)\n      .reduce((sum, log) => sum + (log.size ?? 0), 0)\n\n    this.outboundBytes += buffer\n      .filter(log => log.isOutbound)\n      .reduce((sum, log) => sum + (log.size ?? 0), 0)\n\n    this.clearNewLogs()\n  }\n\n  // eslint-disable-next-line unicorn/consistent-function-scoping\n  clearNewLogs = debounce(() => {\n    runInAction(() => {\n      this.newLogs = []\n    })\n  }, 1000)\n\n  filterFunction = (collection: DDPLog[], search: string) =>\n    collection\n      .filter(log => !this.filterRegularExpression.test(log.content))\n      .filter(\n        log =>\n          !search ||\n          `${log.content.toLowerCase()}${log.preview ?? ''}`.includes(\n            search.toLowerCase(),\n          ),\n      )\n\n  @action\n  clearLogs() {\n    this.collection = []\n    this.inboundBytes = 0\n    this.outboundBytes = 0\n\n    clearCache()\n  }\n\n  @computed\n  get filterRegularExpression() {\n    return new RegExp(\n      `\"msg\":\"(${PanelStore.settingStore.activeFilterBlacklist.join('|')})\"`,\n    )\n  }\n\n  @computed\n  get subscriptionLogs() {\n    return this.collection.filter(\n      log =>\n        log.parsedContent.msg === 'ready' || log.parsedContent.msg === 'sub',\n    )\n  }\n\n  getSubscriptionInit(subscription) {\n    return this.subscriptionLogs.find(\n      log => log.parsedContent.id === subscription.id,\n    )\n  }\n\n  getSubscriptionReady(subscription) {\n    return this.subscriptionLogs.find(log =>\n      log.parsedContent.subs?.includes?.(subscription.id),\n    )\n  }\n\n  getSubscriptionDuration(subscription) {\n    const initLog = this.getSubscriptionInit(subscription)\n    const readyLog = this.getSubscriptionReady(subscription)\n\n    if (initLog && readyLog)\n      return `${readyLog.timestamp - initLog.timestamp}ms`\n\n    if (readyLog) return `???`\n\n    if (initLog) return `waiting`\n\n    return 'NA'\n  }\n\n  getSubscriptionMeta(subscription) {\n    return {\n      meta: {\n        init: this.getSubscriptionInit(subscription),\n        ready: this.getSubscriptionReady(subscription),\n      },\n    }\n  }\n}\n"
  },
  {
    "path": "src/Stores/Panel/MinimongoStore/CollectionStore.ts",
    "content": "import { Searchable } from '@/Stores/Common/Searchable'\nimport { makeObservable } from 'mobx'\n\nexport class CollectionStore extends Searchable<IDocumentWrapper> {\n  constructor() {\n    super()\n    makeObservable(this)\n  }\n\n  filterFunction = (collection: IDocumentWrapper[], search: string) =>\n    collection.filter(\n      document =>\n        !search ||\n        JSON.stringify(document).toLowerCase().includes(search.toLowerCase()),\n    )\n}\n"
  },
  {
    "path": "src/Stores/Panel/MinimongoStore/index.ts",
    "content": "import debounce from 'lodash.debounce'\nimport { action, computed, makeObservable, observable } from 'mobx'\nimport { CollectionStore } from './CollectionStore'\nimport { JSONUtils } from '@/Utils/JSONUtils'\nimport { StringUtils } from '@/Utils/StringUtils'\nimport prettyBytes from 'pretty-bytes'\nimport { mapValues } from '@/Utils/Objects'\n\nexport class MinimongoStore {\n  activeCollectionDocuments = new CollectionStore()\n\n  @observable collections: MinimongoCollections = {}\n  @observable collectionMetadata: ICollectionMetadata = {}\n  @observable activeCollection: string | null = null\n  @observable search: string = ''\n  @observable collectionColorMap: Record<string, string> = {}\n  @observable isNavigatorVisible = false\n\n  constructor() {\n    makeObservable(this)\n  }\n\n  @computed\n  get totalDocuments() {\n    return Object.values(this.collections).reduce(\n      (acc, cur) => acc + cur.length,\n      0,\n    )\n  }\n\n  @computed\n  get collectionNames() {\n    return Object.keys(this.collections).sort()\n  }\n\n  @computed\n  get filteredCollectionNames() {\n    return this.collectionNames.filter(\n      name =>\n        !this.search || name.toLowerCase().includes(this.search.toLowerCase()),\n    )\n  }\n\n  @computed\n  get totalSize() {\n    return Object.entries(this.collectionMetadata).reduce(\n      (sum, [collectionName, metadata]) => sum + metadata.collectionSize,\n      0,\n    )\n  }\n\n  @action\n  getMetadata(collectionName: string) {\n    return this.collectionMetadata?.[collectionName]\n  }\n\n  @action\n  computeCollectionSizes() {\n    for (const collectionName of Object.keys(this.collections)) {\n      const collectionSize = this.collections[collectionName].reduce(\n        (acc: number, cur: IDocumentWrapper) => acc + cur._size,\n        0,\n      )\n\n      this.collectionMetadata[collectionName] = {\n        collectionSize,\n        collectionSizePretty: prettyBytes(collectionSize),\n      }\n    }\n  }\n\n  @action\n  syncDocuments() {\n    if (this.activeCollection) {\n      return this.activeCollectionDocuments.setCollection(\n        this.collections[this.activeCollection],\n      )\n    }\n\n    this.activeCollectionDocuments.setCollection(\n      Object.entries(this.collections).flatMap(\n        ([collectionName, documents]) => {\n          return documents\n        },\n      ),\n    )\n  }\n\n  @action\n  setCollections(collections: RawCollections) {\n    this.collections = mapValues(collections, (collection, collectionName) => {\n      return collection.map(document =>\n        MinimongoStore.wrapDocument(document, collectionName),\n      )\n    })\n\n    this.computeCollectionSizes()\n\n    this.syncDocuments()\n  }\n\n  @action\n  setActiveCollection(collection: string | null) {\n    this.activeCollection = collection\n\n    this.syncDocuments()\n  }\n\n  setSearch = debounce(\n    action((search: string) => (this.search = search)),\n    250,\n  )\n\n  @action\n  setNavigatorVisible(isVisible: boolean) {\n    this.isNavigatorVisible = isVisible\n  }\n\n  static wrapDocument(\n    document: IDocument,\n    collectionName: string,\n  ): IDocumentWrapper {\n    const _string = JSONUtils.stringify(document)\n\n    console.log({ collectionName })\n\n    return {\n      collectionName,\n      document,\n      _string,\n      _size: StringUtils.getSize(_string),\n    }\n  }\n}\n"
  },
  {
    "path": "src/Stores/Panel/PerformanceStore.ts",
    "content": "import { action, makeObservable, observable } from 'mobx'\nimport sortBy from 'lodash.sortby'\nimport debounce from 'lodash.debounce'\n\ntype AccCallData = {\n  collectionName: string\n  key: string\n  method: string\n  args: string\n  runtime: number\n  averageRuntime: number\n  updatedAt: number\n  calls: number\n}\n\nexport class PerformanceStore<T> {\n  constructor() {\n    makeObservable(this)\n  }\n\n  callMap = new Map<string, AccCallData>()\n\n  @observable.shallow\n  renderData: AccCallData[] = []\n\n  updateRenderData = debounce(\n    action(() => {\n      this.renderData = sortBy(\n        [...this.callMap.values()],\n        ['runtime', 'args', 'method', 'collectionName'],\n      )\n        .reverse()\n        .slice(0, 100)\n    }),\n    250,\n    {\n      maxWait: 5000,\n    },\n  )\n\n  push(data: CallData) {\n    const key = `${data.collectionName}${data.key}${data.args}`\n\n    if (this.callMap.has(key)) {\n      const existingData = this.callMap.get(key)\n\n      const runtime = (existingData?.runtime ?? 0) + data.runtime\n\n      this.callMap.set(key, {\n        collectionName: data.collectionName,\n        key,\n        method: data.key,\n        args: data.args,\n        runtime,\n        averageRuntime: runtime / existingData.calls,\n        updatedAt: Date.now(),\n        calls: existingData.calls + 1,\n      })\n    } else {\n      this.callMap.set(key, {\n        collectionName: data.collectionName,\n        key,\n        method: data.key,\n        args: data.args,\n        runtime: data?.runtime,\n        averageRuntime: data?.runtime,\n        updatedAt: Date.now(),\n        calls: 1,\n      })\n    }\n\n    this.updateRenderData()\n  }\n\n  @action\n  clear() {\n    this.callMap.clear()\n    this.renderData = []\n  }\n}\n"
  },
  {
    "path": "src/Stores/Panel/SettingStore.ts",
    "content": "import {\n  action,\n  makeObservable,\n  observable,\n  reaction,\n  runInAction,\n  toJS,\n} from 'mobx'\nimport { PanelDatabase } from '@/Database/PanelDatabase'\nimport { FilterCriteria } from '@/Pages/Panel/DDP/FilterConstants'\nimport { compact, flatten, omit } from '@/Utils/Objects'\n\nexport class SettingStore implements ISettings {\n  hydrated = false\n\n  @observable repositoryData: IGitHubRepository | null = null\n\n  @observable activeFilterBlacklist: string[] = []\n\n  @observable activeFilters: FilterTypeMap<boolean> = {\n    heartbeat: true,\n    subscription: true,\n    collection: true,\n    method: true,\n    connection: true,\n  }\n\n  constructor() {\n    makeObservable(this)\n\n    PanelDatabase.getSettings().then(settings => {\n      runInAction(() => {\n        Object.assign(this, settings)\n      })\n\n      setTimeout(() => {\n        runInAction(() => {\n          this.hydrated = true\n        })\n      }, 1000)\n    })\n\n    reaction(\n      () => toJS(this),\n      (data: ISettings) => {\n        if (this.hydrated) {\n          PanelDatabase.saveSettings(omit(data, ['hydrated']) as ISettings)\n            .then(() => {\n              console.log('Settings updated.')\n            })\n\n            .catch(console.error)\n        }\n      },\n    )\n  }\n\n  @action\n  setRepositoryData(repositoryData: IGitHubRepository) {\n    this.repositoryData = repositoryData\n  }\n\n  @action\n  updateRepositoryData() {\n    fetch(\n      'https://api.github.com/repos/leonardoventurini/meteor-devtools-evolved',\n    )\n      .then(response => response.json())\n      .then(data => {\n        if (data) {\n          if (!data.stargazers_count || !data.open_issues_count) {\n            console.log('Not updating repository data', data)\n            return\n          }\n\n          runInAction(() => {\n            this.setRepositoryData(data)\n          })\n        }\n      })\n\n      .catch(console.error)\n  }\n\n  @action\n  setFilter(type: FilterType, isEnabled: boolean) {\n    this.activeFilters[type] = isEnabled\n\n    this.activeFilterBlacklist = flatten(\n      compact(\n        Object.entries(this.activeFilters).map(([type, isEnabled]) => {\n          return isEnabled ? false : FilterCriteria[type as FilterType]\n        }),\n      ),\n    )\n  }\n}\n"
  },
  {
    "path": "src/Stores/Panel/SubscriptionStore.ts",
    "content": "import { Searchable } from '@/Stores/Common/Searchable'\nimport { computed, makeObservable } from 'mobx'\nimport { PanelStore } from '@/Stores/PanelStore'\n\nexport class SubscriptionStore extends Searchable<IMeteorSubscription> {\n  constructor() {\n    super()\n    makeObservable(this)\n  }\n\n  filterFunction = (collection: IMeteorSubscription[], search: string) =>\n    collection.filter(\n      document =>\n        !search ||\n        JSON.stringify(document).toLowerCase().includes(search.toLowerCase()),\n    )\n\n  @computed\n  get subsWithMeta() {\n    return this.filtered.map(sub => ({\n      ...sub,\n      ...PanelStore.ddpStore.getSubscriptionMeta(sub),\n    }))\n  }\n}\n"
  },
  {
    "path": "src/Stores/PanelStore.tsx",
    "content": "import { action, makeObservable, observable, toJS } from 'mobx'\nimport React, { createContext, FunctionComponent, useContext } from 'react'\nimport { BookmarkStore } from './Panel/BookmarkStore'\nimport { DDPStore } from './Panel/DDPStore'\nimport { MinimongoStore } from './Panel/MinimongoStore'\nimport { PanelPage } from '@/Constants'\nimport { SettingStore } from '@/Stores/Panel/SettingStore'\nimport { SubscriptionStore } from '@/Stores/Panel/SubscriptionStore'\nimport { PerformanceStore } from './Panel/PerformanceStore'\n\nexport class PanelStoreConstructor {\n  @observable selectedTabId: string = PanelPage.DDP\n\n  @observable activeObjectTitle: string | null = null\n  @observable activeObject: ViewableObject = null\n  @observable.shallow activeStackTrace: StackTrace[] | null = null\n\n  @observable isHelpDrawerVisible = false\n  @observable subscriptions: Record<string, IMeteorSubscription> = {}\n\n  @observable gitCommitHash?: string | null = null\n\n  ddpStore = new DDPStore()\n  bookmarkStore = new BookmarkStore()\n  minimongoStore = new MinimongoStore()\n  subscriptionStore = new SubscriptionStore()\n  settingStore = new SettingStore()\n  performanceStore = new PerformanceStore()\n\n  constructor() {\n    makeObservable(this)\n\n    this.bookmarkStore.sync().catch(console.error)\n  }\n\n  @action\n  syncSubscriptions(subscriptions: Record<MeteorID, IMeteorSubscription>) {\n    this.subscriptionStore.setCollection(Object.values(subscriptions))\n  }\n\n  @action\n  setActiveObject(viewableObject: ViewableObject, title: string | null = null) {\n    this.activeObject = viewableObject\n    this.activeObjectTitle = title\n  }\n\n  @action\n  setActiveStackTrace(trace: StackTrace[] | null) {\n    this.activeStackTrace = trace\n  }\n\n  @action\n  setSelectedTabId(selectedTabId: string) {\n    this.selectedTabId = selectedTabId\n  }\n\n  @action\n  setHelpDrawerVisible(isHelpDrawerVisible: boolean) {\n    this.isHelpDrawerVisible = isHelpDrawerVisible\n  }\n\n  @action\n  getSubscriptionById(id: string) {\n    const subs = toJS(this.subscriptions)\n\n    return id in subs ? subs[id] : null\n  }\n\n  @action\n  setGitCommitHash(hash: string) {\n    this.gitCommitHash = hash\n  }\n}\n\nexport const PanelStore = new PanelStoreConstructor()\n\nconst PanelStoreContext = createContext<PanelStoreConstructor | null>(null)\n\nexport const PanelStoreProvider: FunctionComponent = ({ children }) => (\n  <PanelStoreContext.Provider value={PanelStore}>\n    {children}\n  </PanelStoreContext.Provider>\n)\n\nexport const usePanelStore = () => {\n  const store = useContext(PanelStoreContext)\n\n  if (!store) {\n    throw new Error('Must be used within a provider.')\n  }\n\n  return store\n}\n"
  },
  {
    "path": "src/Styles/App.scss",
    "content": "@import '~normalize.css/normalize.css';\n@import '~@blueprintjs/core/lib/css/blueprint.css';\n@import '~@blueprintjs/popover2/lib/css/blueprint-popover2.css';\n@import '~@blueprintjs/icons/lib/css/blueprint-icons.css';\n\n@import 'Utils';\n\n$background-color: #30404d;\n\n::-webkit-scrollbar {\n  width: 10px;\n  background: transparent;\n}\n\n::-webkit-scrollbar-track {\n  -webkit-box-shadow: none;\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  -webkit-box-shadow: none;\n  background-color: lighten($background-color, 15%);\n}\n\nhtml,\nbody {\n  font-size: 12px;\n}\n\nbody {\n  background-color: $background-color;\n  overflow: hidden;\n}\n\npre {\n  white-space: pre-wrap;\n}\n\n.truncated {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.mde-stack-trace {\n  pre {\n    margin-bottom: 4px;\n  }\n}\n\n.bp3-menu-item {\n  i.fas,\n  i.fab {\n    margin-top: 2px;\n    font-size: 16px;\n  }\n}\n"
  },
  {
    "path": "src/Styles/Breakpoints.ts",
    "content": "import { mapValues } from '@/Utils/Objects'\nimport { css, FlattenSimpleInterpolation } from 'styled-components'\n\ntype BreakpointLabel = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\nexport const Breakpoints: Record<BreakpointLabel, number> = {\n  xs: 0,\n  sm: 600,\n  md: 960,\n  lg: 1280,\n  xl: 1920,\n}\n\nexport const respond = mapValues(\n  Breakpoints,\n  (value: number) => (content: FlattenSimpleInterpolation) => css`\n    @media (min-width: ${value}px) {\n      ${content};\n    }\n  `,\n)\n"
  },
  {
    "path": "src/Styles/Constants.ts",
    "content": "export const MIN_LAYOUT_WIDTH = 600\nexport const NAVBAR_HEIGHT = 29\nexport const STATUS_HEIGHT = 29\nexport const BACKGROUND_COLOR = '#30404d'\n"
  },
  {
    "path": "src/Styles/Mixins.ts",
    "content": "import { css } from 'styled-components'\n\nexport const truncate = () => css`\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n`\n\nexport const centerItems = () => css`\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n`\n"
  },
  {
    "path": "src/Styles/Tailwind.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n.section {\n  @apply text-base;\n}\n\n.section-title {\n  @apply mb-4 font-medium uppercase text-gray-500;\n}\n"
  },
  {
    "path": "src/Styles/_Utils.scss",
    "content": "@mixin truncate {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n"
  },
  {
    "path": "src/Utils/BackgroundEvents.ts",
    "content": "import browser from 'webextension-polyfill'\n\nexport const openTab = (url: string): void => {\n  browser.runtime\n    .sendMessage({\n      source: 'meteor-devtools-evolved',\n      eventType: 'create-tab',\n      data: { url: url },\n    })\n    .catch(console.error)\n}\n"
  },
  {
    "path": "src/Utils/Hideable.tsx",
    "content": "import React, { FunctionComponent, HTMLProps } from 'react'\n\ninterface Props {\n  isVisible: boolean\n}\n\nexport const Hideable: FunctionComponent<Props & HTMLProps<HTMLDivElement>> = ({\n  children,\n  isVisible,\n  ...props\n}) => {\n  const styles = {\n    display: isVisible ? undefined : 'none',\n  }\n\n  return (\n    <div className='hideable' style={styles} {...props}>\n      {children}\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/Utils/Hooks/useAnalytics.ts",
    "content": "import { singletonHook } from 'react-singleton-hook'\nimport { useEffect, useState } from 'react'\nimport { Analytics } from '@/Analytics'\n\nexport const useAnalytics = singletonHook(null, () => {\n  const [instance, setInstance] = useState<Analytics>()\n\n  useEffect(() => {\n    const GA_TID = 'UA-211731487-1'\n\n    setInstance(new Analytics(GA_TID, { userAgent: navigator.userAgent }))\n  }, [])\n\n  return instance\n})\n"
  },
  {
    "path": "src/Utils/Hooks/useBreakpoints.ts",
    "content": "import { useRef } from 'react'\nimport { useDimensions } from '@/Utils/Hooks/useDimensions'\n\ntype Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'navigationCollapse'\n\nexport const useBreakpoints = () => {\n  const ref = useRef(document.body)\n\n  const { width } = useDimensions(ref, [])\n\n  const breakpoints: { [key in Breakpoint]: boolean } = {\n    xs: width <= 360,\n    sm: width <= 720,\n    md: width <= 1280,\n    lg: width <= 1920,\n    xl: width > 1920,\n    navigationCollapse: width <= 980,\n  }\n\n  return breakpoints\n}\n"
  },
  {
    "path": "src/Utils/Hooks/useDimensions.ts",
    "content": "import { RefObject, useEffect, useState } from 'react'\nimport { useResize } from '@/Utils/Hooks/useResize'\n\nexport const useDimensions = (ref: RefObject<HTMLElement>, deps: any[]) => {\n  const [dimensions, setDimensions] = useState({\n    height: 300,\n    width: 300,\n  })\n\n  useEffect(() => {\n    setDimensions({\n      width: ref?.current?.clientWidth ?? 300,\n      height: ref?.current?.clientHeight ?? 300,\n    })\n  }, deps)\n\n  useResize(() => {\n    setDimensions({\n      width: ref?.current?.clientWidth ?? 300,\n      height: ref?.current?.clientHeight ?? 300,\n    })\n  })\n\n  return dimensions\n}\n"
  },
  {
    "path": "src/Utils/Hooks/useInterval.ts",
    "content": "import { useEffect, useRef } from 'react'\n\nexport const useInterval = (callback: () => void, delay: number) => {\n  const savedCallback = useRef<() => void>()\n\n  useEffect(() => {\n    savedCallback.current = callback\n  }, [callback])\n\n  useEffect(() => {\n    if (delay) {\n      const id = setInterval(\n        () => savedCallback.current && savedCallback.current(),\n        delay,\n      )\n      return () => clearInterval(id)\n    }\n  }, [delay])\n}\n"
  },
  {
    "path": "src/Utils/Hooks/useResize.ts",
    "content": "import { useEffect } from 'react'\n\nexport const useResize = (onResize: () => void) => {\n  useEffect(() => {\n    window.addEventListener('resize', onResize)\n\n    return () => {\n      window.removeEventListener('resize', onResize)\n    }\n  }, [])\n}\n"
  },
  {
    "path": "src/Utils/JSONUtils.ts",
    "content": "export namespace JSONUtils {\n  export const getCircularReplacer = () => {\n    const seen = new WeakSet()\n    return (key: string, value: any) => {\n      if (typeof value === 'object' && value !== null) {\n        if (seen.has(value)) return\n        seen.add(value)\n      }\n      return value\n    }\n  }\n\n  export const stringify = (value: any) =>\n    JSON.stringify(value, getCircularReplacer())\n}\n"
  },
  {
    "path": "src/Utils/MessageFormatter.ts",
    "content": "import { isString, StringUtils } from '@/Utils/StringUtils'\nimport { isNumber } from './Numbers'\n\nconst MAX_CHARACTERS = 512\n\nexport const MessageFormatter = {\n  heartbeat({ msg }: DDPLogContent) {\n    return msg\n  },\n\n  collection({ msg, collection }: DDPLogContent) {\n    const prepMap: { [key: string]: string } = {\n      added: 'to',\n      removed: 'from',\n      changed: 'at',\n    }\n\n    if (msg && msg in prepMap) {\n      return `${msg} ${prepMap[msg]} ${collection}`\n    }\n  },\n\n  connection({ msg, session }: DDPLogContent) {\n    return session || msg\n  },\n\n  subscription({ msg, id, name, subs }: any) {\n    if (msg === 'unsub') {\n      return `${id} stopping`\n    }\n\n    if (msg === 'nosub') {\n      return `${id} stopped`\n    }\n\n    if (msg === 'sub') {\n      return `${name} initializing`\n    }\n\n    if (msg === 'ready') {\n      const idsToNames = subs.map((id: string) => id).filter(Boolean)\n\n      return `[${idsToNames.join(', ')}] ready`\n    }\n\n    return null\n  },\n\n  method({ msg, method, result, error }: DDPLogContent) {\n    if (msg === 'method') {\n      return method\n    }\n\n    if (msg === 'result' && error) {\n      return StringUtils.truncate(\n        `${error.errorType}: ${error.message}`,\n        MAX_CHARACTERS,\n      )\n    }\n\n    if (msg === 'result') {\n      return StringUtils.truncate(JSON.stringify(result), MAX_CHARACTERS)\n    }\n\n    return msg\n  },\n}\n\nconst idFormat = (message: string, id?: string | number | null) => {\n  if (isNumber(id) || isString(id)) {\n    return `[${id}] ${StringUtils.truncate(message, MAX_CHARACTERS)}`\n  }\n\n  return message\n}\n\nexport const generatePreview = (\n  content: string,\n  parsedContent: DDPLogContent,\n  filterType?: FilterType | null,\n) => {\n  if (parsedContent && filterType) {\n    const message = (() => {\n      if (filterType in MessageFormatter) {\n        return MessageFormatter[filterType](parsedContent)\n      }\n\n      return null\n    })()\n\n    if (message) {\n      return idFormat(message, parsedContent.id)\n    }\n  }\n\n  return StringUtils.truncate(content, MAX_CHARACTERS)\n}\n"
  },
  {
    "path": "src/Utils/Numbers.ts",
    "content": "export const isNumber = (value: any) => typeof value === 'number'\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/ArrayNodeRenderer.tsx",
    "content": "import React from 'react'\nimport { ObjectTreeNode } from '@/Utils/ObjectTreerinator/index'\nimport { isArray, isBoolean, isNil, isNumber, isObject, isString } from 'lodash'\nimport { Collapsible } from '@/Utils/ObjectTreerinator/Collapsible'\n\nexport const ArrayNodeRenderer = (child: any, level: number) => {\n  if (isNil(child))\n    return (\n      <span role='null' style={{ marginLeft: '.33rem' }}>\n        null\n      </span>\n    )\n\n  if (isString(child)) return <span role='string'>{`\"${child}\"`}</span>\n\n  if (isNumber(child)) return <span role='number'>{child}</span>\n\n  if (isBoolean(child))\n    return <span role='boolean'>{JSON.stringify(child)}</span>\n\n  if (isArray(child))\n    return (\n      <Collapsible object={child} level={level + 1}>\n        <ol start={0} role='array'>\n          {child.map((item, index) => (\n            <li key={index} role='item'>\n              <span role='index'>{index}:</span>\n              {ArrayNodeRenderer(item, level + 1)}\n            </li>\n          ))}\n        </ol>\n      </Collapsible>\n    )\n\n  if (isObject(child))\n    return <ObjectTreeNode object={child} level={level + 1} />\n\n  return <span role='string'>{`\"${JSON.stringify(child)}\"`}</span>\n}\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/ArrayRenderer.tsx",
    "content": "import React, { FunctionComponent } from 'react'\nimport { Collapsible } from '@/Utils/ObjectTreerinator/Collapsible'\nimport { ArrayNodeRenderer } from '@/Utils/ObjectTreerinator/ArrayNodeRenderer'\n\ninterface Props {\n  property: string\n  child: any[]\n  level: number\n}\n\nexport const ArrayRenderer: FunctionComponent<Props> = ({\n  property,\n  child,\n  level,\n}) => (\n  <li key={property}>\n    <span role='collapsible-property'>{property}</span>\n\n    <Collapsible object={child} level={level + 1}>\n      <ol start={0} role='array'>\n        {child.map((item, index) => (\n          <li key={index} role='item'>\n            <span role='index'>{index}:</span>\n            {ArrayNodeRenderer(item, level + 1)}\n          </li>\n        ))}\n      </ol>\n    </Collapsible>\n  </li>\n)\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/BooleanRenderer.tsx",
    "content": "import React from 'react'\n\nexport const BooleanRenderer = (key: string, child: boolean) => (\n  <li key={key}>\n    <span role='property'>{key}</span>:&nbsp;\n    <span role='boolean'>{JSON.stringify(child)}</span>\n  </li>\n)\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/Collapsible.tsx",
    "content": "import React, { FunctionComponent, useState } from 'react'\nimport { isArray, isEmpty, isObject } from 'lodash'\n\ninterface Props {\n  object: any\n  level?: number\n}\n\nexport const Collapsible: FunctionComponent<Props> = ({\n  children,\n  object,\n  level = 0,\n}) => {\n  const [isCollapsed, setIsCollapsed] = useState(level > 5)\n\n  if (isArray(object)) {\n    const isArrayEmpty = isEmpty(object)\n\n    if (isCollapsed || isArrayEmpty) {\n      return (\n        <span\n          role='expand'\n          onClick={() => !isArrayEmpty && setIsCollapsed(false)}\n        >{`[${object.length}]`}</span>\n      )\n    }\n\n    return (\n      <>\n        {level > 1 && (\n          <span role='collapse' onClick={() => setIsCollapsed(true)}>\n            {'[-]'}\n          </span>\n        )}\n        {children}\n      </>\n    )\n  }\n\n  if (isObject(object)) {\n    const isObjectEmpty = isEmpty(object)\n\n    if (isCollapsed) {\n      return (\n        <span\n          role='expand'\n          onClick={() => !isObjectEmpty && setIsCollapsed(false)}\n        >{`{${Object.keys(object).length}}`}</span>\n      )\n    }\n\n    return (\n      <>\n        {level > 1 && (\n          <span role='collapse' onClick={() => setIsCollapsed(true)}>\n            {'{-}'}\n          </span>\n        )}\n        {children}\n      </>\n    )\n  }\n\n  console.error('Not a valid collapsible value.')\n\n  console.trace(object)\n\n  return null\n}\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/NullRenderer.tsx",
    "content": "import React from 'react'\n\nexport const NullRenderer = (key: string) => (\n  <li key={key}>\n    <span role='property'>{key}</span>:&nbsp;\n    <span role='null'>null</span>\n  </li>\n)\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/NumberRenderer.tsx",
    "content": "import React from 'react'\n\nexport const NumberRenderer = (key: string, child: number) => (\n  <li key={key}>\n    <span role='property'>{key}</span>:&nbsp;<span role='number'>{child}</span>\n  </li>\n)\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/ObjectRenderer.tsx",
    "content": "import React, { FunctionComponent } from 'react'\nimport { ObjectTreeNode } from '@/Utils/ObjectTreerinator/index'\n\ninterface Props {\n  property: string\n  child: object\n  level: number\n}\n\nexport const ObjectRenderer: FunctionComponent<Props> = ({\n  property,\n  child,\n  level,\n}) => (\n  <li key={property}>\n    <span role='collapsible-property'>{property}</span>\n    <ObjectTreeNode object={child} level={level + 1} />\n  </li>\n)\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/StringRenderer.tsx",
    "content": "import React from 'react'\n\nexport const StringRenderer = (key: string, child: string) => (\n  <li key={key}>\n    <span role='property'>{key}</span>:&nbsp;\n    <span role='string'>{`\"${child}\"`}</span>\n  </li>\n)\n"
  },
  {
    "path": "src/Utils/ObjectTreerinator/index.tsx",
    "content": "import {\n  isArray,\n  isBoolean,\n  isNil,\n  isNumber,\n  isObject,\n  isString,\n  toPairs,\n} from 'lodash'\nimport React, { FunctionComponent } from 'react'\n\nimport { Collapsible } from './Collapsible'\nimport { StringRenderer } from '@/Utils/ObjectTreerinator/StringRenderer'\nimport { ArrayRenderer } from '@/Utils/ObjectTreerinator/ArrayRenderer'\nimport { ObjectRenderer } from '@/Utils/ObjectTreerinator/ObjectRenderer'\nimport { BooleanRenderer } from '@/Utils/ObjectTreerinator/BooleanRenderer'\nimport { NumberRenderer } from '@/Utils/ObjectTreerinator/NumberRenderer'\nimport { NullRenderer } from '@/Utils/ObjectTreerinator/NullRenderer'\nimport styled from 'styled-components'\n\nconst TreeWrapper = styled.div`\n  font-family: monospace;\n  font-size: 12px;\n  padding: 1rem;\n\n  span[role='collapsible-property'] {\n    color: #669eff;\n  }\n\n  span[role='property'] {\n    color: #ff6e4a;\n  }\n\n  span[role='index'] {\n    color: #808080;\n  }\n\n  span[role='string'] {\n    color: #c88953;\n  }\n\n  span[role='null'] {\n    color: #c274c2;\n  }\n\n  span[role='number'] {\n    color: #ad99ff;\n  }\n\n  span[role='boolean'] {\n    color: #c274c2;\n  }\n\n  span[role='expand'],\n  span[role='collapse'] {\n    font-family: monospace;\n    color: #808080;\n    margin-left: 0.33rem;\n    cursor: pointer;\n    user-select: none;\n  }\n\n  ul,\n  ol {\n    list-style: none;\n    padding-left: 1rem;\n    margin: 0;\n  }\n\n  & > ul,\n  & > ol {\n    padding: 0;\n  }\n`\n\nexport const ObjectTreeNode: FunctionComponent<{\n  object: { [key: string]: any }\n  level: number\n}> = ({ object, level }) => {\n  if (!(typeof object === 'object' && object?.constructor === Object)) {\n    console.error('Invalid Object')\n\n    console.debug(object)\n  }\n\n  const children = toPairs(object).map(([key, child]) => {\n    if (isString(child)) return StringRenderer(key, child)\n\n    if (isNumber(child)) return NumberRenderer(key, child)\n\n    if (isBoolean(child)) return BooleanRenderer(key, child)\n\n    if (isNil(child)) return NullRenderer(key)\n\n    if (isArray(child))\n      return (\n        <ArrayRenderer key={key} property={key} child={child} level={level} />\n      )\n\n    if (isObject(child))\n      return (\n        <ObjectRenderer key={key} property={key} child={child} level={level} />\n      )\n\n    return StringRenderer(key, JSON.stringify(child))\n  })\n\n  return (\n    <Collapsible object={object} level={level}>\n      <ul role='object'>{children}</ul>\n    </Collapsible>\n  )\n}\n\nexport const ObjectTreerinator: FunctionComponent<{\n  object?: { [key: string]: any }\n}> = ({ object }) => (\n  <TreeWrapper>\n    {object && <ObjectTreeNode object={object} level={1} />}\n  </TreeWrapper>\n)\n"
  },
  {
    "path": "src/Utils/Objects.ts",
    "content": "export const isObject = (value: any) => typeof value === 'object'\n\nexport function omit(object, keys) {\n  const result = {}\n  for (const key of Object.keys(object)) {\n    if (!keys.includes(key)) {\n      result[key] = object[key]\n    }\n  }\n  return result\n}\n\nexport function mapValues(object, fn) {\n  const result = {}\n  for (const key of Object.keys(object)) {\n    result[key] = fn(object[key], key)\n  }\n  return result\n}\n\nexport function flatten(array) {\n  return array.flat()\n}\n\nexport function compact(array) {\n  return array.filter(Boolean)\n}\n\nexport const isNil = value => value === null || value === undefined\n\nexport const isUndefined = value => value === undefined\n"
  },
  {
    "path": "src/Utils/Pagination.ts",
    "content": "export const calculatePagination = (\n  offset: number,\n  length: number,\n  currentPage: number,\n  setSearch: (search: string) => void,\n  setCurrentPage: (page: number) => void,\n): Pagination => {\n  const lastIndex = length - 1\n  const start = (currentPage - 1) * offset\n  const end1 = start + offset\n  const end2 = Math.min(end1, length)\n  const pages = Math.ceil(length / offset)\n  const hasOnePage = pages === 1\n  const hasNextPage = currentPage < pages\n  const hasPreviousPage = currentPage > 1\n\n  return {\n    offset,\n    length,\n    lastIndex,\n    start: Math.max(start, 0),\n    end: end2,\n    pages,\n    hasOnePage,\n    hasNextPage,\n    hasPreviousPage,\n    currentPage,\n    setCurrentPage,\n    pageItems: Math.min(length, end2),\n    setSearch(search: string) {\n      setSearch(search)\n    },\n    next() {\n      if (hasNextPage) {\n        setCurrentPage(currentPage + 1)\n      }\n    },\n    prev() {\n      if (hasPreviousPage) {\n        setCurrentPage(currentPage - 1)\n      }\n    },\n  }\n}\n"
  },
  {
    "path": "src/Utils/StringUtils.ts",
    "content": "import memoize from 'lodash.memoize'\n\nexport const isString = (value: any) => typeof value === 'string'\n\nexport namespace StringUtils {\n  export const classPrefix = 'mde'\n\n  export const truncate = (str: string, max: number = 40) => {\n    return isString(str) && str.length > max\n      ? [...str.slice(0, max), '...']\n      : str\n  }\n\n  /**\n   * Five levels of brightness from 1 to 5.\n   *\n   * @param brightness\n   */\n  export const getRandomColor = (brightness: number) => {\n    if (brightness < 1 || brightness > 5)\n      throw new Error(\n        'Only five brightness levels, from 1 to 5, are acceptable.',\n      )\n\n    const variance = 255 / 5\n\n    const getByte = () =>\n      Math.round(variance * (brightness - 1) + Math.random() * variance)\n\n    const rgb = [0, 0, 0].map(() => getByte()).join(',')\n\n    return `rgb(${rgb})`\n  }\n\n  export const toClipboard = (data: string, mimeType = 'text/plain') => {\n    document.addEventListener('copy', function (event: ClipboardEvent) {\n      event.clipboardData?.setData(mimeType, data)\n      event.preventDefault()\n    })\n    document.execCommand('copy', false)\n  }\n\n  export const getSize = memoize((content: string) => new Blob([content]).size)\n\n  export function getPrefixedClass(className) {\n    return `${classPrefix}-${className}`\n  }\n}\n"
  },
  {
    "path": "src/Utils/index.ts",
    "content": "import { DEVELOPMENT } from '@/Constants'\nimport browser from 'webextension-polyfill'\nimport { isNil } from './Objects'\n\nexport const inDevelopmentOnly = (callback: () => any) => {\n  if (DEVELOPMENT) {\n    console.trace('DEVELOPMENT ONLY')\n    callback()\n  }\n}\n\nexport const checkFirefoxBrowser = async (): Promise<boolean> => {\n  const { name } = (await browser.runtime.getBrowserInfo?.()) || {}\n  return name === 'Firefox'\n}\n\nexport const exists = (value: any) => !isNil(value)\n"
  },
  {
    "path": "src/index.d.ts",
    "content": "declare module '*.gif'\ndeclare module '*.png'\n\ntype MeteorID = string\n\ninterface Window {\n  __meteor_devtools_evolved: boolean\n\n  __meteor_devtools_evolved_receiveMessage(message: Message<any>): void\n}\n\ndeclare namespace Meteor {\n  const connection: any\n  const gitCommitHash: string | undefined | null\n}\n\ntype MessageSource = 'meteor-devtools-evolved'\ntype EventType =\n  | 'ddp-event'\n  | 'minimongo-get-collections'\n  | 'ddp-run-method'\n  | 'console'\n  | 'sync-subscriptions'\n  | 'stats'\n  | 'meteor-data-performance'\n  | 'cache:clear'\n\ninterface Message<T> {\n  eventType: EventType\n  data: T\n}\n\ninterface IMessagePayload<T> extends Message<T> {\n  source: MessageSource\n}\n\ndeclare interface StackTrace {\n  url: string\n  callee: string\n}\n\ninterface DDPError {\n  isClientSafe: boolean\n  error: number\n  reason: string\n  message: string\n  errorType: string\n}\n\ninterface DDPLogContent {\n  msg?: string\n  collection?: string\n  session?: string\n  id?: string\n  method?: string\n  result?: string\n  name?: string\n  error?: DDPError\n  subs?: string[]\n}\n\ninterface DDPLog {\n  id: string\n  content: string\n  parsedContent?: DDPLogContent\n  trace?: StackTrace[]\n  isInbound?: boolean\n  isOutbound?: boolean\n  timestamp?: number\n  timestampPretty?: string\n  timestampLong?: string\n  size?: number\n  sizePretty?: string\n  host?: string\n  filterType?: FilterType | null\n  preview?: string\n}\n\ninterface Bookmark {\n  id?: string\n  timestamp: number\n  log: DDPLog\n}\n\ntype FilterType =\n  | 'heartbeat'\n  | 'subscription'\n  | 'collection'\n  | 'method'\n  | 'connection'\n\ntype FilterTypeMap<T> = { [key in FilterType]: T }\n\ninterface Pagination {\n  readonly offset: number\n  readonly length: number\n  readonly lastIndex: number\n  readonly start: number\n  readonly end: number\n  readonly pages: number\n  readonly currentPage: number\n  readonly hasOnePage: boolean\n  readonly hasNextPage: boolean\n  readonly hasPreviousPage: boolean\n  readonly pageItems: number\n\n  setSearch(search: string): void\n\n  setCurrentPage(page: number): void\n\n  next(): void\n\n  prev(): void\n}\n\ninterface IDocument extends Record<string, any> {\n  _id: string\n}\n\ntype MinimongoCollections = Record<string, IDocumentWrapper[]>\ntype RawCollections = Record<string, IDocument[]>\n\ntype ViewableObject = object | null\n\ntype MessageHandler = (message: Message<any>) => void\n\ninterface IDocumentWrapper {\n  collectionName: string\n  document: IDocument\n  _string: string\n  _size: number\n}\n\ninterface IGitHubRepository {\n  id: number\n  node_id: string\n  name: string\n  full_name: string\n  private: boolean\n  owner: {\n    login: string\n    id: number\n    node_id: string\n    avatar_url: string\n    gravatar_id: string\n    url: string\n    html_url: string\n    followers_url: string\n    following_url: string\n    gists_url: string\n    starred_url: string\n    subscriptions_url: string\n    organizations_url: string\n    repos_url: string\n    events_url: string\n    received_events_url: string\n    type: string\n    site_admin: boolean\n  }\n  html_url: string\n  description: string\n  fork: boolean\n  url: string\n  forks_url: string\n  keys_url: string\n  collaborators_url: string\n  teams_url: string\n  hooks_url: string\n  issue_events_url: string\n  events_url: string\n  assignees_url: string\n  branches_url: string\n  tags_url: string\n  blobs_url: string\n  git_tags_url: string\n  git_refs_url: string\n  trees_url: string\n  statuses_url: string\n  languages_url: string\n  stargazers_url: string\n  contributors_url: string\n  subscribers_url: string\n  subscription_url: string\n  commits_url: string\n  git_commits_url: string\n  comments_url: string\n  issue_comment_url: string\n  contents_url: string\n  compare_url: string\n  merges_url: string\n  archive_url: string\n  downloads_url: string\n  issues_url: string\n  pulls_url: string\n  milestones_url: string\n  notifications_url: string\n  labels_url: string\n  releases_url: string\n  deployments_url: string\n  created_at: string\n  updated_at: string\n  pushed_at: string\n  git_url: string\n  ssh_url: string\n  clone_url: string\n  svn_url: string\n  homepage: string\n  size: number\n  stargazers_count: number\n  watchers_count: number\n  language: string\n  has_issues: boolean\n  has_projects: boolean\n  has_downloads: boolean\n  has_wiki: boolean\n  has_pages: boolean\n  forks_count: number\n  mirror_url: string | null\n  archived: boolean\n  disabled: boolean\n  open_issues_count: number\n  license: {\n    key: string\n    name: string\n    spdx_id: string\n    url: string\n    node_id: string\n  }\n  forks: number\n  open_issues: number\n  watchers: number\n  default_branch: string\n  temp_clone_token: string | null\n  network_count: number\n  subscribers_count: number\n}\n\ninterface ISettings {\n  repositoryData: IGitHubRepository | null\n  activeFilterBlacklist: string[]\n  activeFilters: FilterTypeMap<boolean>\n}\n\ntype ConsoleType = 'log' | 'info' | 'warn' | 'error'\n\ninterface IMeteorSubscription {\n  id: string\n  name: string\n  params: any[]\n  inactive: boolean\n  ready: boolean\n}\n\ninterface ICollectionMetadata {\n  [key: string]: {\n    collectionSize: number\n    collectionSizePretty: string\n  }\n}\n\ntype CallData = {\n  collectionName: string\n  key: string\n  args: string\n  runtime: number\n}\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "module.exports = {\n  darkMode: 'class',\n  content: ['./src/**/*.{js,jsx,ts,tsx}'],\n  theme: {\n    extend: {},\n  },\n  plugins: [require('daisyui')],\n  daisyui: {\n    styled: true,\n    themes: true,\n    base: true,\n    utils: true,\n    logs: false,\n    rtl: false,\n    prefix: '',\n    darkTheme: 'light',\n  },\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"strict\": false,\n    \"noImplicitAny\": false,\n    \"target\": \"ES6\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"useDefineForClassFields\": true,\n    \"sourceMap\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react\",\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@/*\": [\"src/*\"]\n    }\n  },\n  \"include\": [\"src/*.d.ts\", \"src/**/*.ts\", \"src/**/*.tsx\"]\n}\n"
  },
  {
    "path": "webpack/base.js",
    "content": "const path = require('path')\nconst { merge } = require('webpack-merge')\nconst { DefinePlugin } = require('webpack')\nconst { getTypeScriptAliases } = require('./utils')\n\nconst src = path.join(__dirname, '../src/')\n\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin')\nconst CopyPlugin = require('copy-webpack-plugin')\n\nconst aliases = getTypeScriptAliases()\n\nconst manifestVersion = {\n  chrome: 3,\n  firefox: 2,\n}\n\nmodule.exports = (browser = 'chrome', override) => {\n  const extDir = path.join(__dirname, `../extension`)\n  const distPath = `${extDir}/${browser}/dist/`\n\n  return merge(\n    {\n      entry: {\n        bundle: path.resolve(src, 'App.tsx'),\n        inject: path.resolve(src, 'Browser', 'Inject.ts'),\n        background: path.resolve(src, 'Browser', 'Background.ts'),\n        content: path.resolve(src, 'Browser', 'Content.ts'),\n        devtools: path.resolve(src, 'Browser', 'DevTools.ts'),\n      },\n\n      output: {\n        chunkFilename: '[name].js',\n        path: distPath,\n        publicPath: '/dist/',\n      },\n\n      plugins: [\n        new CleanWebpackPlugin(),\n\n        new DefinePlugin({\n          'process.env.MODE': JSON.stringify(override.mode),\n        }),\n        new CopyPlugin({\n          patterns: [\n            {\n              from: extDir,\n              to: `${extDir}/${browser}`,\n              globOptions: {\n                dot: true,\n                gitignore: true,\n                ignore: [\n                  '**/manifest-v2.json',\n                  '**/manifest-v3.json',\n                  '**/firefox',\n                  '**/chrome',\n                ],\n              },\n            },\n            {\n              from: `${extDir}/manifest-v${manifestVersion[browser]}.json`,\n              to: `${extDir}/${browser}/manifest.json`,\n            },\n          ],\n        }),\n      ],\n\n      module: {\n        rules: [\n          {\n            parser: {\n              amd: false,\n            },\n          },\n          {\n            test: /\\.js/,\n            use: 'babel-loader',\n            include: src,\n          },\n          { test: /\\.tsx?$/, use: 'ts-loader' },\n          {\n            test: /\\.css$/,\n            use: ['style-loader', 'css-loader', 'postcss-loader'],\n          },\n          {\n            test: /\\.scss$/,\n            use: ['style-loader', 'css-loader', 'sass-loader'],\n          },\n          {\n            test: /\\.(gif|png|jpg)$/,\n            type: 'asset/resource',\n            generator: {\n              filename: 'assets/[name][ext]',\n            },\n          },\n        ],\n      },\n\n      resolve: {\n        alias: aliases,\n\n        extensions: [\n          '.css',\n          '.eot',\n          '.js',\n          '.json',\n          '.jsx',\n          '.mjs',\n          '.sass',\n          '.scss',\n          '.ttf',\n          '.gif',\n          '.ts',\n          '.tsx',\n          '.woff',\n          '.jpg',\n          '.png',\n        ],\n      },\n\n      performance: {\n        hints: false,\n      },\n    },\n    override,\n  )\n}\n"
  },
  {
    "path": "webpack/chrome.dev.js",
    "content": "const base = require('./base')\n\nmodule.exports = base('chrome', {\n  watch: true,\n  mode: 'development',\n  devtool: 'inline-source-map',\n  stats: {\n    modules: false,\n  },\n})\n"
  },
  {
    "path": "webpack/chrome.prod.js",
    "content": "const base = require('./base')\n\nconst TerserPlugin = require('terser-webpack-plugin')\n\nmodule.exports = base('chrome', {\n  mode: 'production',\n\n  optimization: {\n    minimize: true,\n    minimizer: [new TerserPlugin()],\n  },\n})\n"
  },
  {
    "path": "webpack/firefox.dev.js",
    "content": "const base = require('./base')\n\nmodule.exports = base('firefox', {\n  watch: true,\n  mode: 'development',\n  devtool: 'inline-source-map',\n  stats: {\n    modules: false,\n  },\n})\n"
  },
  {
    "path": "webpack/firefox.prod.js",
    "content": "const base = require('./base')\n\nconst TerserPlugin = require('terser-webpack-plugin')\n\nmodule.exports = base('firefox', {\n  mode: 'production',\n\n  optimization: {\n    minimize: true,\n    minimizer: [new TerserPlugin()],\n  },\n})\n"
  },
  {
    "path": "webpack/utils.js",
    "content": "const { resolve } = require('path')\nconst { toPairs } = require('lodash')\n\nconst getTypeScriptAliases = () => {\n  const { paths } = require('../tsconfig').compilerOptions\n\n  console.log(toPairs(paths))\n\n  return toPairs(paths).reduce(\n    (acc, [key, item]) => ({\n      ...acc,\n      [key.replace('/*', '')]: resolve(\n        __dirname,\n        '..',\n        item[0].replace('/*', '').replace('*', ''),\n      ),\n    }),\n    {},\n  )\n}\n\nmodule.exports = { getTypeScriptAliases }\n"
  }
]