Skip to content
This repository was archived by the owner on May 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d170676
feat(ui): integrate devtools plugin and add dark mode support
JerryWu1234 May 22, 2025
ab9b2d4
Add light and dark mode support using Tailwind CSS
openhands-agent May 23, 2025
bc6f85f
Fix ThemeToggle component and update vite.config.mts for server confi…
openhands-agent May 23, 2025
b343c14
refactor(ui): remove unused CSS variables and streamline theme config…
JerryWu1234 May 24, 2025
89c6302
refactor(vite): remove dev server host and port config
JerryWu1234 May 24, 2025
2022ec4
refactor(vite): remove dev server host and port config
JerryWu1234 May 24, 2025
bb24361
Merge pull request #3 from JerryWu1234/textDarkAndLight
JerryWu1234 May 24, 2025
2353764
feat(theme): implement theme switching with persistence
JerryWu1234 May 28, 2025
3a81fff
style(ui): standardize code formatting and fix linting issues
JerryWu1234 May 29, 2025
f11cc88
refactor(theme): simplify theme handling and remove unused code
JerryWu1234 May 29, 2025
6943d10
fix(devtools): remove unused ThemeScript import and fix panel visibil…
JerryWu1234 Jun 3, 2025
177a8be
style: enforce single quotes in codebase and update prettier config
JerryWu1234 Jun 3, 2025
52356f6
refactor(theme): consolidate theme handling and cleanup
JerryWu1234 Jun 3, 2025
437278b
feat(ThemeToggle): add auto theme option and improve theme handling
JerryWu1234 Jun 4, 2025
6db10ec
formate
JerryWu1234 Jun 5, 2025
3bd77e0
feat(ui): add ThemeToggle component with persistent theme selection
JerryWu1234 Jun 5, 2025
02ddcd0
fix situation which didn't set default value
JerryWu1234 Jun 5, 2025
3699f57
chore: update @qwik.dev/core and @qwik.dev/router to beta versions, u…
JerryWu1234 Jun 6, 2025
9e4be8f
refactor(ThemeToggle): remove console logs and improve type annotatio…
JerryWu1234 Jun 6, 2025
ec2f527
fix
JerryWu1234 Jun 6, 2025
11077da
FIX: add animation when icon switching
JerryWu1234 Jun 13, 2025
5af8192
format
JerryWu1234 Jun 16, 2025
7c36ceb
optimize
JerryWu1234 Jun 16, 2025
38e1d5f
Merge branch 'QwikDev:main' into main
JerryWu1234 Jun 17, 2025
5d0fbf9
Merge branch 'QwikDev:main' into main
JerryWu1234 Aug 13, 2025
5024bbc
Merge branch 'QwikDev:main' into main
JerryWu1234 Aug 28, 2025
dfb0860
Merge branch 'QwikDev:main' into main
JerryWu1234 Sep 19, 2025
2cc2ab4
Merge branch 'QwikDev:main' into main
JerryWu1234 Sep 23, 2025
194a409
Merge branch 'QwikDev:main' into main
JerryWu1234 Sep 24, 2025
f91dc80
Merge branch 'QwikDev:main' into main
JerryWu1234 Sep 26, 2025
28577a1
refactor: enhance Tree component styling and functionality; update Re…
JerryWu1234 Sep 26, 2025
69fa275
feat: add dependency preloading and enhance package management in Qwi…
JerryWu1234 Oct 9, 2025
dfce856
Merge branch 'QwikDev:main' into main
JerryWu1234 Oct 9, 2025
ea6f279
chore: update package configurations and CI workflow
JerryWu1234 Oct 9, 2025
8cc9651
feat: upgrade Tailwind CSS and refactor UI components
JerryWu1234 Oct 9, 2025
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
1 change: 1 addition & 0 deletions packages/kit/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface ServerFunctions {
getComponents: () => Promise<Component[]>;
getRoutes: () => any;
getQwikPackages: () => Promise<[string, string][]>;
getAllDependencies: () => Promise<any[]>;
installPackage: (
packageName: string,
isDev?: boolean,
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { createServerRpc, setViteServerContext, VIRTUAL_QWIK_DEVTOOLS_KEY, INNER
import VueInspector from 'vite-plugin-inspect'
import useCollectHooksSource from './utils/useCollectHooks'
import { parseQwikCode } from './parse/parse';
import { startPreloading } from './npm/index';


export function qwikDevtools(): Plugin[] {
let _config: ResolvedConfig;
const qwikData = new Map<string, any>();
let preloadStarted = false;
const qwikDevtoolsPlugin: Plugin = {
name: 'vite-plugin-qwik-devtools',
apply: 'serve',
Expand Down Expand Up @@ -39,6 +41,14 @@ export function qwikDevtools(): Plugin[] {
},
configResolved(viteConfig) {
_config = viteConfig;

// Start preloading as early as possible, right after config is resolved
if (!preloadStarted) {
preloadStarted = true;
startPreloading({ config: _config }).catch((err) => {
console.error('[Qwik DevTools] Failed to start preloading:', err);
});
}
},
transform: {
order: 'pre',
Expand Down Expand Up @@ -88,6 +98,8 @@ export function qwikDevtools(): Plugin[] {
const rpcFunctions = getServerFunctions({ server, config: _config, qwikData });

createServerRpc(rpcFunctions);

// Preloading should have already started in configResolved
},
}
return [
Expand Down
236 changes: 236 additions & 0 deletions packages/plugin/src/npm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ import { NpmInfo } from '@devtools/kit';
import { execSync } from 'child_process';
import path from 'path';

// In-memory cache for npm package information
interface CacheEntry {
data: any;
timestamp: number;
}

const packageCache = new Map<string, CacheEntry>();
const CACHE_TTL = 1000 * 60 * 30; // 30 minutes cache TTL

// Preloaded dependencies cache - loaded at server startup
let preloadedDependencies: any[] | null = null;
let isPreloading = false;
let preloadPromise: Promise<any[]> | null = null;

function getCachedPackage(name: string): any | null {
const cached = packageCache.get(name);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
packageCache.delete(name);
return null;
}

function setCachedPackage(name: string, data: any): void {
packageCache.set(name, {
data,
timestamp: Date.now(),
});
}

export async function detectPackageManager(
projectRoot: string,
): Promise<'npm' | 'pnpm' | 'yarn'> {
Expand Down Expand Up @@ -38,6 +68,194 @@ export async function detectPackageManager(
}
}

// Preload dependencies function - moved to module scope
const preloadDependencies = async (config: any): Promise<any[]> => {
if (preloadedDependencies) {
console.log('[Qwik DevTools] Dependencies already preloaded');
return preloadedDependencies;
}

if (isPreloading && preloadPromise) {
console.log('[Qwik DevTools] Preloading already in progress...');
return preloadPromise;
}

isPreloading = true;
console.log('[Qwik DevTools] Starting to preload dependencies...');

preloadPromise = (async () => {
const pathToPackageJson = config.configFileDependencies.find(
(file: string) => file.endsWith('package.json'),
);

if (!pathToPackageJson) {
preloadedDependencies = [];
isPreloading = false;
console.log('[Qwik DevTools] No package.json found');
return [];
}

try {
const pkgJson = await fsp.readFile(pathToPackageJson, 'utf-8');
const pkg = JSON.parse(pkgJson);

const allDeps = {
...pkg.dependencies || {},
...pkg.devDependencies || {},
...pkg.peerDependencies || {},
};

const dependencies = Object.entries<string>(allDeps);

// Check cache first
const cachedPackages: any[] = [];
const uncachedDependencies: [string, string][] = [];

for (const [name, version] of dependencies) {
const cached = getCachedPackage(name);
if (cached) {
cachedPackages.push({ ...cached, version });
} else {
uncachedDependencies.push([name, version]);
}
}

if (uncachedDependencies.length === 0) {
preloadedDependencies = cachedPackages;
isPreloading = false;
return cachedPackages;
}

// Load all dependencies - use larger batch for initial preload
const batchSize = 100;
const batches = [];
for (let i = 0; i < uncachedDependencies.length; i += batchSize) {
batches.push(uncachedDependencies.slice(i, i + batchSize));
}

const fetchedPackages: any[] = [];

console.log(`[Qwik DevTools] Fetching ${uncachedDependencies.length} packages in parallel...`);

const allBatchPromises = batches.map(async (batch) => {
const batchPromises = batch.map(async ([name, version]) => {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // Longer timeout for initial load

const response = await fetch(`https://registry.npmjs.org/${name}`, {
headers: {
'Accept': 'application/json',
},
signal: controller.signal,
});

clearTimeout(timeoutId);

if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

const packageData = await response.json();

const latestVersion = packageData['dist-tags']?.latest || version;
const versionData = packageData.versions?.[latestVersion] || packageData.versions?.[version];

let repositoryUrl = versionData?.repository?.url || packageData.repository?.url;
if (repositoryUrl) {
repositoryUrl = repositoryUrl
.replace(/^git\+/, '')
.replace(/^ssh:\/\/git@/, 'https://')
.replace(/\.git$/, '');
}

let iconUrl = null;

if (packageData.logo) {
iconUrl = packageData.logo;
} else if (name.startsWith('@')) {
const scope = name.split('/')[0].substring(1);
iconUrl = `https://avatars.githubusercontent.com/${scope}?size=64`;
} else if (repositoryUrl?.includes('github.com')) {
const repoMatch = repositoryUrl.match(/github\.com\/([^\/]+)/);
if (repoMatch) {
iconUrl = `https://avatars.githubusercontent.com/${repoMatch[1]}?size=64`;
}
}

const packageInfo = {
name,
version,
description: versionData?.description || packageData.description || 'No description available',
author: versionData?.author || packageData.author,
homepage: versionData?.homepage || packageData.homepage,
repository: repositoryUrl,
npmUrl: `https://www.npmjs.com/package/${name}`,
iconUrl,
};

setCachedPackage(name, packageInfo);
return packageInfo;
} catch (error) {
const basicInfo = {
name,
version,
description: 'No description available',
npmUrl: `https://www.npmjs.com/package/${name}`,
iconUrl: null,
};

setCachedPackage(name, basicInfo);
return basicInfo;
}
});

const batchResults = await Promise.allSettled(batchPromises);
return batchResults
.filter((result): result is PromiseFulfilledResult<any> => result.status === 'fulfilled')
.map(result => result.value);
});

const allBatchResults = await Promise.all(allBatchPromises);
for (const batchResult of allBatchResults) {
fetchedPackages.push(...batchResult);
}

const allPackages = [...cachedPackages, ...fetchedPackages];
preloadedDependencies = allPackages;
isPreloading = false;

console.log(`[Qwik DevTools] ✓ Successfully preloaded ${allPackages.length} dependencies`);

return allPackages;
} catch (error) {
console.error('[Qwik DevTools] ✗ Failed to preload dependencies:', error);
preloadedDependencies = [];
isPreloading = false;
return [];
}
})();

return preloadPromise;
};

// Export function to start preloading from plugin initialization
export async function startPreloading({ config }: { config: any }) {
const startTime = Date.now();
console.log('[Qwik DevTools] 🚀 Initiating dependency preload (background)...');

// Start preloading in background, don't wait for it
preloadDependencies(config).then(() => {
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
console.log(`[Qwik DevTools] ⚡ Preload completed in ${duration}s`);
}).catch((err) => {
console.error('[Qwik DevTools] ✗ Preload failed:', err);
});

// Return immediately, don't block
return Promise.resolve();
}

export function getNpmFunctions({ config }: ServerContext) {
return {
async getQwikPackages(): Promise<NpmInfo> {
Expand All @@ -57,6 +275,24 @@ export function getNpmFunctions({ config }: ServerContext) {
}
},

async getAllDependencies(): Promise<any[]> {
// Return preloaded data immediately if available
if (preloadedDependencies) {
console.log('[Qwik DevTools] Returning preloaded dependencies');
return preloadedDependencies;
}

// If preloading is in progress, wait for it
if (isPreloading && preloadPromise) {
console.log('[Qwik DevTools] Waiting for preload to complete...');
return preloadPromise;
}

// If preloading hasn't started (shouldn't happen), start it now
console.log('[Qwik DevTools] Warning: Preload not started, starting now...');
return preloadDependencies(config);
},

async installPackage(
packageName: string,
isDev = true,
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"devDependencies": {
"@devtools/kit": "workspace:*",
"@qwikest/icons": "^0.0.13",
"@tailwindcss/postcss": "^4.0.0",
"@tailwindcss/vite": "^4.0.0",
"@types/eslint": "8.56.10",
"@types/node": "20.14.11",
"@types/react": "^18.2.28",
Expand All @@ -61,7 +63,7 @@
"react-dom": "18.2.0",
"shiki": "^3.8.1",
"superjson": "^2.2.2",
"tailwindcss": "^3.4.6",
"tailwindcss": "^4.0.0",
"typescript": "5.4.5",
"vite": "7.1.3",
"vite-hot-client": "2.0.4",
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': { preflight: false },
},
};
2 changes: 1 addition & 1 deletion packages/ui/src/components/Tab/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const Tab = component$<TabProps>(({ state, id, title }) => {
class={{
'flex h-10 w-10 items-center justify-center rounded-lg p-2.5 transition-all duration-200':
true,
'bg-foreground/5 hover:bg-foreground/10 text-muted-foreground hover:bg-primary-hover hover:text-foreground':
'bg-transparent hover:bg-foreground/5 text-muted-foreground hover:text-foreground':
state.activeTab !== id,
'shadow-accent/35 bg-accent text-white shadow-lg':
state.activeTab === id,
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/components/TabContent/TabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export const TabContent = component$(() => {
<Slot name="title" />
</div>

<Slot name="content" />
<div class="flex-1 overflow-y-auto pb-6">
<Slot name="content" />
</div>
</div>
);
});
6 changes: 2 additions & 4 deletions packages/ui/src/components/Tree/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,12 @@ const TreeNodeComponent = component$(
style={{ paddingLeft: `${props.level * props.gap}px` }}
class={`flex w-full cursor-pointer items-center p-1 ${
isActive
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-700/40 font-semibold'
? 'border border-emerald-300 bg-emerald-100 font-semibold text-emerald-700 dark:border-emerald-700/40 dark:bg-emerald-900/30 dark:text-emerald-300'
: ''
}`}
onClick$={handleNodeClick}
>
<div
class={`inline-flex items-center rounded-md px-2 py-1`}
>
<div class={`inline-flex items-center rounded-md px-2 py-1`}>
{hasChildren ? (
<HiChevronUpMini
class={`mr-2 h-4 w-4 flex-shrink-0 text-muted-foreground transition-transform duration-200 ${
Expand Down
Loading
Loading