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
34 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
dfce856
Merge branch 'QwikDev:main' into main
JerryWu1234 Oct 9, 2025
0db882a
Merge branch 'QwikDev:main' into main
JerryWu1234 Oct 16, 2025
1f9e156
chore: update package versions and add CodeBreack feature
JerryWu1234 Oct 21, 2025
971266e
chore: update changeset
gioboa Oct 21, 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
5 changes: 5 additions & 0 deletions .changeset/proud-ears-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/devtools': patch
---

chore: update package versions and add CodeBreack feature
4 changes: 2 additions & 2 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"README.md"
],
"peerDependencies": {
"@qwik.dev/core": "2.0.0-beta.9",
"@qwik.dev/router": "2.0.0-beta.9",
"@qwik.dev/core": "2.0.0-beta.11",
"@qwik.dev/router": "2.0.0-beta.11",
"vite": "7.1.3"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"superjson": "^2.2.2"
},
"peerDependencies": {
"vite": "^6.2.6"
"vite": "^7.1.3"
},
"devDependencies": {
"@types/eslint": "8.56.10",
Expand All @@ -31,7 +31,7 @@
"@typescript-eslint/parser": "7.16.1",
"cpy-cli": "^5.0.0",
"eslint": "8.57.0",
"eslint-plugin-qwik": "2.0.0-beta.9",
"eslint-plugin-qwik": "2.0.0-beta.11",
"np": "^8.0.4",
"prettier": "3.3.3",
"typescript": "5.4.5",
Expand Down
6 changes: 3 additions & 3 deletions packages/playgrounds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
"devDependencies": {
"@devtools/plugin": "workspace:*",
"@devtools/ui": "workspace:*",
"@qwik.dev/core": "2.0.0-beta.9",
"@qwik.dev/router": "2.0.0-beta.9",
"@qwik.dev/core": "2.0.0-beta.11",
"@qwik.dev/router": "2.0.0-beta.11",
"@types/eslint": "8.56.10",
"@types/node": "20.14.11",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"eslint": "8.57.0",
"eslint-plugin-qwik": "2.0.0-beta.9",
"eslint-plugin-qwik": "2.0.0-beta.11",
"prettier": "3.3.3",
"typescript": "5.4.5",
"vite": "7.1.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"devDependencies": {
"@babel/types": "^7.26.7",
"@devtools/kit": "workspace:*",
"@qwik.dev/core": "2.0.0-beta.9",
"@qwik.dev/core": "2.0.0-beta.11",
"@types/eslint": "8.56.10",
"@types/node": "20.14.11",
"@typescript-eslint/eslint-plugin": "7.16.1",
Expand Down
7 changes: 5 additions & 2 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function qwikDevtools(): Plugin[] {
) {
return {
code: useCollectHooksSource,
map: null,
map: { mappings: '' },
};
}
},
Expand All @@ -57,8 +57,11 @@ export function qwikDevtools(): Plugin[] {
// Ensure virtual import is present at the very top once when a component$ is present
if (id.endsWith('.tsx') && code.includes('component$')) {
if (!code.includes(VIRTUAL_QWIK_DEVTOOLS_KEY)) {

const importLine = `import { ${INNER_USE_HOOK} } from '${VIRTUAL_QWIK_DEVTOOLS_KEY}';\n`
code = importLine + code
}else {
console.log('importing virtual qwik devtools', VIRTUAL_QWIK_DEVTOOLS_KEY, code);
}
code = parseQwikCode(code, {path: id})
}
Expand Down Expand Up @@ -88,7 +91,7 @@ export function qwikDevtools(): Plugin[] {

return {
code,
map: null,
map: { mappings: '' },
};
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"peerDependencies": {
"@devtools/plugin": "workspace:*",
"@qwik.dev/core": "2.0.0-beta.9"
"@qwik.dev/core": "2.0.0-beta.11"
},
"devDependencies": {
"@devtools/kit": "workspace:*",
Expand Down
6 changes: 2 additions & 4 deletions packages/ui/src/components/ThemeToggle/ThemeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,8 @@ export const ThemeToggle = component$(() => {
currentTheme = 'light';
} else if (currentTheme === 'light') {
currentTheme = 'auto';
} else {
currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'light'
: 'dark';
} else if (currentTheme === 'auto') {
currentTheme = 'dark';
}
setTheme(currentTheme);
});
Expand Down
11 changes: 3 additions & 8 deletions packages/ui/src/components/Tree/filterVnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import {
_ElementVNode,
_VirtualVNode,
_VNode,
_vnode_getAttr,
_vnode_getAttrKeys,
_vnode_getFirstChild,
_vnode_getNextSibling,
_vnode_getProps,
_vnode_isMaterialized,
_vnode_isVirtualVNode,
QRL,
Expand Down Expand Up @@ -70,10 +67,8 @@ function buildTreeRecursive(

const value = container.getHostProp(currentVNode!, key) as QRL;
// Update the underlying VNode props array and the new object's props.
_vnode_getProps(currentVNode!)[
_vnode_getProps(currentVNode!).indexOf(key) + 1
] = value;
vnodeObject.props![key] = _vnode_getAttr(currentVNode!, key);
currentVNode?.setProp(key, value);
vnodeObject.props![key] = currentVNode?.getAttr(key);

// Special handling to set the label from the render function's symbol.
if (key === QRENDERFN) {
Expand Down Expand Up @@ -106,7 +101,7 @@ function buildTreeRecursive(
}

// Move to the next sibling in the tree.
currentVNode = _vnode_getNextSibling(currentVNode);
currentVNode = currentVNode.nextSibling as _VNode | null;
}

return result;
Expand Down
13 changes: 12 additions & 1 deletion packages/ui/src/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
HiPhotoOutline,
HiMegaphoneMini,
HiCubeOutline,

HiCodeBracketSolid
} from '@qwikest/icons/heroicons';
import { BsDiagram3 } from '@qwikest/icons/bootstrap';
import { LuFolderTree } from '@qwikest/icons/lucide';
Expand All @@ -38,6 +40,7 @@ import { Packages } from './features/Packages/Packages';
import { Inspect } from './features/inspect/Inspect';
import { ThemeToggle } from './components/ThemeToggle/ThemeToggle';
import { ThemeScript } from './components/ThemeToggle/theme-script';
import { CodeBreack } from './features/CodeBreack/CodeBreack';
function getClientRpcFunctions() {
return {
healthCheck: () => true,
Expand Down Expand Up @@ -146,7 +149,9 @@ export const QwikDevtools = component$(() => {
<Tab state={state} id="inspect" title="inspect">
<HiMegaphoneMini class="h-5 w-5" />
</Tab>

<Tab state={state} id="codeBreack" title="codeBreack">
< HiCodeBracketSolid class="h-5 w-5" />
</Tab>
<div class="mt-auto">
<ThemeToggle />
</div>
Expand Down Expand Up @@ -211,6 +216,12 @@ export const QwikDevtools = component$(() => {
<RenderTree q:slot="content" />
</TabContent>
)}
{state.activeTab === 'codeBreack' && (
<TabContent>
<TabTitle title="codeBreack" q:slot="title" />
<CodeBreack q:slot="content" />
</TabContent>
)}
</div>
</DevtoolsPanel>
)}
Expand Down
59 changes: 59 additions & 0 deletions packages/ui/src/features/CodeBreack/CodeBreack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { component$, useSignal, useStyles$ } from '@qwik.dev/core';
import { StateParser } from './StateParser';
import { HtmlParser } from './HtmlParser';

type ParserTab = 'state' | 'html';

export const CodeBreack = component$(() => {
useStyles$(`
.code-output-container {
overflow-x: auto;
overflow-y: auto;
}
.code-output-container > div {
width: fit-content;
min-width: 100%;
}
.code-output-container pre[class*='language-'] {
margin: 0;
min-width: max-content;
}
`);

const currentTab = useSignal<ParserTab>('state');


return (
<div class="space-y-6 h-full overflow-hidden">
{/* Segmented Navigation */}
<div class="flex justify-center">
<div class="inline-flex rounded-xl border border-border bg-background p-0.5">
<button
onClick$={() => (currentTab.value = 'state')}
class={{
'px-3 md:px-4 py-2 rounded-lg text-sm font-medium transition-colors': true,
'bg-accent text-white shadow': currentTab.value === 'state',
'text-muted-foreground hover:bg-foreground/5': currentTab.value !== 'state',
}}
>
State Parser
</button>
<button
onClick$={() => (currentTab.value = 'html')}
class={{
'px-3 md:px-4 py-2 rounded-lg text-sm font-medium transition-colors': true,
'bg-accent text-white shadow': currentTab.value === 'html',
'text-muted-foreground hover:bg-foreground/5': currentTab.value !== 'html',
}}
>
HTML Parser
</button>
</div>
</div>

{/* Content */}
{currentTab.value === 'state' && <StateParser />}
{currentTab.value === 'html' && <HtmlParser />}
</div>
);
});
121 changes: 121 additions & 0 deletions packages/ui/src/features/CodeBreack/HtmlParser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { $, component$, useSignal, useVisibleTask$, useStyles$ } from '@qwik.dev/core';
import { _getDomContainer, _vnode_toString } from '@qwik.dev/core/internal';
import { createHighlighter } from 'shiki';

export const HtmlParser = component$(() => {
useStyles$(`
pre.shiki { overflow: auto; padding: 10px; height: 100%; }
`);
const inputHtml = useSignal('');
const parsingTime = useSignal<number | null>(null);
const htmlResult = useSignal<string>('');
const highlightedHtml = useSignal<string>('');

const onParseHtml$ = $(() => {
if (!inputHtml.value.trim()) {
parsingTime.value = null;
htmlResult.value = '// Paste HTML on the left to parse';
return;
}

const startTime = performance.now();

try {
const parser = new DOMParser();
const doc = parser.parseFromString(inputHtml.value, 'text/html');
let output = '';
try {
const container = _getDomContainer(doc.documentElement);
if (container) {
output += '// Qwik Container Found:\n';
output += `- Container Type: ${container.qContainer}\n`;
output += `- Manifest Hash: ${container.qManifestHash}\n\n`;
try {
const vdomTree = _vnode_toString.call(
container!.rootVNode as any,
Number.MAX_SAFE_INTEGER,
'',
true,
false,
false
);
output += '// VNode Tree:\n' + vdomTree + '\n\n';
} catch (vnodeErr) {
output += '// VNode parsing error: ' + vnodeErr + '\n\n';
}
} else {
output = '// No Qwik container found in the HTML';
}
} catch (containerErr) {
output = '// No Qwik container found or error: ' + containerErr;
}
parsingTime.value = performance.now() - startTime;
htmlResult.value = output;
return;
} catch (error) {
parsingTime.value = performance.now() - startTime;
htmlResult.value = `// Error parsing HTML: ${error instanceof Error ? error.message : 'Invalid HTML format'}\n\n// Raw input:\n${inputHtml.value}`;
return;
}
});

const shikiRef = useSignal<any>(null);
useVisibleTask$(async ({ track }) => {
track(() => htmlResult.value);
if (!htmlResult.value) {
highlightedHtml.value = '';
return;
}
if (!shikiRef.value) {
shikiRef.value = await createHighlighter({ themes: ['nord'], langs: ['html'] });
}
highlightedHtml.value = shikiRef.value.codeToHtml(htmlResult.value, { lang: 'html', theme: 'nord' });
});

return (
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="rounded-xl border border-border bg-card-item-bg flex h-[60vh] min-h-0 flex-col">
<div class="flex items-center justify-between border-b border-border p-3">
<div class="text-sm font-medium">Input HTML</div>
{parsingTime.value !== null && (
<span class="rounded-full border border-border px-2 py-0.5 text-xs text-muted-foreground">
{parsingTime.value}ms
</span>
)}
</div>
<div class="p-3 space-y-3 flex-1 min-h-0 flex-col">
<textarea
value={inputHtml.value}
onInput$={(e, t) => (inputHtml.value = (t as HTMLTextAreaElement).value)}
placeholder="Paste HTML and click to parse/format."
class="h-full min-h-0 w-full flex-1 resize-none rounded-md border border-border bg-background p-3 font-mono text-sm"
/>
<div class="flex items-center gap-3">
<button
onClick$={onParseHtml$}
class="bg-accent hover:opacity-90 rounded-md px-3 py-1.5 text-sm text-white"
>
Parse HTML
</button>
</div>
</div>
</div>

<div class="rounded-xl border border-border bg-card-item-bg overflow-hidden flex h-[60vh] min-h-0 flex-col">
<div class="flex items-center justify-between border-b border-border p-3">
<div class="text-sm font-medium">VNode Tree</div>
{parsingTime.value !== null && (
<span class="rounded-full border border-border px-2 py-0.5 text-xs text-muted-foreground">
{parsingTime.value}ms
</span>
)}
</div>
<div class="flex-1 min-h-0 h-full">
<pre class="overflow-auto h-full" dangerouslySetInnerHTML={highlightedHtml.value || ''} />
</div>
</div>
</div>
);
});


Loading
Loading