deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
{
"name": "@theia/design-panel",
"version": "1.68.0",
"description": "Vibn Design Panel — screen browser and live preview",
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
"keywords": [
"theia-extension"
],
"theiaExtensions": [
{
"frontend": "lib/browser/design-panel-frontend-module"
}
],
"dependencies": {
"@theia/core": "1.68.0",
"@theia/filesystem": "1.68.0",
"@theia/workspace": "1.68.0",
"tslib": "^2.6.2"
},
"devDependencies": {
"@theia/ext-scripts": "1.68.0"
},
"files": [
"lib",
"src"
],
"scripts": {
"build": "theiaext build",
"clean": "theiaext clean",
"compile": "theiaext compile",
"lint": "theiaext lint",
"watch": "theiaext watch"
},
"nyc": {
"extends": "../../configs/nyc.json"
}
}

View File

@@ -0,0 +1,42 @@
import { injectable } from '@theia/core/shared/inversify';
import { AbstractViewContribution } from '@theia/core/lib/browser';
import { Command, CommandRegistry, MenuModelRegistry } from '@theia/core/lib/common';
import { CommonMenus } from '@theia/core/lib/browser';
import { DesignPanelWidget } from './design-panel-widget';
export const DesignPanelCommand: Command = {
id: 'vibn.design.panel.open',
label: 'Open Design Panel',
category: 'Design',
};
@injectable()
export class DesignPanelContribution extends AbstractViewContribution<DesignPanelWidget> {
constructor() {
super({
widgetId: DesignPanelWidget.ID,
widgetName: DesignPanelWidget.LABEL,
defaultWidgetOptions: {
area: 'main',
},
toggleCommandId: DesignPanelCommand.id,
});
}
override registerCommands(registry: CommandRegistry): void {
super.registerCommands(registry);
registry.registerCommand(DesignPanelCommand, {
execute: () => this.openView({ reveal: true, activate: true }),
});
}
override registerMenus(menus: MenuModelRegistry): void {
super.registerMenus(menus);
menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
commandId: DesignPanelCommand.id,
label: 'Design Panel',
order: 'z',
});
}
}

View File

@@ -0,0 +1,14 @@
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
import { WidgetFactory, bindViewContribution } from '@theia/core/lib/browser';
import { DesignPanelContribution } from './design-panel-contribution';
import { DesignPanelWidget } from './design-panel-widget';
export default new ContainerModule((bind: interfaces.Bind) => {
bindViewContribution(bind, DesignPanelContribution);
bind(DesignPanelWidget).toSelf();
bind(WidgetFactory).toDynamicValue(ctx => ({
id: DesignPanelWidget.ID,
createWidget: () => ctx.container.get<DesignPanelWidget>(DesignPanelWidget),
})).inSingletonScope();
});

View File

@@ -0,0 +1,225 @@
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { ReactWidget, Message } from '@theia/core/lib/browser';
import { WorkspaceService } from '@theia/workspace/lib/browser';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import URI from '@theia/core/lib/common/uri';
import * as React from '@theia/core/shared/react';
import '../../src/browser/style/index.css';
interface Route {
label: string;
path: string;
}
interface DesignPanelState {
routes: Route[];
selectedRoute: string | null;
previewUrl: string;
scanning: boolean;
}
@injectable()
export class DesignPanelWidget extends ReactWidget {
static readonly ID = 'vibn.design.panel';
static readonly LABEL = 'Design';
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;
@inject(FileService)
protected readonly fileService: FileService;
protected state: DesignPanelState = {
routes: [],
selectedRoute: null,
previewUrl: 'http://localhost:3000',
scanning: false,
};
@postConstruct()
protected init(): void {
this.id = DesignPanelWidget.ID;
this.title.label = DesignPanelWidget.LABEL;
this.title.caption = 'Design — screen browser & live preview';
this.title.iconClass = 'codicon codicon-browser';
this.title.closable = true;
this.node.tabIndex = 0;
this.workspaceService.onWorkspaceChanged(() => this.scanRoutes());
this.workspaceService.ready.then(() => this.scanRoutes());
}
protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.node.focus();
}
protected setState(partial: Partial<DesignPanelState>): void {
this.state = { ...this.state, ...partial };
this.update();
}
protected async scanRoutes(): Promise<void> {
this.setState({ scanning: true, routes: [] });
const roots = await this.workspaceService.roots;
if (!roots.length) {
this.setState({ scanning: false });
return;
}
const routes: Route[] = [];
for (const root of roots) {
const appDir = root.resource.resolve('app');
try {
await this.collectRoutes(appDir, '', routes);
} catch {
// app/ dir may not exist yet
}
}
routes.sort((a, b) => a.path.localeCompare(b.path));
this.setState({ scanning: false, routes });
}
protected async collectRoutes(dir: URI, routePrefix: string, result: Route[]): Promise<void> {
let stat;
try {
stat = await this.fileService.resolve(dir);
} catch {
return;
}
if (!stat.isDirectory || !stat.children) {
return;
}
for (const child of stat.children) {
// Skip private folders like (auth), _components, etc.
const name = child.name;
if (name.startsWith('_') || name.startsWith('.')) {
continue;
}
if (child.isDirectory) {
// Route groups like (auth) — strip parens for routing, keep for display
const isRouteGroup = name.startsWith('(') && name.endsWith(')');
const segment = isRouteGroup ? '' : '/' + name;
await this.collectRoutes(child.resource, routePrefix + segment, result);
} else if (child.name === 'page.tsx' || child.name === 'page.ts' || child.name === 'page.jsx' || child.name === 'page.js') {
const routePath = routePrefix || '/';
result.push({
label: routePath,
path: routePath,
});
}
}
}
protected handleRouteClick(route: Route): void {
const base = this.state.previewUrl.replace(/\/$/, '');
const path = route.path === '/' ? '' : route.path;
this.setState({ selectedRoute: route.path, previewUrl: base + path });
}
protected handleUrlChange(e: React.ChangeEvent<HTMLInputElement>): void {
this.setState({ previewUrl: e.target.value });
}
protected handleUrlKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {
if (e.key === 'Enter') {
this.update();
}
}
protected handleRefresh(): void {
// Force iframe reload by briefly blanking the src
this.update();
}
protected render(): React.ReactNode {
const { routes, selectedRoute, previewUrl, scanning } = this.state;
return (
<div className="vibn-design-panel">
{/* Top bar */}
<div className="vibn-design-toolbar">
<span className="vibn-design-toolbar__title">Design</span>
<input
className="vibn-design-url-bar"
type="text"
value={previewUrl}
onChange={this.handleUrlChange.bind(this)}
onKeyDown={this.handleUrlKeyDown.bind(this)}
placeholder="http://localhost:3000"
spellCheck={false}
/>
<button
className="vibn-design-btn"
title="Refresh preview"
onClick={this.handleRefresh.bind(this)}
>
<span className="codicon codicon-refresh" />
</button>
<button
className="vibn-design-btn"
title="Scan routes"
onClick={() => this.scanRoutes()}
>
<span className="codicon codicon-search" />
</button>
</div>
{/* Body */}
<div className="vibn-design-body">
{/* Screen list */}
<div className="vibn-design-screens">
<div className="vibn-design-screens__header">
Screens
{scanning && <span className="vibn-design-spinner" />}
</div>
{!scanning && routes.length === 0 && (
<div className="vibn-design-screens__empty">
<span className="codicon codicon-info" />
<p>No Next.js routes found.</p>
<p>Open a project with an <code>app/</code> directory.</p>
</div>
)}
{routes.map(route => (
<div
key={route.path}
className={`vibn-design-route${selectedRoute === route.path ? ' vibn-design-route--active' : ''}`}
onClick={() => this.handleRouteClick(route)}
title={route.path}
>
<span className="codicon codicon-file-code" />
<span className="vibn-design-route__label">{route.label}</span>
</div>
))}
</div>
{/* Preview */}
<div className="vibn-design-preview">
{previewUrl ? (
<iframe
className="vibn-design-iframe"
src={previewUrl}
title="Design preview"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals"
/>
) : (
<div className="vibn-design-preview__placeholder">
<span className="codicon codicon-browser" />
<p>Enter a URL above or start your dev server.</p>
</div>
)}
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,213 @@
.vibn-design-panel {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
overflow: hidden;
background: var(--theia-editor-background);
color: var(--theia-foreground);
font-family: var(--theia-ui-font-family);
font-size: var(--theia-ui-font-size1);
}
/* ── Toolbar ─────────────────────────────────────────── */
.vibn-design-toolbar {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: var(--theia-titleBar-activeBackground);
border-bottom: 1px solid var(--theia-widget-border);
flex-shrink: 0;
}
.vibn-design-toolbar__title {
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--theia-foreground);
opacity: 0.7;
white-space: nowrap;
}
.vibn-design-url-bar {
flex: 1;
padding: 3px 8px;
background: var(--theia-input-background);
color: var(--theia-input-foreground);
border: 1px solid var(--theia-input-border);
border-radius: 3px;
font-size: 12px;
font-family: var(--theia-ui-font-family);
outline: none;
min-width: 0;
}
.vibn-design-url-bar:focus {
border-color: var(--theia-focusBorder);
}
.vibn-design-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
background: transparent;
border: none;
border-radius: 3px;
color: var(--theia-foreground);
cursor: pointer;
flex-shrink: 0;
}
.vibn-design-btn:hover {
background: var(--theia-toolbar-hoverBackground);
}
/* ── Body ─────────────────────────────────────────────── */
.vibn-design-body {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
/* ── Screen list ─────────────────────────────────────── */
.vibn-design-screens {
width: 180px;
min-width: 140px;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--theia-widget-border);
overflow-y: auto;
background: var(--theia-sideBar-background);
}
.vibn-design-screens__header {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 10px 6px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--theia-sideBarTitle-foreground);
border-bottom: 1px solid var(--theia-widget-border);
flex-shrink: 0;
}
.vibn-design-screens__empty {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 4px;
padding: 20px 12px;
color: var(--theia-descriptionForeground);
font-size: 12px;
}
.vibn-design-screens__empty .codicon {
font-size: 24px;
opacity: 0.5;
margin-bottom: 4px;
}
.vibn-design-screens__empty p {
margin: 0;
line-height: 1.4;
}
.vibn-design-screens__empty code {
background: var(--theia-textCodeBlock-background);
padding: 1px 4px;
border-radius: 2px;
font-size: 11px;
}
.vibn-design-route {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
cursor: pointer;
user-select: none;
border-radius: 0;
overflow: hidden;
white-space: nowrap;
}
.vibn-design-route:hover {
background: var(--theia-list-hoverBackground);
}
.vibn-design-route--active {
background: var(--theia-list-activeSelectionBackground);
color: var(--theia-list-activeSelectionForeground);
}
.vibn-design-route__label {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ── Spinner ─────────────────────────────────────────── */
.vibn-design-spinner {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid var(--theia-foreground);
border-top-color: transparent;
border-radius: 50%;
animation: vibn-spin 0.6s linear infinite;
margin-left: auto;
}
@keyframes vibn-spin {
to { transform: rotate(360deg); }
}
/* ── Preview pane ────────────────────────────────────── */
.vibn-design-preview {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.vibn-design-iframe {
flex: 1;
width: 100%;
height: 100%;
border: none;
background: #fff;
}
.vibn-design-preview__placeholder {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
color: var(--theia-descriptionForeground);
font-size: 13px;
text-align: center;
padding: 20px;
}
.vibn-design-preview__placeholder .codicon {
font-size: 40px;
opacity: 0.3;
}
.vibn-design-preview__placeholder p {
margin: 0;
}

View File

@@ -0,0 +1,16 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
],
"references": [
{ "path": "../core" },
{ "path": "../filesystem" },
{ "path": "../workspace" }
]
}