Compare commits

..

2 Commits

Author SHA1 Message Date
mirivlad 600b67bc1e gui: add Wails v3 desktop app skeleton
- Go upgraded to 1.25.10 (required by Wails v3)
- Wails v3 installed (alpha.96)
- Frontend: Svelte+Vite scaffold in frontend/
- guimain.go: Wails GUI entry point (compiled with -tags gui)
- wails_service.go: stub service for Wails bindings
- Verstak desktop binary builds: go build -tags gui -o verstak-gui .
- CLI (./cmd/verstak/) unaffected
- Legacy HTTP GUI (internal/gui/) preserved as prototype
- Build: 24MB ELF binary with GTK4/WebKit2GTK-6

Build commands:
  CLI:  go build -o verstak ./cmd/verstak/
  GUI:  cd frontend && npm run build && go build -tags gui -o verstak-gui .
2026-05-31 15:45:52 +08:00
mirivlad 537e8a126e plan: rewrite for Wails GUI + full file/folder workflow
- Archive browser prototype as legacy (step 6)
- New steps 11-14: Wails GUI, Files/Folders, D&D, stabilization
- Steps 15+ paused until step 14 complete
- DokuWiki moved to contrib/plugins/ (optional)
- Full architecture: Wails bindings → Go core → vault+SQLite
- Detailed acceptance criteria for each step
2026-05-31 12:10:58 +08:00
38 changed files with 3174 additions and 408 deletions

3
.gitignore vendored
View File

@ -21,6 +21,9 @@ go.work
# Wails # Wails
frontend/dist/ frontend/dist/
frontend/node_modules/ frontend/node_modules/
frontend/bindings/
verstak-gui
verstak-cli
# VS Code # VS Code
.vscode/ .vscode/

355
build/Taskfile.yml Normal file
View File

@ -0,0 +1,355 @@
version: '3'
tasks:
go:mod:tidy:
summary: Runs `go mod tidy`
internal: true
cmds:
- go mod tidy
install:frontend:deps:
summary: Install frontend dependencies
cmds:
- task: install:frontend:deps:{{.PACKAGE_MANAGER}}
install:frontend:deps:npm:
dir: frontend
sources:
- package.json
- package-lock.json
generates:
- node_modules
preconditions:
- sh: npm version
msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
cmds:
- npm install
install:frontend:deps:bun:
dir: frontend
sources:
- package.json
- bun.lock
- bun.lockb
generates:
- node_modules
preconditions:
- sh: bun --version
msg: "bun not found"
cmds:
- bun install
install:frontend:deps:pnpm:
dir: frontend
sources:
- package.json
- pnpm-lock.yaml
generates:
- node_modules
preconditions:
- sh: pnpm --version
msg: "pnpm not found"
cmds:
- pnpm install
install:frontend:deps:yarn:
dir: frontend
sources:
- package.json
- yarn.lock
status:
- test -d node_modules || test -f .pnp.cjs
preconditions:
- sh: yarn --version
msg: "yarn not found"
cmds:
- yarn install
build:frontend:
label: build:frontend (DEV={{.DEV}} RUNNER={{.PACKAGE_MANAGER}})
summary: Build the frontend project
dir: frontend
sources:
- "**/*"
- exclude: node_modules/**/*
generates:
- dist/**/*
deps:
- task: install:frontend:deps
- task: generate:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
OBFUSCATED:
ref: .OBFUSCATED
cmds:
- task: frontend:run
vars:
SCRIPT: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}'
env:
PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}'
frontend:run:
summary: Run a frontend script with selected runner
cmds:
- task: frontend:run:{{.PACKAGE_MANAGER}}
vars:
SCRIPT: "{{.SCRIPT}}"
vars:
SCRIPT: "{{.SCRIPT}}"
frontend:run:npm:
dir: frontend
cmds:
- npm run {{.SCRIPT}} -q
vars:
SCRIPT: "{{.SCRIPT}}"
frontend:run:yarn:
dir: frontend
cmds:
- yarn {{.SCRIPT}}
vars:
SCRIPT: "{{.SCRIPT}}"
frontend:run:pnpm:
dir: frontend
cmds:
- pnpm run {{.SCRIPT}}
vars:
SCRIPT: "{{.SCRIPT}}"
frontend:run:bun:
dir: frontend
cmds:
- bun run {{.SCRIPT}}
vars:
SCRIPT: "{{.SCRIPT}}"
frontend:vendor:puppertino:
summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling
sources:
- frontend/public/puppertino/puppertino.css
generates:
- frontend/public/puppertino/puppertino.css
cmds:
- |
set -euo pipefail
mkdir -p frontend/public/puppertino
# If bundled Puppertino exists, prefer it. Otherwise, try to fetch, but don't fail build on error.
if [ ! -f frontend/public/puppertino/puppertino.css ]; then
echo "No bundled Puppertino found. Attempting to fetch from GitHub..."
if curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css; then
curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE || true
echo "Puppertino CSS downloaded to frontend/public/puppertino/puppertino.css"
else
echo "Warning: Could not fetch Puppertino CSS. Proceeding without download since template may bundle it."
fi
else
echo "Using bundled Puppertino at frontend/public/puppertino/puppertino.css"
fi
# Ensure index.html includes Puppertino CSS and button classes
INDEX_HTML=frontend/index.html
if [ -f "$INDEX_HTML" ]; then
if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then
# Insert Puppertino link tag after style.css link
awk '
/href="\/style.css"\/?/ && !x { print; print " <link rel=\"stylesheet\" href=\"/puppertino/puppertino.css\"/>"; x=1; next }1
' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML"
fi
# Replace default .btn with Puppertino primary button classes if present
sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true
fi
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings for the frontend
deps:
- task: go:mod:tidy
sources:
- "**/*.[jt]s"
- exclude: frontend/**/*
- frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true{{if eq .OBFUSCATED "true"}} -obfuscated{{end}}
generate:icons:
summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms).
dir: build
sources:
- "appicon.png"
- "appicon.icon"
generates:
- "darwin/icons.icns"
- "windows/icon.ico"
cmds:
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin
dev:frontend:
summary: Runs the frontend in development mode
deps:
- task: install:frontend:deps
cmds:
- task: frontend:dev:{{.PACKAGE_MANAGER}}
frontend:dev:npm:
dir: frontend
cmds:
- npm run dev -- --port {{.VITE_PORT}} --strictPort
frontend:dev:yarn:
dir: frontend
cmds:
- yarn dev --port {{.VITE_PORT}} --strictPort
frontend:dev:pnpm:
dir: frontend
cmds:
- pnpm dev --port {{.VITE_PORT}} --strictPort
frontend:dev:bun:
dir: frontend
cmds:
- bun run dev --port {{.VITE_PORT}} --strictPort
update:build-assets:
summary: Updates the build assets
dir: build
cmds:
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .
build:server:
summary: Builds the application in server mode (no GUI, HTTP server only)
desc: |
Builds the application with the server build tag enabled.
Server mode runs as a pure HTTP server without native GUI dependencies.
Usage: task build:server
deps:
- task: build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- go build -tags server {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
vars:
BUILD_FLAGS: "{{.BUILD_FLAGS}}"
run:server:
summary: Builds and runs the application in server mode
deps:
- task: build:server
cmds:
- ./{{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
build:docker:
summary: Builds a Docker image for server mode deployment
desc: |
Creates a minimal Docker image containing the server mode binary.
The image is based on distroless for security and small size.
Usage: task build:docker [TAG=myapp:latest]
cmds:
- docker build -t {{.TAG | default (printf "%s:latest" .APP_NAME)}} -f build/docker/Dockerfile.server .
vars:
TAG: "{{.TAG}}"
preconditions:
- sh: docker info > /dev/null 2>&1
msg: "Docker is required. Please install Docker first."
- sh: test -f build/docker/Dockerfile.server
msg: "Dockerfile.server not found. Run 'wails3 update build-assets' to generate it."
run:docker:
summary: Builds and runs the Docker image
desc: |
Builds the Docker image and runs it, exposing port 8080.
Usage: task run:docker [TAG=myapp:latest] [PORT=8080]
Note: The internal container port is always 8080. The PORT variable
only changes the host port mapping. Ensure your app uses port 8080
or modify the Dockerfile to match your ServerOptions.Port setting.
deps:
- task: build:docker
vars:
TAG:
ref: .TAG
cmds:
- docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default (printf "%s:latest" .APP_NAME)}}
vars:
TAG: "{{.TAG}}"
PORT: "{{.PORT}}"
setup:docker:
summary: Builds Docker image for cross-compilation (~800MB download)
desc: |
Builds the Docker image needed for cross-compiling to any platform.
Run this once to enable cross-platform builds from any OS.
cmds:
- docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/
preconditions:
- sh: docker info > /dev/null 2>&1
msg: "Docker is required. Please install Docker first."
ios:device:list:
summary: Lists connected iOS devices (UDIDs)
cmds:
- xcrun xcdevice list
ios:run:device:
summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl)
vars:
PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/<YourProject>.xcodeproj
SCHEME: '{{.SCHEME}}' # e.g., ios.dev
CONFIG: '{{.CONFIG | default "Debug"}}'
DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}'
UDID: '{{.UDID}}' # from `task ios:device:list`
BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev
TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing
preconditions:
- sh: xcrun -f xcodebuild
msg: "xcodebuild not found. Please install Xcode."
- sh: xcrun -f devicectl
msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)."
- sh: test -n '{{.PROJECT}}'
msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)."
- sh: test -n '{{.SCHEME}}'
msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)."
- sh: test -n '{{.UDID}}'
msg: "Set UDID to your device UDID (see: task ios:device:list)."
- sh: test -n '{{.BUNDLE_ID}}'
msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)."
cmds:
- |
set -euo pipefail
echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}"
XCB_ARGS=(
-project "{{.PROJECT}}"
-scheme "{{.SCHEME}}"
-configuration "{{.CONFIG}}"
-destination "id={{.UDID}}"
-derivedDataPath "{{.DERIVED}}"
-allowProvisioningUpdates
-allowProvisioningDeviceRegistration
)
# Optionally inject signing identifiers if provided
if [ -n '{{.TEAM_ID}}' ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi
if [ -n '{{.BUNDLE_ID}}' ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi
xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true
# If xcpretty isn't installed, run without it
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
xcodebuild "${XCB_ARGS[@]}" build
fi
# Find built .app
APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1)
if [ -z "$APP_PATH" ]; then
echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2
exit 1
fi
echo "Installing: $APP_PATH"
xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH"
echo "Launching: {{.BUNDLE_ID}}"
xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}"

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 583 533" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-246,-251)">
<g id="Ebene1">
<path d="M246,251L265,784L401,784L506,450L507,450L505,784L641,784L829,251L682,251L596,567L595,567L596,251L478,251L378,568L391,251L246,251Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 698 B

View File

@ -0,0 +1,51 @@
{
"fill" : {
"automatic-gradient" : "extended-gray:1.00000,1.00000"
},
"groups" : [
{
"layers" : [
{
"fill-specializations" : [
{
"appearance" : "dark",
"value" : {
"solid" : "srgb:0.92143,0.92145,0.92144,1.00000"
}
},
{
"appearance" : "tinted",
"value" : {
"solid" : "srgb:0.83742,0.83744,0.83743,1.00000"
}
}
],
"image-name" : "wails_icon_vector.svg",
"name" : "wails_icon_vector",
"position" : {
"scale" : 1.25,
"translation-in-points" : [
36.890625,
4.96875
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : true,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

BIN
build/appicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

79
build/config.yml Normal file
View File

@ -0,0 +1,79 @@
# This file contains the configuration for this project.
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
# Note that this will overwrite any changes you have made to the assets.
version: '3'
# This information is used to generate the build assets.
info:
companyName: "My Company" # The name of the company
productName: "My Product" # The name of the application
productIdentifier: "com.mycompany.myproduct" # The unique product identifier
description: "A program that does X" # The application description
copyright: "(c) 2025, My Company" # Copyright text
comments: "Some Product Comments" # Comments
version: "0.0.1" # The application version
# cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional)
# # Should match the name of your .icon file without the extension
# # If not set and Assets.car exists, defaults to "appicon"
# iOS build configuration (uncomment to customise iOS project generation)
# Note: Keys under `ios` OVERRIDE values under `info` when set.
# ios:
# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier)
# bundleID: "com.mycompany.myproduct"
# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName)
# displayName: "My Product"
# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion)
# version: "0.0.1"
# # The company/organisation name for templates and project settings
# company: "My Company"
# # Additional comments to embed in Info.plist metadata
# comments: "Some Product Comments"
# Dev mode configuration
dev_mode:
root_path: .
log_level: warn
debounce: 1000
ignore:
dir:
- .git
- node_modules
- frontend
- bin
file:
- .DS_Store
- .gitignore
- .gitkeep
- "*_test.go"
watched_extension:
- "*.go"
- "*.js" # Watch for changes to JS/TS files included using the //wails:include directive.
- "*.ts" # The frontend directory will be excluded entirely by the setting above.
git_ignore: true
executes:
- cmd: wails3 build DEV=true
type: blocking
- cmd: wails3 task common:dev:frontend
type: background
- cmd: wails3 task run
type: primary
# File Associations
# More information at: https://v3.wails.io/noit/done/yet
fileAssociations:
# - ext: wails
# name: Wails
# description: Wails Application File
# iconName: wailsFileIcon
# role: Editor
# - ext: jpg
# name: JPEG
# description: Image File
# iconName: jpegFileIcon
# role: Editor
# mimeType: image/jpeg # (optional)
# Other data
other:
- name: My Other Data

View File

@ -0,0 +1,212 @@
# Cross-compile Wails v3 apps to any platform
#
# Darwin: Zig + macOS SDK
# Linux: Native GCC when host matches target, Zig for cross-arch
# Windows: Zig + bundled mingw
#
# Usage:
# docker build -t wails-cross -f Dockerfile.cross .
# docker run --rm -v $(pwd):/app wails-cross darwin arm64
# docker run --rm -v $(pwd):/app wails-cross darwin amd64
# docker run --rm -v $(pwd):/app wails-cross linux amd64
# docker run --rm -v $(pwd):/app wails-cross linux arm64
# docker run --rm -v $(pwd):/app wails-cross windows amd64
# docker run --rm -v $(pwd):/app wails-cross windows arm64
FROM golang:1.26-bookworm
ARG TARGETARCH
ARG GARBLE_VERSION=v0.16.0
# Install base tools, GCC, and GTK/WebKit dev packages
RUN apt-get update && apt-get install -y --no-install-recommends \
curl xz-utils nodejs npm pkg-config gcc libc6-dev \
libgtk-3-dev libwebkit2gtk-4.1-dev \
libgtk-4-dev libwebkitgtk-6.0-dev \
&& rm -rf /var/lib/apt/lists/*
RUN go install mvdan.cc/garble@${GARBLE_VERSION}
# Install Zig - automatically selects correct binary for host architecture
ARG ZIG_VERSION=0.14.0
RUN ZIG_ARCH=$(case "${TARGETARCH}" in arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C /opt \
&& ln -s /opt/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}/zig /usr/local/bin/zig
# Download macOS SDK (required for darwin targets)
ARG MACOS_SDK_VERSION=14.5
RUN curl -L "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz" \
| tar -xJ -C /opt \
&& mv /opt/MacOSX${MACOS_SDK_VERSION}.sdk /opt/macos-sdk
ENV MACOS_SDK_PATH=/opt/macos-sdk
# Create Zig CC wrappers for cross-compilation targets
# Darwin and Windows use Zig; Linux uses native GCC (run with --platform for cross-arch)
# Darwin arm64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-mmacosx-version-min=*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -fno-sanitize=all -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-darwin-arm64
# Darwin amd64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-mmacosx-version-min=*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-darwin-amd64
# Windows amd64 - uses Zig's bundled mingw
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-Wl,*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target x86_64-windows-gnu $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-windows-amd64
# Windows arm64 - uses Zig's bundled mingw
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-Wl,*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target aarch64-windows-gnu $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-windows-arm64
# Build script
COPY <<'SCRIPT' /usr/local/bin/build.sh
#!/bin/sh
set -e
OS=${1:-darwin}
ARCH=${2:-arm64}
case "${OS}-${ARCH}" in
darwin-arm64|darwin-aarch64)
export CC=zcc-darwin-arm64
export GOARCH=arm64
export GOOS=darwin
;;
darwin-amd64|darwin-x86_64)
export CC=zcc-darwin-amd64
export GOARCH=amd64
export GOOS=darwin
;;
linux-arm64|linux-aarch64)
export CC=gcc
export GOARCH=arm64
export GOOS=linux
;;
linux-amd64|linux-x86_64)
export CC=gcc
export GOARCH=amd64
export GOOS=linux
;;
windows-arm64|windows-aarch64)
export CC=zcc-windows-arm64
export GOARCH=arm64
export GOOS=windows
;;
windows-amd64|windows-x86_64)
export CC=zcc-windows-amd64
export GOARCH=amd64
export GOOS=windows
;;
*)
echo "Usage: <os> <arch>"
echo " os: darwin, linux, windows"
echo " arch: amd64, arm64"
exit 1
;;
esac
export CGO_ENABLED=1
export CGO_CFLAGS="-w"
# Build frontend if exists and not already built (host may have built it)
if [ -d "frontend" ] && [ -f "frontend/package.json" ] && [ ! -d "frontend/dist" ]; then
(cd frontend && npm install --silent && npm run build --silent)
fi
# Build
APP=${APP_NAME:-$(basename $(pwd))}
mkdir -p bin
EXT=""
LDFLAGS="-s -w"
if [ "$GOOS" = "windows" ]; then
EXT=".exe"
LDFLAGS="-s -w -H windowsgui"
fi
TAGS="production"
if [ -n "$EXTRA_TAGS" ]; then
TAGS="${TAGS},${EXTRA_TAGS}"
fi
COMPILER="go build"
if [ "$OBFUSCATED" = "true" ]; then
COMPILER="garble ${GARBLE_ARGS} build"
TAGS="${TAGS},wails_obfuscated"
fi
${COMPILER} -tags "$TAGS" -trimpath -buildvcs=false -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}"
SCRIPT
RUN chmod +x /usr/local/bin/build.sh
WORKDIR /app
ENTRYPOINT ["/usr/local/bin/build.sh"]
CMD ["darwin", "arm64"]

View File

@ -0,0 +1,41 @@
# Wails Server Mode Dockerfile
# Multi-stage build for minimal image size
# Build stage
FROM golang:alpine AS builder
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache git
# Copy source code
COPY . .
# Remove local replace directive if present (for production builds)
RUN sed -i '/^replace/d' go.mod || true
# Download dependencies
RUN go mod tidy
# Build the server binary
RUN go build -tags server -ldflags="-s -w" -o server .
# Runtime stage - minimal image
FROM gcr.io/distroless/static-debian12
# Copy the binary
COPY --from=builder /app/server /server
# Copy frontend assets
COPY --from=builder /app/frontend/dist /frontend/dist
# Expose the default port
EXPOSE 8080
# Bind to all interfaces (required for Docker)
# Can be overridden at runtime with -e WAILS_SERVER_HOST=...
ENV WAILS_SERVER_HOST=0.0.0.0
# Run the server
ENTRYPOINT ["/server"]

224
build/linux/Taskfile.yml Normal file
View File

@ -0,0 +1,224 @@
version: '3'
includes:
common: ../Taskfile.yml
vars:
# Signing configuration - edit these values for your project
# PGP_KEY: "path/to/signing-key.asc"
# SIGN_ROLE: "builder" # Options: origin, maint, archive, builder
#
# Password is stored securely in system keychain. Run: wails3 setup signing
# Docker image for cross-compilation (used when building on non-Linux or no CC available)
CROSS_IMAGE: wails-cross
tasks:
build:
summary: Builds the application for Linux
cmds:
# Linux requires CGO - use Docker when:
# 1. Cross-compiling from non-Linux, OR
# 2. No C compiler is available, OR
# 3. Target architecture differs from host architecture (cross-arch compilation)
- task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}'
vars:
ARCH: '{{.ARCH}}'
DEV: '{{.DEV}}'
OUTPUT: '{{.OUTPUT}}'
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
OBFUSCATED: '{{.OBFUSCATED}}'
GARBLE_ARGS: '{{.GARBLE_ARGS}}'
vars:
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
# Determine target architecture (defaults to host ARCH if not specified)
TARGET_ARCH: '{{.ARCH | default ARCH}}'
# Check if a C compiler is available (gcc or clang) — cross-platform via wails3 tool
HAS_CC:
sh: 'wails3 tool has-cc'
build:native:
summary: Builds the application natively on Linux
internal: true
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
OBFUSCATED:
ref: .OBFUSCATED
DEV:
ref: .DEV
- task: common:generate:icons
- task: generate:dotdesktop
preconditions:
- sh: '{{if eq .OBFUSCATED "true"}}command -v garble >/dev/null 2>&1{{else}}true{{end}}'
msg: "garble is required for obfuscated builds. Install it with: go install mvdan.cc/garble@v0.16.0 (requires Go 1.24+). See https://github.com/burrowers/garble/releases for version/toolchain compatibility."
cmds:
- '{{if eq .OBFUSCATED "true"}}garble {{.GARBLE_ARGS}} build{{else}}go build{{end}} {{.BUILD_FLAGS}} -o {{.OUTPUT}}'
vars:
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
env:
GOOS: linux
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
build:docker:
summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available)
internal: true
deps:
- task: common:build:frontend
vars:
OBFUSCATED:
ref: .OBFUSCATED
- task: common:generate:icons
- task: generate:dotdesktop
preconditions:
- sh: docker info > /dev/null 2>&1
msg: "Docker is required for cross-compilation to Linux. Please install Docker."
- sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1
msg: |
Docker image '{{.CROSS_IMAGE}}' not found.
Build it first: wails3 task setup:docker
cmds:
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.DOCKER_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{if eq .OBFUSCATED "true"}}-e OBFUSCATED=true{{end}} {{if .GARBLE_ARGS}}-e GARBLE_ARGS="{{.GARBLE_ARGS}}"{{end}} "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
- cmd: docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
platforms: [linux, darwin]
- mkdir -p {{.BIN_DIR}}
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
vars:
DOCKER_ARCH: '{{.ARCH | default "amd64"}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
# Generate Docker volume mounts: Go module cache + go.mod replace directives
# Uses wails3 tool docker-mounts for cross-platform compatibility (Windows/Linux/macOS)
DOCKER_MOUNTS:
sh: 'wails3 tool docker-mounts'
package:
summary: Packages the application for Linux
deps:
- task: build
cmds:
- task: create:appimage
- task: create:deb
- task: create:rpm
- task: create:aur
create:appimage:
summary: Creates an AppImage
dir: build/linux/appimage
deps:
- task: build
- task: generate:dotdesktop
cmds:
- cp "{{.APP_BINARY}}" "{{.APP_NAME}}"
- cp ../../appicon.png "{{.APP_NAME}}.png"
- wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
vars:
APP_NAME: '{{.APP_NAME}}'
APP_BINARY: '../../../bin/{{.APP_NAME}}'
ICON: '{{.APP_NAME}}.png'
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
OUTPUT_DIR: '../../../bin'
create:deb:
summary: Creates a deb package
deps:
- task: build
cmds:
- task: generate:dotdesktop
- task: generate:deb
create:rpm:
summary: Creates a rpm package
deps:
- task: build
cmds:
- task: generate:dotdesktop
- task: generate:rpm
create:aur:
summary: Creates a arch linux packager package
deps:
- task: build
cmds:
- task: generate:dotdesktop
- task: generate:aur
generate:deb:
summary: Creates a deb package
cmds:
- wails3 tool package -name "{{.APP_NAME}}" -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:rpm:
summary: Creates a rpm package
cmds:
- wails3 tool package -name "{{.APP_NAME}}" -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:aur:
summary: Creates a arch linux packager package
cmds:
- wails3 tool package -name "{{.APP_NAME}}" -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:dotdesktop:
summary: Generates a `.desktop` file
dir: build
cmds:
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}"
vars:
APP_NAME: '{{.APP_NAME}}'
EXEC: '{{.APP_NAME}}'
ICON: '{{.APP_NAME}}'
CATEGORIES: 'Development;'
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}'
sign:deb:
summary: Signs the DEB package
desc: |
Signs the .deb package with a PGP key.
Configure PGP_KEY in the vars section at the top of this file.
Password is retrieved from system keychain (run: wails3 setup signing)
deps:
- task: create:deb
cmds:
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.deb" --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}}
preconditions:
- sh: '[ -n "{{.PGP_KEY}}" ]'
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
sign:rpm:
summary: Signs the RPM package
desc: |
Signs the .rpm package with a PGP key.
Configure PGP_KEY in the vars section at the top of this file.
Password is retrieved from system keychain (run: wails3 setup signing)
deps:
- task: create:rpm
cmds:
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.rpm" --pgp-key {{.PGP_KEY}}
preconditions:
- sh: '[ -n "{{.PGP_KEY}}" ]'
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
sign:packages:
summary: Signs all Linux packages (DEB and RPM)
desc: |
Signs both .deb and .rpm packages with a PGP key.
Configure PGP_KEY in the vars section at the top of this file.
Password is retrieved from system keychain (run: wails3 setup signing)
cmds:
- task: sign:deb
- task: sign:rpm
preconditions:
- sh: '[ -n "{{.PGP_KEY}}" ]'
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"

View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Copyright (c) 2018-Present Lea Anthony
# SPDX-License-Identifier: MIT
# Fail script on any error
set -euxo pipefail
# Define variables
APP_DIR="${APP_NAME}.AppDir"
# Create AppDir structure
mkdir -p "${APP_DIR}/usr/bin"
cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
cp "${ICON_PATH}" "${APP_DIR}/"
cp "${DESKTOP_FILE}" "${APP_DIR}/"
if [[ $(uname -m) == *x86_64* ]]; then
# Download linuxdeploy and make it executable
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
# Run linuxdeploy to bundle the application
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
else
# Download linuxdeploy and make it executable (arm64)
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
chmod +x linuxdeploy-aarch64.AppImage
# Run linuxdeploy to bundle the application (arm64)
./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
fi
# Rename the generated AppImage
mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"

13
build/linux/desktop Normal file
View File

@ -0,0 +1,13 @@
[Desktop Entry]
Version=1.0
Name=My Product
Comment=A verstak application
# The Exec line includes %u to pass the URL to the application
Exec=/usr/local/bin/verstak %u
Terminal=false
Type=Application
Icon=verstak
Categories=Utility;
StartupWMClass=verstak

View File

@ -0,0 +1,80 @@
# Feel free to remove those if you don't want/need to use them.
# Make sure to check the documentation at https://nfpm.goreleaser.com
#
# The lines below are called `modelines`. See `:help modeline`
name: "verstak"
arch: ${GOARCH}
platform: "linux"
version: "0.1.0"
section: "default"
priority: "extra"
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
description: "A verstak application"
vendor: "My Company"
homepage: "https://wails.io"
license: "MIT"
release: "1"
contents:
- src: "./bin/verstak"
dst: "/usr/local/bin/verstak"
- src: "./build/appicon.png"
dst: "/usr/share/icons/hicolor/128x128/apps/verstak.png"
- src: "./build/linux/verstak.desktop"
dst: "/usr/share/applications/verstak.desktop"
# Default dependencies for the GTK4 + WebKitGTK 6.0 stack (Ubuntu 24.04+ / Debian 13+)
depends:
- libgtk-4-1
- libwebkitgtk-6.0-4
# Distribution-specific overrides for different package formats
overrides:
# RPM packages for Fedora / RHEL / AlmaLinux / Rocky Linux
rpm:
depends:
- gtk4
- webkitgtk6.0
# Arch Linux packages
archlinux:
depends:
- gtk4
- webkitgtk-6.0
# scripts section to ensure desktop database is updated after install
scripts:
postinstall: "./build/linux/nfpm/scripts/postinstall.sh"
# You can also add preremove, postremove if needed
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
# If you build your app with -tags gtk3 (legacy WebKit2GTK 4.1 stack — supported through v3.0.x, removed in v3.1),
# replace the depends/overrides above with these:
#
# depends:
# - libgtk-3-0
# - libwebkit2gtk-4.1-0
# overrides:
# rpm:
# depends:
# - gtk3
# - webkit2gtk4.1
# archlinux:
# depends:
# - gtk3
# - webkit2gtk-4.1
#
# replaces:
# - foobar
# provides:
# - bar
# recommends:
# - whatever
# suggests:
# - something-else
# conflicts:
# - not-foo
# - not-bar
# changelog: "changelog.yaml"

View File

@ -0,0 +1,21 @@
#!/bin/sh
# Update desktop database for .desktop file changes
# This makes the application appear in application menus and registers its capabilities.
if command -v update-desktop-database >/dev/null 2>&1; then
echo "Updating desktop database..."
update-desktop-database -q /usr/share/applications
else
echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2
fi
# Update MIME database for custom URL schemes (x-scheme-handler)
# This ensures the system knows how to handle your custom protocols.
if command -v update-mime-database >/dev/null 2>&1; then
echo "Updating MIME database..."
update-mime-database -n /usr/share/mime
else
echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2
fi
exit 0

View File

@ -0,0 +1 @@
#!/bin/bash

View File

@ -0,0 +1 @@
#!/bin/bash

View File

@ -0,0 +1 @@
#!/bin/bash

View File

@ -1,71 +0,0 @@
//go:build gui
// +build gui
package main
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"syscall"
gui "verstak/internal/gui"
"verstak/internal/core/storage"
)
func main() {
vaultPath := "."
if len(os.Args) > 1 {
vaultPath = os.Args[1]
}
abs, err := filepath.Abs(vaultPath)
if err != nil {
log.Fatal(err)
}
dbPath := filepath.Join(abs, ".verstak", "index.db")
db, err := storage.Open(dbPath)
if err != nil {
log.Fatalf("Open vault: %v", err)
}
defer db.Close()
srv := gui.NewServer(db, abs)
addr, err := srv.Start()
if err != nil {
log.Fatalf("Start GUI: %v", err)
}
fmt.Println("Верстак GUI:", addr)
openBrowser(addr)
// Wait for interrupt.
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
srv.Stop()
deferFunc()
}
func openBrowser(url string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("xdg-open", url)
case "darwin":
cmd = exec.Command("open", url)
case "windows":
cmd = exec.Command("cmd", "/c", "start", "", url)
}
if cmd != nil {
go cmd.Start()
}
}
func deferFunc() {}

View File

@ -1,4 +1,4 @@
# Верстак — Пошаговый план реализации # Верстак — Пошаговый план разработки
## Принципы работы ## Принципы работы
@ -16,359 +16,246 @@
| 3 | Nodes Repository + CRUD + CLI Node | ✅ выполнен | | 3 | Nodes Repository + CRUD + CLI Node | ✅ выполнен |
| 4 | Vault Files: Trash + File Service + CLI File | ✅ выполнен | | 4 | Vault Files: Trash + File Service + CLI File | ✅ выполнен |
| 5 | Markdown Notes: Create/Read/Save + CLI Note | ✅ выполнен | | 5 | Markdown Notes: Create/Read/Save + CLI Note | ✅ выполнен |
| 6 | Wails GUI MVP: Sidebar + Main Panel | ✅ выполнен (Go HTTP SPA) | | 6 | GUI (browser prototype): Sidebar + Main Panel | ✅ выполнен |
| 7 | Actions: Run URL/File/Command + GUI Tab | ✅ выполнен | | 7 | Actions + Worklog | ✅ выполнен |
| 8 | Worklog: Entries + Report + GUI Tab | ✅ выполнен | | 8 | FTS5 Search | ✅ выполнен |
| 9 | FTS5 Search: Rebuild Index + GUI Search Bar | ✅ выполнен | | 9 | Section assignment + Sidebar filtering | ✅ выполнен |
| 10 | Plugins System (Lua + Templates) | ✅ выполнен | | 10 | Plugin Manager (discovery + templates) | ✅ выполнен |
| 11 | Sync Server Skeleton | ⬜ не начат | | 11 | **Wails Desktop GUI** | ⬜ не начат |
| 12 | Sync Client MVP | ⬜ не начат | | 12 | **Files/Folders full workflow** | ⬜ не начат |
| 13 | Activity + File Scanner/Watcher | ⬜ не начат | | 13 | **Drag-and-drop** | ⬜ не начат |
| 14 | TUI MVP (Bubble Tea) | ⬜ не начат | | 14 | **MVP stabilization** | ⬜ не начат |
| 15 | Integrity Check + Repair + Vault Restore | ⬜ не начат | | 15 | Sync Server Skeleton | 🔒 приостановлен |
| 16 | Plugins System (Lua + Templates) | ⬜ не начат | | 16 | Sync Client MVP | 🔒 приостановлен |
| 17 | Activity + File Scanner/Watcher | 🔒 приостановлен |
| 18 | TUI MVP (Bubble Tea) | 🔒 приостановлен |
| 19 | Integrity Check + Repair | 🔒 приостановлен |
| 20 | Plugins: Lua runtime | 🔒 приостановлен |
| 21 | DokuWiki Importer (plugin) | 🔒 приостановлен |
> 🔒 = **PAUSED** — не начинать до завершения шага 14 (MVP stabilization).
--- ---
## ШАГ 1 — Git Init + Skeleton ## Выполненные шаги (1-10)
**Цель:** репозиторий создан, пустая структура, "hello world" билдится. ### ШАГ 1 — Git Init + Skeleton
- go module `verstak`, структура cmd/internal/migrations
**Acceptance:** - CLI `verstak --version`
- `go build ./...` проходит
- `go test ./...` проходит
- `verstak --version` выводит версию
- Повторный init безопасен
**Действия:**
- git init, .gitignore (Go, Wails)
- `go mod init verstak`
- Структура: `cmd/verstak/`, `internal/core/`, `migrations/`
- `cmd/verstak/main.go`: --version flag
- README.md - README.md
**Commit:** `step 1: skeleton` ### ШАГ 2 — Init + SQLite
- storage.go: DB wrapper, migration runner
- vault.go: Init() создаёт .verstak/ + index.db
- config.go: YAML config
### ШАГ 3 — Nodes Repository
- Node struct + CRUD (Create, Get, ListChildren, ListRoots, UpdateTitle, Move, SoftDelete)
- Meta KV + tests
### ШАГ 4 — Files
- FileService: AddExternal, CopyIntoVault, Get, ListByNode, MarkMissing, DeleteToTrash, Open
- file_test.go: 5 tests
### ШАГ 5 — Notes
- NoteService: Create, Read, Save (с backup), Delete
- note_test.go: 3 tests
### ШАГ 6 — GUI (browser prototype)
- Go HTTP SPA на случайном порту
- Sidebar tree + разделы (Сегодня, Неразобранное, Клиенты, Проекты...)
- Dashboard дела + вкладки (Обзор, Заметки, Файлы, Действия, Журнал, Активность)
- Модальное окно "+ Добавить" с выбором типа
- Поиск по корневым нодам
- **Это legacy prototype — не развивать как основной GUI**
### ШАГ 7 — Actions + Worklog
- ActionService: Create, Get, ListByNode, Delete, Run (open_url/file/folder, run_command)
- WorklogService: Add, Update, ListByNode, Delete, SumMinutes, Report
- CLI: `action add/list/run/delete`, `log add/list/report`
- GUI вкладки с кнопками действий и журналом работ
### ШАГ 8 — FTS5 Search
- SearchService: Index, Remove, Rebuild, Search (FTS5 MATCH)
- FTS5 virtual table создаётся лениво (работает с/без FTS5)
- Fallback на LIKE по заголовкам нод
- CLI: `verstak index rebuild`
- GUI search bar
### ШАГ 9 — Section assignment
- Колонка `section` в nodes (clients/projects/recipes/documents/archive/inbox)
- Фильтрация разделов по `?section=` в API
- Root-ноды без section → inbox
### ШАГ 10 — Plugin Manager
- Discovery: `.verstak/plugins/<name>/plugin.json`
- Enable/disable, templates → pre-filled node trees
- Built-in шаблон "Клиент" (Overview + Документы/Переписка/Скриншоты)
- Template selector в модалке создания дела
- POST /api/nodes/from-template
- CLI: `plugin list/enable/disable/templates`
- Lua runtime — stub (placeholder)
--- ---
## ШАГ 2 — Init + SQLite + First Migration ## Текущий этап: ШАГ 11 — Wails Desktop GUI
**Цель:** `verstak init --vault ./test` создаёт vault с index.db. **Целевой commit:** `gui/wails-file-workflow`
**Acceptance:** Архитектура:
- `go test ./...` проходит
- init создаёт `.verstak/index.db`
- повторный init безопасен
**Действия:** ```
- migration runner (cmd + SQL migrations/) ┌─────────────────────────────────────────────────┐
- миграция 001_init.sql (таблица nodes) │ Frontend (Wails) │
- `_ "github.com/mattn/go-sqlite3"` или modernc driver │ frontend/src/ │
- CLI `init`: vault dir + `.verstak/` + `index.db` │ App.svelte │
│ components/Sidebar, TopBar, CaseView, ... │
│ stores/selection, nodes, files, ui │
│ styles/theme.css │
└──────────────────┬──────────────────────────────┘
│ Wails bindings
┌──────────────────▼──────────────────────────────┐
│ Go Core (internal/core/) │
│ nodes, vault, storage, notes, files, │
│ actions, worklog, search, plugins │
└──────────────────┬──────────────────────────────┘
┌──────────────────▼──────────────────────────────┐
│ Vault filesystem + SQLite │
└─────────────────────────────────────────────────┘
```
**Commit:** `step 2: init + sqlite + first migration` ### Действия
1. Создать Wails app skeleton (`wails init`)
2. Структура frontend/ — Svelte/Vue/vanilla TS
3. Backend bindings — методы Wails над core services
4. Перенести текущий UI shell из inline HTML в frontend
5. Оставить текущий `internal/gui/` как legacy (не удалять, но не развивать)
### Backend bindings (минимум)
```go
// Nodes
ListSections() ([]SectionDTO, error)
ListNodes(section string) ([]NodeDTO, error)
GetNodeDetail(nodeID string) (NodeDetailDTO, error)
CreateNode(parentID, section, typ, title string) (NodeDTO, error)
FromTemplate(parentID, section, typ, title, template string) (NodeDTO, error)
DeleteNode(id string) error
MoveNode(id, parentID string) error
// Notes
CreateNote(parentID, title string) (NodeDTO, error)
ReadNote(noteID string) (string, error)
SaveNote(noteID, content string) error
// Files
ListFiles(nodeID string) ([]FileDTO, error)
AddPathCopy(nodeID, sourcePath string) ([]NodeDTO, error) // файл или папка
AddPathLink(nodeID, sourcePath string) ([]NodeDTO, error)
DeleteFileOrFolder(id string) error
OpenFile(id string) error
OpenFolder(id string) error
PickFile() (string, error)
PickDirectory() (string, error)
// Actions/Worklog/Search
ListActions(nodeID string) ([]ActionDTO, error)
RunAction(id string) error
CreateWorklog(nodeID, summary string, minutes int) (WorklogDTO, error)
Search(query string) ([]SearchResultDTO, error)
```
--- ---
## ШАГ 3 — Nodes Repository + CRUD + CLI Node ## ШАГ 12 — Files/Folders full workflow
**Цель:** можно создать/прочитать/переместить/удалить дело через CLI. ### Core service extensions
**Acceptance:** Расширить `files.Service`:
- nodes + node_meta таблицы
- NodeRepository: Create, Get, ListChildren, UpdateTitle, Move, SoftDelete
- CLI: `node create`, `node list`, `node move`, `node delete`
- unit tests проходят
**Действия:** ```go
- Полная схема nodes (id, parent_id, type, title, slug, path, sort_order, created_at, updated_at, deleted_at, revision, device_id) AddPathCopy(nodeID string, sourcePath string) ([]Node, error)
- node_meta (node_id, key, value) AddPathLink(nodeID string, sourcePath string) ([]Node, error)
- Node struct + Repository ```
- UUID вместо auto-increment
- Soft delete (deleted_at)
- безопасный slug для path
- Tests: in-memory SQLite
**Commit:** `step 3: nodes repository + CRUD` Логика:
- `os.Stat(sourcePath)` → если директория → рекурсивный обход
- Каждый файл → File node + file record
- Каждая папка → Folder node
- Структура сохраняется через parent_id
### Folder model
Папка = node type `folder` (не file record с mime=directory).
При импорте `romashka-docs/`:
```
Folder node: romashka-docs (type=folder)
File node: dogovor.docx (type=file)
Folder node: screenshots (type=folder)
File node: error.png (type=file)
```
### Name conflict resolution
Если в target уже есть `docs`:
```
docs
docs (2)
docs (3)
```
### Safety checks
При добавлении папки показать summary:
- количество файлов/папок
- общий размер
- предупреждение если > 1000 файлов, > 1 GB, содержит `.git`/`node_modules`/`.cache`
### Trash
- Soft delete node + children
- Vault files → `.verstak/trash`
- External files — только удалить связь
- Не `rm -rf`
### Tests
1. Copy single file → vault, record created, source intact
2. Link single file → no copy, external path saved
3. Copy folder → tree created, files in vault
4. Link folder → node created, no content copied
5. Delete vault file → soft-deleted, file in trash
6. Delete vault folder → children soft-deleted
7. Name conflict → no overwrite, safe suffix
8. Open file → mocked opener (no real app launch)
--- ---
## ШАГ 4 — Vault Files: Trash + File Service + CLI File ## ШАГ 13 — Drag-and-drop
**Цель:** можно добавить файл в дело, открыть системным приложением, удалить в trash. ### External D&D
**Acceptance:** Drop target: активное дело / вкладка Файлы / Неразобранное.
- `.verstak/trash/` создаётся при init
- copy file into vault работает
- open with system app работает
- delete-to-trash работает
- тесты проходят
**Действия:** После drop → диалог:
- Таблица files (id, node_id, filename, path, storage_mode, size, sha256, mime, ...) ```
- VaultService: CopyFile, LinkExternal, OpenFile, DeleteToTrash, RestoreFromTrash Добавить в "ООО Ромашка / Сайт"
- CLI: `file add`, `file list`, `file open`, `file trash` Файлов: 3, Папок: 1, 240 MB
[Скопировать] [Переместить] [Привязать] [Отмена]
**Commit:** `step 4: vault files + trash + CLI` ```
--- ---
## ШАГ 5 — Markdown Notes: Create/Read/Save + CLI Note ## ШАГ 14 — MVP stabilization
**Цель:** можно создать заметку, писать в неё, читать обратно. - Smoke tests базовых сценариев
- Проверка: дело → заметка → файл → папка → trash → перезапуск
**Acceptance:** - go test ./... pass
- type "note" для nodes - Обновление документации
- создать .md файл в vault - Остановка перед следующими фичами
- save делает backup старой версии
- тесты проходят
**Действия:**
- Таблица notes (node_id, file_id, format, original_format, encrypted)
- NoteService: CreateNote, ReadNote, SaveNote (с backup)
- CLI: `note create`, `note read`, `note write`
- Backup старой версии перед перезаписью
**Commit:** `step 5: markdown notes`
---
## ШАГ 6 — Wails GUI MVP
**Цель:** GUI запускается, видно дерево дел, можно создать дело и заметку.
**Acceptance:**
- sidebar tree показывает дела
- create node работает
- markdown textarea editor с save
- file list + add file + open file
- главный пользовательский поток работает
**Действия:**
- Wails app init (Go backend + Svelte/Vue)
- Backend bindings: NodeService, VaultService, NoteService
- Frontend: sidebar tree, main panel, modals, markdown editor
- Поток: дело → заметка → файл → открыть файл
**Commit:** `step 6: Wails GUI MVP`
---
## ШАГ 7 — Actions
**Цель:** можно создать кнопку "Открыть сайт", нажать, сайт открылся.
**Acceptance:**
- open_url, open_file, open_folder, run_command
- confirm_required диалог
- action log
- GUI вкладка "Действия"
**Действия:**
- Таблица actions
- ActionService: Run с confirm, exec.Command БЕЗ shell, args массивом
- CLI: `action add`, `action list`, `action run`
- GUI: вкладка с кнопками
**Commit:** `step 7: actions`
---
## ШАГ 8 — Worklog
**Цель:** можно записать "3ч обновил витрину", скопировать отчёт.
**Acceptance:**
- add/edit/delete entry
- approximate minutes + billable flag
- copy report копирует в буфер
- GUI вкладка "Журнал"
**Действия:**
- Таблица worklog_entries
- WorklogService: Add, Edit, Delete, CopyReport
- CLI: `worklog add`, `worklog list`, `worklog report`
- GUI: вкладка журнал + кнопка copy report
**Commit:** `step 8: worklog`
---
## ШАГ 9 — FTS5 Search
**Цель:** можно найти "витрину" по заметкам, файлам, журналу.
**Acceptance:**
- `verstak index rebuild` перестраивает индекс
- поиск по node titles, note content, filenames, worklog summaries
- GUI search bar + результаты с type/path
**Действия:**
- Таблица search_index (FTS5): node_id, title, content, path, tags, type
- Триггеры для автоматического обновления или manual rebuild
- SearchService: RebuildIndex, Search(query)
- CLI: `index rebuild`
- GUI: search bar в header
**Commit:** `step 9: FTS5 search`
---
## ШАГ 10 — Система плагинов (Lua + шаблоны дел)
**Цель:** можно положить Lua-скрипт в `.verstak/plugins/` — и он работает.
Без перекомпиляции программы.
**Acceptance:**
- `.verstak/plugins/<name>/plugin.json` — мета
- `main.lua` — загрузка через gopher-lua
- `on_init`, `on_vault_open`, `on_node_create` хуки
- `verstak.node.register_type()` — новые типы дел
- `verstak.http.route()` — API для GUI
- шаблоны дела (JSON) → предзаполненное дерево
- песочница: нет io/os.execute, только API
- CLI: `verstak plugin list / install / enable`
- DokuWiki импортер — пример плагина в `contrib/plugins/importer-dokuwiki/`
**Действия:**
- `internal/core/plugins/manager.go` — сканирование, загрузка, валидация
- Lua runtime (gopher-lua) с песочницей
- Plugin API: node, config, activity, http, ui, vault
- Миграции плагинов (SQL)
- Реестр типов дел → GUI рендерит разные карточки
- CLI: plugin list/install/enable
- Базовый шаблон дела (client.json)
- Пример плагина: DokuWiki importerв `contrib/`
**Commit:** `step 10: plugins system`
---
## ШАГ 11 — Sync Server Skeleton
**Цель:** verstak-server отвечает на /health, /sync/push, /sync/pull, /blobs.
**Acceptance:**
- HTTP server на отдельном порту
- API key auth
- blob storage by sha256
- GET /health, POST /sync/push, POST /sync/pull, POST /blobs/upload, GET /blobs/{sha256}
**Действия:**
- `cmd/verstak-server/main.go`
- SQLite server db
- Push/pull operations endpoints
- Blob upload/download with sha256 naming
**Commit:** `step 11: sync server skeleton`
---
## ШАГ 12 — Sync Client MVP
**Цель:** `verstak sync` отправляет локальные операции на сервер и получает обратно.
**Acceptance:**
- sync_ops таблица
- операции создаются при каждом изменении
- push local ops + pull remote ops
- upload/download blobs
- conflict copy при неуверенности
**Действия:**
- Таблица sync_ops (опционально добавить триггеры в repository)
- SyncClient: Push, Pull, UploadBlob, DownloadBlob
- CLI: `verstak sync`
- Server URL + API key в .verstak/config
**Commit:** `step 12: sync client MVP`
---
## ШАГ 13 — Activity + File Scanner/Watcher
**Цель:** фиксируется открытие/редактирование, scanner видит новые файлы.
**Acceptance:**
- activity_events таблица
- scanner сравнивает реальность с SQLite
- watcher (fsnotify) ускоряет обнаружение
- экран "Активность" с группировкой по делу
- можно создать worklog из events
**Действия:**
- Таблица activity_events
- Запись событий из nodes/notes/files/actions
- Snapshot scanner (источник правды)
- fsnotify watcher (ускоритель)
- CLI: `scan`, `activity list`
- GUI: экран "Активность"
**Commit:** `step 13: activity + scanner/watcher`
---
## ШАГ 14 — TUI MVP (Bubble Tea)
**Цель:** быстрый поиск дела, добавление worklog, запуск action.
**Acceptance:**
- fuzzy search tree
- open node
- add worklog
- run action
- sync now
**Действия:**
- `cmd/verstak-tui/main.go` с Bubble Tea
- Модели: search, node view, worklog form, action runner
- Не повторяет весь GUI — только быстрые действия
**Commit:** `step 14: TUI MVP`
---
## ШАГ 15 — Integrity Check + Repair + Vault Restore
**Цель:** `verstak vault check` находит проблемы, repair чинит, restore восстанавливает.
**Acceptance:**
- check: missing files, orphan files, SQLite references, hash mismatch
- repair: устраняет найденные проблемы
- restore с сервера восстанавливает vault
**Действия:**
- CLI: `vault check` — сканирует и отчитывается
- CLI: `vault repair` — чинит найденное
- CLI: `restore --server <url> --api-key <key> --target <path>`
**Commit:** `step 15: integrity + restore`
---
## ШАГ 16 — Система плагинов (Lua + шаблоны дел)
**Цель:** можно положить Lua-скрипт в `.verstak/plugins/` — и он работает.
**Acceptance:**
- `.verstak/plugins/<name>/plugin.json` — мета
- `main.lua` — загрузка через gopher-lua
- `on_init`, `on_vault_open`, `on_node_create` хуки
- `verstak.node.register_type()` — новые типы дел
- `verstak.http.route()` — API для GUI
- шаблоны дела (JSON) → предзаполненное дерево
- CLI: `verstak plugin list / install / enable`
**Действия:**
- `internal/core/plugins/manager.go` — сканирование, загрузка, валидация
- Lua runtime (gopher-lua) с песочницей
- Plugin API: node, config, activity, http, ui, vault
- Миграции плагинов (SQL)
- Реестр типов дел → GUI рендерит разные карточки
- CLI: plugin list/install/enable
- Базовый шаблон дела (client.json)
**Commit:** `step 10: plugins system`
--- ---
@ -382,10 +269,19 @@ verstak/
cmd/ cmd/
verstak/ # CLI verstak/ # CLI
verstak-gui/ # Wails GUI verstak-gui/ # Wails GUI main
verstak-tui/ # Bubble Tea TUI verstak-tui/ # Bubble Tea TUI
verstak-server/ # Sync server verstak-server/ # Sync server
frontend/ # Wails frontend
package.json
wails.json
src/
App.svelte
components/
stores/
styles/
internal/ internal/
core/ core/
nodes/ nodes/
@ -400,9 +296,11 @@ verstak/
sync/ sync/
security/ security/
config/ config/
plugins/ plugins/ # manager, lua (stub)
frontend/ # Wails frontend (Svelte/Vue) contrib/
plugins/
importer-dokuwiki/
migrations/ migrations/
001_init.sql 001_init.sql
@ -411,15 +309,11 @@ verstak/
004_add_notes.sql 004_add_notes.sql
005_add_actions.sql 005_add_actions.sql
006_add_worklog.sql 006_add_worklog.sql
007_add_activity.sql
008_add_fts.sql
009_add_sync.sql
``` ```
## RAID (Risks, Assumptions, Issues, Dependencies) ## RAID
- **Критично:** Wails v3 может быть нестабилен — проверить перед шагом 6 - **Критично:** Wails требует Node.js для frontend-сборки
- **Критично:** go-sqlite3 нужен cgo; modernc — чистый Go, выбрать до шага 2 - **Критично:** go-sqlite3 + cgo; gcc уже установлен
- **Зависимость:** Шаги 12 (sync client) зависят от 11 (server) - **Зависимость:** Steps 15+ ждут завершения step 14
- **Зависимость:** Шаг 6 (GUI) лучше откладывать до стабильности core - **Риск:** Wails v3 может быть нестабилен — проверить перед шагом 11
- **Риск:** Svelte/Vue фронтенд потребует node/npm — подготовить

View File

@ -0,0 +1,93 @@
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,9 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
Object.freeze($Create.Events);

View File

@ -0,0 +1,2 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

14
frontend/index.html Normal file
View File

@ -0,0 +1,14 @@
<!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>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

1155
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
frontend/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"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"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"svelte": "^5.46.4",
"vite": "^8.0.5"
}
}

Binary file not shown.

157
frontend/public/style.css Normal file
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

BIN
frontend/public/wails.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

10
frontend/src/App.svelte Normal file
View File

@ -0,0 +1,10 @@
<script>
// Stub — will be replaced with actual UI
</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>
</div>

4
frontend/src/main.js Normal file
View File

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

2
frontend/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

25
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
/**
* This file tells your IDE where the root of your JavaScript project is, and sets some
* options that it can use to provide autocompletion and other features.
*/
{
"compilerOptions": {
"allowJs": true,
"moduleResolution": "bundler",
/**
* The target and module can be set to ESNext to allow writing modern JavaScript,
* and Vite will compile down to the level of "build.target" specified in the vite config file.
* Builds will error if you use a feature that cannot be compiled down to the target level.
*/
"target": "ESNext",
"module": "ESNext",
"resolveJsonModule": true,
/**
* Enable checkJs if you'd like type checking in `.svelte` and `.js` files.
*/
"checkJs": false,
"strict": true,
"skipLibCheck": true,
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "bindings"]
}

13
frontend/vite.config.js Normal file
View File

@ -0,0 +1,13 @@
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,
strictPort: true,
},
plugins: [svelte(), wails("./bindings")],
});

42
go.mod
View File

@ -1,8 +1,48 @@
module verstak module verstak
go 1.22 go 1.25.0
require ( require (
dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/lmittmann/tint v1.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.44 // indirect github.com/mattn/go-sqlite3 v1.14.44 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/wailsapp/wails/v3 v3.0.0-alpha.96 // indirect
github.com/wailsapp/wails/webview2 v1.0.24 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

111
go.sum
View File

@ -1,5 +1,116 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/wailsapp/wails/v3 v3.0.0-alpha.96 h1:FmZdo4LiUkFUvz8rZO8f4nGLm5af9J+D3sr3Flset2g=
github.com/wailsapp/wails/v3 v3.0.0-alpha.96/go.mod h1:p1MUZwFPPQyx81cgejDvKIwU1+x/hndR+4z1uG5bw6s=
github.com/wailsapp/wails/webview2 v1.0.24 h1:uULnjCSaRfMlU84mS3kjLgPsRosEOIusVK1nFOHZHzs=
github.com/wailsapp/wails/webview2 v1.0.24/go.mod h1:sdf+s0nAdxlzVWf9SCxC15XaxnQPJeY+uU1Ucn3jHQM=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

73
guimain.go Normal file
View File

@ -0,0 +1,73 @@
// This file is only compiled with -tags gui.
//go:build gui
// +build gui
package main
import (
"embed"
_ "embed"
"log"
"os"
"path/filepath"
"verstak/internal/core/storage"
"verstak/internal/core/nodes"
"verstak/internal/core/files"
"verstak/internal/core/notes"
"verstak/internal/core/actions"
"verstak/internal/core/worklog"
"verstak/internal/core/search"
"verstak/internal/core/plugins"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
vaultPath := "."
if len(os.Args) > 1 {
vaultPath = os.Args[1]
}
abs, err := filepath.Abs(vaultPath)
if err != nil {
log.Fatal(err)
}
dbPath := filepath.Join(abs, ".verstak", "index.db")
db, err := storage.Open(dbPath)
if err != nil {
log.Fatalf("Open vault: %v", err)
}
defer db.Close()
// Initialize core services (registered for Wails bindings).
_ = nodes.NewRepository(db)
_ = files.NewService(db, abs)
_ = notes.NewService(db, abs, nil, nil)
_ = actions.NewService(db)
_ = worklog.NewService(db)
_ = search.NewService(db)
plugins.NewManager(abs).Discover()
app := application.New(application.Options{
Name: "verstak",
Description: "Verstak — local-first working vault",
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
})
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Верстак",
Width: 1200,
Height: 800,
})
if err := app.Run(); err != nil {
log.Fatal(err)
}
}

Binary file not shown.

88
wails_service.go Normal file
View File

@ -0,0 +1,88 @@
package main
import (
"context"
"encoding/json"
"fmt"
"verstak/internal/core/nodes"
)
// WailsService exposes core methods to the Wails frontend.
type WailsService struct{}
// ListRootNodes returns root-level nodes.
func (s *WailsService) ListRootNodes(ctx context.Context) string {
// This is a stub — actual implementation will use injected DB
return "[]"
}
// ListChildren returns children of a node.
func (s *WailsService) ListChildren(ctx context.Context, parentID string) string {
return "[]"
}
// CreateNode creates a new node.
func (s *WailsService) CreateNode(ctx context.Context, parentID, nodeType, title, section string) string {
return "{}"
}
// ListFiles returns files for a node.
func (s *WailsService) ListFiles(ctx context.Context, nodeID string) string {
return "[]"
}
// ListActions returns actions for a node.
func (s *WailsService) ListActions(ctx context.Context, nodeID string) string {
return "[]"
}
// ListWorklog returns worklog entries for a node.
func (s *WailsService) ListWorklog(ctx context.Context, nodeID string) string {
return "[]"
}
// Search performs a search query.
func (s *WailsService) Search(ctx context.Context, query string) string {
return "[]"
}
// ListTemplates returns available templates.
func (s *WailsService) ListTemplates(ctx context.Context) string {
return "[]"
}
// ListSections returns available sections.
func (s *WailsService) ListSections(ctx context.Context) string {
sections := []map[string]string{
{"id": "today", "label": "Сегодня"},
{"id": "inbox", "label": "Неразобранное"},
{"id": "clients", "label": "Клиенты"},
{"id": "projects", "label": "Проекты"},
{"id": "recipes", "label": "Рецепты"},
{"id": "documents", "label": "Документы"},
{"id": "archive", "label": "Архив"},
}
data, _ := json.Marshal(sections)
return string(data)
}
// GetCurrentSelection returns current selection state.
func (s *WailsService) GetCurrentSelection(ctx context.Context) string {
sel := map[string]string{"kind": "section", "section": "today"}
data, _ := json.Marshal(sel)
return string(data)
}
// OpenFile opens a file with the system application.
func (s *WailsService) OpenFile(ctx context.Context, fileID string) string {
return fmt.Sprintf("opened file %s", fileID)
}
// OpenFolder opens a folder in the system file manager.
func (s *WailsService) OpenFolder(ctx context.Context, nodeID string) string {
return fmt.Sprintf("opened folder %s", nodeID)
}
// Silence unused import.
var _ = nodes.TypeCase