gui: port frontend to Wails v2

Frontend build failure root cause:
- Vite 8 uses rolldown with Wails v3 typed-events plugin
- @wailsio/runtime (Wails v3) in frontend dependencies
- vite.config.js had wails('./bindings') plugin from Wails v3 template
- main.js used Svelte 5 mount() API but Svelte 4 required

Fixes:
- Remove @wailsio/runtime dependency
- Remove wails('./bindings') plugin from vite.config.js
- Replace Vite 8 with Vite 5.4.21 + Rollup (stable)
- Downgrade Svelte 5 to Svelte 4.2.19
- Downgrade @sveltejs/vite-plugin-svelte to v3.1.2
- Fix main.js: mount() -> new App({ target })
- Rewrite App.svelte with Wails v2 binding calls (window.go.main.App.*)
- UI: sidebar with sections, nodes, basic navigation

Build: cd frontend && npm run build -> dist/ (476ms)
Build GUI: go build -tags 'gui production webkit2_41' -o verstak-gui ./cmd/verstak-gui
Run: ./verstak-gui (window opens, no SIGSEGV)
This commit is contained in:
mirivlad 2026-05-31 19:39:49 +08:00
parent c65187f656
commit 77a7918569
12 changed files with 1162 additions and 717 deletions

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.svelte-10a60fp.svelte-10a60fp{box-sizing:border-box;margin:0;padding:0}.app.svelte-10a60fp.svelte-10a60fp{display:flex;flex-direction:column;height:100vh;background:#13131f;color:#e4e4ef;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.topbar.svelte-10a60fp.svelte-10a60fp{padding:12px 20px;border-bottom:1px solid #2a2a3c;display:flex;align-items:center;gap:12px}.logo.svelte-10a60fp.svelte-10a60fp{font-size:20px}.topbar.svelte-10a60fp h1.svelte-10a60fp{font-size:18px;font-weight:600}.version.svelte-10a60fp.svelte-10a60fp{color:#666;font-size:12px;margin-left:auto}.error-banner.svelte-10a60fp.svelte-10a60fp{background:#422;color:#f88;padding:8px 20px;font-size:13px}.main.svelte-10a60fp.svelte-10a60fp{display:flex;flex:1;overflow:hidden}.sidebar.svelte-10a60fp.svelte-10a60fp{width:240px;border-right:1px solid #2a2a3c;padding:12px;overflow-y:auto;flex-shrink:0}.sidebar-header.svelte-10a60fp.svelte-10a60fp{font-size:11px;text-transform:uppercase;color:#666;margin-bottom:8px;padding:0 12px}.sidebar-item.svelte-10a60fp.svelte-10a60fp{padding:8px 12px;border-radius:6px;cursor:pointer;margin-bottom:2px;color:#ccc}.sidebar-item.svelte-10a60fp.svelte-10a60fp:hover{background:#1e1e2e}.sidebar-item.selected.svelte-10a60fp.svelte-10a60fp{background:#2a2a3c;color:#fff}.sidebar-empty.svelte-10a60fp.svelte-10a60fp{color:#666;font-size:12px;padding:8px 12px}.content.svelte-10a60fp.svelte-10a60fp{flex:1;padding:20px;overflow-y:auto}.welcome.svelte-10a60fp.svelte-10a60fp{color:#8888a4;font-size:14px;line-height:1.6}.error-text.svelte-10a60fp.svelte-10a60fp{color:#f88}.hint.svelte-10a60fp.svelte-10a60fp{color:#666;margin-top:8px}.loading.svelte-10a60fp.svelte-10a60fp{color:#888;font-size:14px}.node-view.svelte-10a60fp h2.svelte-10a60fp{font-size:24px;margin-bottom:16px}.node-view.svelte-10a60fp p.svelte-10a60fp{color:#888;font-size:13px;margin-bottom:8px}

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/wails.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<title>Wails + Svelte</title>
<script type="module" crossorigin src="/assets/index-C37GEF9W.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D3NJ53hP.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,157 @@
:root {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: rgba(27, 38, 54, 1);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
src: local(""),
url("./Inter-Medium.ttf") format("truetype");
}
h3 {
font-size: 3em;
line-height: 1.1;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
button {
width: 60px;
height: 30px;
line-height: 30px;
border-radius: 3px;
border: none;
margin: 0 0 0 20px;
padding: 0 8px;
cursor: pointer;
}
.result {
height: 20px;
line-height: 20px;
}
body {
margin: 0;
display: flex;
place-items: center;
place-content: center;
min-width: 320px;
min-height: 100vh;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #e80000aa);
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #f7df1eaa);
}
.result {
height: 20px;
line-height: 20px;
margin: 1.5rem auto;
text-align: center;
}
.footer {
margin-top: 1rem;
align-content: center;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
.input-box .btn:hover {
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
color: #333333;
}
.input-box .input {
border: none;
border-radius: 3px;
outline: none;
height: 30px;
line-height: 30px;
padding: 0 10px;
color: black;
background-color: rgba(240, 240, 240, 1);
-webkit-font-smoothing: antialiased;
}
.input-box .input:hover {
border: none;
background-color: rgba(255, 255, 255, 1);
}
.input-box .input:focus {
border: none;
background-color: rgba(255, 255, 255, 1);
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because it is too large Load Diff

View File

@ -5,16 +5,13 @@
"type": "module",
"scripts": {
"dev": "vite",
"build:dev": "vite build --minify false --mode development",
"build": "vite build --mode production",
"preview": "vite preview"
},
"dependencies": {
"@wailsio/runtime": "latest"
},
"dependencies": {},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"svelte": "^5.46.4",
"vite": "^8.0.5"
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"svelte": "^4.2.19",
"vite": "^5.4.21"
}
}

View File

@ -1,10 +1,129 @@
<script>
// Stub — will be replaced with actual UI
import { onMount } from 'svelte'
let sections = []
let nodes = []
let version = ''
let error = ''
let selectedSection = ''
let selectedNode = null
// Wails v2 bindings — generated by wails
// For now, use window.wails or import from wailsjs
async function callGo(method, ...args) {
if (window.go && window.go.main && window.go.main.App) {
return window.go.main.App[method](...args)
}
throw new Error('Wails bindings not loaded')
}
onMount(async () => {
try {
version = 'verstak-gui'
// Try Wails bindings
if (window.go && window.go.main && window.go.main.App) {
version = await window.go.main.App.VerstakVersion()
sections = await window.go.main.App.ListSections()
nodes = await window.go.main.App.ListRootNodes()
}
} catch (e) {
error = String(e)
}
})
function selectSection(id) {
selectedSection = id
// TODO: load nodes for section
}
function selectNode(node) {
selectedNode = node
}
</script>
<div style="display:flex;align-items:center;justify-content:center;height:100vh;background:#13131f;color:#e4e4ef;font-family:sans-serif">
<div style="text-align:center">
<h1 style="font-size:28px;margin-bottom:8px">&#9874; Верстак</h1>
<p style="color:#8888a4">Wails desktop app — under construction</p>
<div class="app">
<!-- Top bar -->
<div class="topbar">
<span class="logo">&#9874;</span>
<h1>Верстак</h1>
<span class="version">{version}</span>
</div>
<!-- Error banner -->
{#if error}
<div class="error-banner">
Error: {error}
</div>
{/if}
<!-- Main content -->
<div class="main">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">Разделы</div>
{#each sections as section}
<div class="sidebar-item {selectedSection === section.id ? 'selected' : ''}"
on:click={() => selectSection(section.id)}>
{section.label}
</div>
{/each}
<div class="sidebar-header" style="margin-top:16px">Корневые дела</div>
{#each nodes as node}
<div class="sidebar-item {selectedNode && selectedNode.id === node.id ? 'selected' : ''}"
on:click={() => selectNode(node)}>
{node.title}
</div>
{/each}
{#if nodes.length === 0 && sections.length > 0}
<div class="sidebar-empty">Нет дел. Создайте первое дело.</div>
{/if}
</div>
<!-- Content panel -->
<div class="content">
{#if selectedNode}
<div class="node-view">
<h2>{selectedNode.title}</h2>
<p>ID: {selectedNode.id}</p>
<p>Type: {selectedNode.type}</p>
</div>
{:else if sections.length > 0}
<div class="welcome">
<p>Wails v2 Desktop GUI работает.</p>
<p>Sections: {sections.length}, Root nodes: {nodes.length}</p>
{#if error}
<p class="error-text">Wails bindings error: {error}</p>
<p class="hint">Window opens but Go bindings not connected yet.</p>
{/if}
</div>
{:else}
<div class="loading">Загрузка...</div>
{/if}
</div>
</div>
</div>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
.app { display: flex; flex-direction: column; height: 100vh; background: #13131f; color: #e4e4ef; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
.topbar { padding: 12px 20px; border-bottom: 1px solid #2a2a3c; display: flex; align-items: center; gap: 12px; }
.logo { font-size: 20px; }
.topbar h1 { font-size: 18px; font-weight: 600; }
.version { color: #666; font-size: 12px; margin-left: auto; }
.error-banner { background: #442222; color: #ff8888; padding: 8px 20px; font-size: 13px; }
.main { display: flex; flex: 1; overflow: hidden; }
.sidebar { width: 240px; border-right: 1px solid #2a2a3c; padding: 12px; overflow-y: auto; flex-shrink: 0; }
.sidebar-header { font-size: 11px; text-transform: uppercase; color: #666; margin-bottom: 8px; padding: 0 12px; }
.sidebar-item { padding: 8px 12px; border-radius: 6px; cursor: pointer; margin-bottom: 2px; color: #ccc; }
.sidebar-item:hover { background: #1e1e2e; }
.sidebar-item.selected { background: #2a2a3c; color: #fff; }
.sidebar-empty { color: #666; font-size: 12px; padding: 8px 12px; }
.content { flex: 1; padding: 20px; overflow-y: auto; }
.welcome { color: #8888a4; font-size: 14px; line-height: 1.6; }
.error-text { color: #ff8888; }
.hint { color: #666; margin-top: 8px; }
.loading { color: #888; font-size: 14px; }
.node-view h2 { font-size: 24px; margin-bottom: 16px; }
.node-view p { color: #888; font-size: 13px; margin-bottom: 8px; }
</style>

View File

@ -1,4 +1,5 @@
import { mount } from 'svelte'
import App from './App.svelte'
mount(App, { target: document.getElementById('app') })
new App({
target: document.getElementById('app')
})

View File

@ -1,13 +1,15 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import wails from "@wailsio/runtime/plugins/vite";
// https://vitejs.dev/config/
export default defineConfig({
server: {
host: "127.0.0.1",
port: Number(process.env.WAILS_VITE_PORT) || 9245,
port: 3001,
strictPort: true,
},
plugins: [svelte(), wails("./bindings")],
plugins: [svelte()],
build: {
outDir: "dist",
emptyOutDir: true,
},
});