Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## 📝 Type of Change

Select the type of change that best describes this PR:

- [ ] 🚀 **Feature** (non-breaking change which adds new functionality)
- [ ] 🐛 **Bug Fix** (non-breaking change which fixes an issue)
- [ ] ⚡ **Optimization / Refactoring** (code structure or performance improvement)
- [ ] 📖 **Documentation** (README, CHANGELOG, inline JSDoc updates)
- [ ] 📦 **Release** (version bump and release preparation)
- [ ] ⚙️ **CI/CD & Tooling** (GitHub Actions, build config, scripts)

---

## 📌 Description & Motivation

Provide a concise summary of the changes introduced in this PR and the problem/user story it resolves.

---

## ✨ Key Changes & Highlights

- Feature/fix item 1
- Feature/fix item 2
- Feature/fix item 3

---

## 📸 Screenshots / Demos (If Applicable)

| Feature | Preview |
| :------------ | :-----------------------------------------: |
| **Feature 1** | <!-- Embed screenshot URL or image HTML --> |
| **Feature 2** | <!-- Embed screenshot URL or image HTML --> |

---

## 🧪 Quality & Verification Checklist

Before submitting this PR, please check and verify the following:

- [ ] My code follows the project's code style and guidelines.
- [ ] I have executed `pnpm check-types` and verified **0 TypeScript errors**.
- [ ] I have executed `pnpm test` and all unit tests pass **100%**.
- [ ] I have executed `pnpm --filter mobile-devtools build` and the package builds cleanly.
- [ ] I have executed `pnpm --filter web lint` and verified **0 ESLint warnings/errors**.
- [ ] I have updated relevant documentation ([`README.md`](./README.md), [`CHANGELOG.md`](./packages/mobile-devtools/CHANGELOG.md), and inline TSDoc comments) if applicable.
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI & Quality Checks

on:
push:
branches: [master, main]
pull_request:
branches: [master, main]

jobs:
ci:
name: Typecheck, Build & Test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.15.0

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

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build mobile-devtools package
run: pnpm --filter mobile-devtools build

- name: Check TypeScript Types
run: pnpm check-types

- name: Run Unit Tests
run: pnpm test

- name: Run Web Application Linting
run: pnpm --filter web lint
83 changes: 62 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[![Turborepo](https://img.shields.io/badge/Monorepo-Turborepo-ef4444.svg)](https://turbo.build/)
[![React](https://img.shields.io/badge/Adapter-React_18%2F19-61dafb.svg)](https://react.dev/)
[![Vue](https://img.shields.io/badge/Adapter-Vue_3-42b883.svg)](https://vuejs.org/)
[![Svelte](https://img.shields.io/badge/Adapter-Svelte_4%2F5-ff3e00.svg)](https://svelte.dev/)
[![Vanilla JS](https://img.shields.io/badge/Adapter-Vanilla_JS-f7df1e.svg)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)

---
Expand All @@ -22,6 +23,7 @@
- [🚀 Framework Quickstart](#-framework-quickstart)
- [⚛️ React Integration](#%EF%B8%8F-react-integration)
- [💚 Vue 3 Integration](#-vue-3-integration)
- [🧡 Svelte 4/5 Integration](#-svelte-45-integration)
- [🍦 Vanilla JS / Legacy Apps](#-vanilla-js--legacy-apps)
- [⚙️ Full Configuration & Props Reference](#%EF%B8%8F-full-configuration--props-reference)
- [🎨 Theme Engine & Customization](#-theme-engine--customization)
Expand All @@ -34,13 +36,13 @@
## 📸 Showcase

<p align="center">
<img src="./docs/assets/console.png" width="31%" alt="Console Tab" />
<img src="./docs/assets/elements.png" width="31%" alt="Elements Tab" />
<img src="./docs/assets/network.png" width="31%" alt="Network Tab" />
<img src="https://raw.githubusercontent.com/dewasemadi/mobile-devtools/master/docs/assets/console.png" width="31%" alt="Console Tab" />
<img src="https://raw.githubusercontent.com/dewasemadi/mobile-devtools/master/docs/assets/elements.png" width="31%" alt="Elements Tab" />
<img src="https://raw.githubusercontent.com/dewasemadi/mobile-devtools/master/docs/assets/network.png" width="31%" alt="Network Tab" />
</p>
<p align="center">
<img src="./docs/assets/storage.png" width="31%" alt="Storage Tab" />
<img src="./docs/assets/system.png" width="31%" alt="System Tab" />
<img src="https://raw.githubusercontent.com/dewasemadi/mobile-devtools/master/docs/assets/storage.png" width="31%" alt="Storage Tab" />
<img src="https://raw.githubusercontent.com/dewasemadi/mobile-devtools/master/docs/assets/system.png" width="31%" alt="System Tab" />
</p>

---
Expand Down Expand Up @@ -74,28 +76,28 @@ Debugging mobile web applications or QA staging builds on physical smartphones,
- 🎨 **Granular UI Style Overrides (`styles`)**: Fine-grained inline CSS style overrides for badge, drawer, overlay, and handle (`styles={{ badge: {}, drawer: {}, overlay: {} }}`).
- 🎨 **Dynamic Theme Engine**: Built-in Light Mode and Dark Mode with auto-contrast luminance detection, accent color swatches, and custom background palettes.
- 🧪 **Comprehensive Test Suite**: Tested with **65 Unit Tests (100% Passed)** + **21 Playwright E2E Tests (100% Passed)** across Desktop Chrome, Mobile Chrome, and Mobile Safari.
- 🧩 **Framework Agnostic**: Native support for **React 18/19**, **Vue 3**, and **Vanilla JS**.
- 🧩 **Framework Agnostic**: Native support for **React 18/19**, **Vue 3**, **Svelte 4/5**, and **Vanilla JS**.

---

## 🏗️ Technical Architecture

```text
┌────────────────────────────────────────────────────────────────────────┐
│ mobile-devtools │
└───────────────────────────────────┬────────────────────────────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
Vanilla JS React Vue 3
(mobile-devtools) (mobile-devtools/react) (mobile-devtools/vue)
│ │ │
└────────────────────────┼────────────────────────┘
│
┌─────────────▼─────────────┐
│ Native Shadow DOM Host │
│ <mobile-devtools-root> │
└─────────────┬─────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ mobile-devtools │
└────────────────────────────────────────┬────────────────────────────────────────┘
│
┌───────────────────┬────────────┴───────────┬───────────────────┐
▼ ▼ ▼ ▼
Vanilla JS React Vue 3 Svelte
(mobile-devtools) (mobile-devtools/react) (mobile-devtools/vue) (mobile-devtools/svelte)
│ │ │ │
└───────────────────┴────────────┬───────────┴───────────────────┘
│
┌─────────────▼─────────────┐
│ Native Shadow DOM Host │
│ <mobile-devtools-root> │
└─────────────┬─────────────┘
│
┌───────────────────────┴───────────────────────┐
│ │
Expand Down Expand Up @@ -197,6 +199,45 @@ Import from `mobile-devtools/vue`:

---

### 🔥 Svelte Integration

Import from `mobile-devtools/svelte`:

```svelte
<script>
import { mobileDevTools } from 'mobile-devtools/svelte';
</script>

<div use:mobileDevTools={{
title: 'My App Debugger',
position: 'bottom-right',
shakeToToggle: true,
theme: { mode: 'dark' }
}}>
<YourAppLayout />
</div>
```

### 🧡 Svelte 4/5 Integration

Import from `mobile-devtools/svelte`:

```svelte
<script>
import { useMobileDevTools } from 'mobile-devtools/svelte';
</script>

<div use:useMobileDevTools={{
title: 'My App Debugger',
position: 'bottom-right',
theme: { mode: 'dark', accentColor: '#0070f3' }
}}>
<YourAppLayout />
</div>
```

---

### 🍦 Vanilla JS / Legacy Apps

Import directly from `mobile-devtools`:
Expand Down
105 changes: 103 additions & 2 deletions apps/web/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,105 @@
import { config as reactJsConfig } from "@mobile-devtools/eslint-config/react-internal";
import boundaries from 'eslint-plugin-boundaries';
import { config as reactJsConfig } from '@mobile-devtools/eslint-config/react-internal';

/** @type {import("eslint").Linter.Config[]} */
export default reactJsConfig;
export default [
...reactJsConfig,
{
plugins: {
boundaries,
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
},
'boundaries/elements': [
{
type: 'app',
pattern: ['src/app/**/*'],
},
{
type: 'views',
pattern: ['src/views/**/*'],
},
{
type: 'widgets',
pattern: ['src/widgets/**/*'],
},
{
type: 'features',
pattern: ['src/features/**/*'],
},
{
type: 'entities',
pattern: ['src/entities/**/*'],
},
{
type: 'shared',
pattern: ['src/shared/**/*'],
},
],
},
rules: {
'boundaries/dependencies': [
'error',
{
default: 'disallow',
message: '{{file.type}} is not allowed to import {{dependency.type}}',
policies: [
{
from: { element: { type: 'app' } },
allow: [
{ to: { element: { type: 'views' } } },
{ to: { element: { type: 'widgets' } } },
{ to: { element: { type: 'features' } } },
{ to: { element: { type: 'entities' } } },
{ to: { element: { type: 'shared' } } },
{ to: { element: { type: 'app' } } },
],
},
{
from: { element: { type: 'views' } },
allow: [
{ to: { element: { type: 'widgets' } } },
{ to: { element: { type: 'features' } } },
{ to: { element: { type: 'entities' } } },
{ to: { element: { type: 'shared' } } },
{ to: { element: { type: 'views' } } },
],
},
{
from: { element: { type: 'widgets' } },
allow: [
{ to: { element: { type: 'features' } } },
{ to: { element: { type: 'entities' } } },
{ to: { element: { type: 'shared' } } },
{ to: { element: { type: 'widgets' } } },
],
},
{
from: { element: { type: 'features' } },
allow: [
{ to: { element: { type: 'entities' } } },
{ to: { element: { type: 'shared' } } },
{ to: { element: { type: 'features' } } },
],
},
{
from: { element: { type: 'entities' } },
allow: [
{ to: { element: { type: 'shared' } } },
{ to: { element: { type: 'entities' } } },
],
},
{
from: { element: { type: 'shared' } },
allow: [{ to: { element: { type: 'shared' } } }],
},
],
},
],
},
},
];
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"eslint": "^9.39.1",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-boundaries": "^7.2.0",
"postcss": "^8.5.26",
"rimraf": "^6.1.3",
"tailwindcss": "^4.3.3",
Expand Down
7 changes: 5 additions & 2 deletions apps/web/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* eslint-disable */
import { defineConfig, devices } from '@playwright/test';

const baseURL = 'http://localhost:3000';

export default defineConfig({
testDir: './e2e',
fullyParallel: true,
Expand All @@ -8,7 +11,7 @@ export default defineConfig({
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
baseURL: baseURL,
trace: 'on-first-retry',
headless: !!process.env.CI,
},
Expand All @@ -28,7 +31,7 @@ export default defineConfig({
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
url: baseURL,
reuseExistingServer: !process.env.CI,
},
});
38 changes: 38 additions & 0 deletions apps/web/plugins/sse-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
import { type Plugin, type ViteDevServer } from 'vite';

/**
* Custom Vite plugin that provides a mock Server-Sent Events (SSE) stream at `/api/sse` for live testing.
*/
export function ssePlugin(): Plugin {
return {
name: 'sse-plugin',
configureServer(server: ViteDevServer) {
server.middlewares.use('/api/sse', (req: IncomingMessage, res: ServerResponse) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'Access-Control-Allow-Origin': '*',
});

res.write(
`data: ${JSON.stringify({ event: 'connected', message: 'SSE Stream Active', timestamp: new Date().toISOString() })}\n\n`
);

const timer = setInterval(() => {
const payload = JSON.stringify({
event: 'ticker',
timestamp: new Date().toISOString(),
status: 'online',
});
res.write(`data: ${payload}\n\n`);
}, 3000);

req.on('close', () => {
clearInterval(timer);
});
});
},
};
}
Loading
Loading