Compare commits
11 Commits
e8e473b357
...
8c0ea632d7
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c0ea632d7 | |||
| db23ee42fc | |||
| 0784546e50 | |||
| 2fe3e15659 | |||
| ed02993350 | |||
| e4e5ff2211 | |||
| d69a573a33 | |||
| 6cc1bc6834 | |||
| 894fd17d1a | |||
| 888f20fdab | |||
| 7318600e20 |
23
AGENTS.md
23
AGENTS.md
@@ -11,7 +11,7 @@ Guidelines for AI agents working in this Bun monorepo.
|
|||||||
- **Package Manager**: Bun — **NOT npm / yarn / pnpm**
|
- **Package Manager**: Bun — **NOT npm / yarn / pnpm**
|
||||||
- **Apps**:
|
- **Apps**:
|
||||||
- `apps/server` - TanStack Start fullstack web app (see `apps/server/AGENTS.md`)
|
- `apps/server` - TanStack Start fullstack web app (see `apps/server/AGENTS.md`)
|
||||||
- `apps/desktop` - Electrobun desktop shell, loads server in native window (see `apps/desktop/AGENTS.md`)
|
- `apps/desktop` - Electron desktop shell, sidecar server pattern (see `apps/desktop/AGENTS.md`)
|
||||||
- **Packages**: `packages/utils`, `packages/tsconfig` (shared configs)
|
- **Packages**: `packages/utils`, `packages/tsconfig` (shared configs)
|
||||||
|
|
||||||
## Build / Lint / Test Commands
|
## Build / Lint / Test Commands
|
||||||
@@ -41,9 +41,11 @@ bun db:studio # Open Drizzle Studio
|
|||||||
|
|
||||||
### Desktop App (`apps/desktop`)
|
### Desktop App (`apps/desktop`)
|
||||||
```bash
|
```bash
|
||||||
bun dev # Start Electrobun dev mode (requires server dev running)
|
bun dev # electron-vite dev mode (requires server dev running)
|
||||||
bun build # Build canary release
|
bun build # electron-vite build (main + preload)
|
||||||
bun build:stable # Build stable release
|
bun build:win # Build + package for Windows
|
||||||
|
bun build:mac # Build + package for macOS
|
||||||
|
bun build:linux # Build + package for Linux
|
||||||
bun fix # Biome auto-fix
|
bun fix # Biome auto-fix
|
||||||
bun typecheck # TypeScript check
|
bun typecheck # TypeScript check
|
||||||
```
|
```
|
||||||
@@ -158,11 +160,14 @@ export const myTable = pgTable('my_table', {
|
|||||||
│ │ │ ├── api/ # ORPC contracts, routers, middlewares
|
│ │ │ ├── api/ # ORPC contracts, routers, middlewares
|
||||||
│ │ │ └── db/ # Drizzle schema
|
│ │ │ └── db/ # Drizzle schema
|
||||||
│ │ └── AGENTS.md
|
│ │ └── AGENTS.md
|
||||||
│ └── desktop/ # Electrobun desktop shell
|
│ └── desktop/ # Electron desktop shell
|
||||||
│ ├── src/
|
│ ├── src/
|
||||||
│ │ └── bun/
|
│ │ ├── main/
|
||||||
│ │ └── index.ts # Main process entry
|
│ │ │ └── index.ts # Main process entry
|
||||||
│ ├── electrobun.config.ts # Electrobun configuration
|
│ │ └── preload/
|
||||||
|
│ │ └── index.ts # Preload script
|
||||||
|
│ ├── electron.vite.config.ts
|
||||||
|
│ ├── electron-builder.yml # Packaging config
|
||||||
│ └── AGENTS.md
|
│ └── AGENTS.md
|
||||||
├── packages/
|
├── packages/
|
||||||
│ ├── tsconfig/ # Shared TS configs
|
│ ├── tsconfig/ # Shared TS configs
|
||||||
@@ -175,4 +180,4 @@ export const myTable = pgTable('my_table', {
|
|||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- `apps/server/AGENTS.md` - Detailed TanStack Start / ORPC patterns
|
- `apps/server/AGENTS.md` - Detailed TanStack Start / ORPC patterns
|
||||||
- `apps/desktop/AGENTS.md` - Electrobun desktop development guide
|
- `apps/desktop/AGENTS.md` - Electron desktop development guide
|
||||||
|
|||||||
11
apps/desktop/.gitignore
vendored
11
apps/desktop/.gitignore
vendored
@@ -1,4 +1,7 @@
|
|||||||
# WebUI native libraries (auto-downloaded)
|
# Electron-vite build output
|
||||||
*.dll
|
out/
|
||||||
*.so
|
dist/
|
||||||
*.dylib
|
|
||||||
|
# Sidecar binary (copied from apps/server build)
|
||||||
|
resources/server
|
||||||
|
resources/server.exe
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
# AGENTS.md - Desktop App Guidelines
|
# AGENTS.md - Desktop App Guidelines
|
||||||
|
|
||||||
Thin WebUI shell hosting the fullstack server app.
|
Thin Electron shell hosting the fullstack server app.
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
> **⚠️ This project uses Bun — NOT Node.js / npm. All commands use `bun`. Never use `npm`, `npx`, or `node`.**
|
> **⚠️ This project uses Bun as the package manager. Runtime is Electron (Node.js). Never use `npm`, `npx`, `yarn`, or `pnpm`.**
|
||||||
|
|
||||||
- **Type**: WebUI desktop shell
|
- **Type**: Electron desktop shell
|
||||||
- **Design**: Server-driven desktop (thin native window hosting web app)
|
- **Design**: Server-driven desktop (thin native window hosting web app)
|
||||||
- **Runtime**: Bun (Main process) + System browser / WebView (via WebUI)
|
- **Runtime**: Electron (Main/Renderer) + Sidecar server binary (Bun-compiled)
|
||||||
- **Window Library**: [webui-dev/webui](https://github.com/webui-dev/webui) — opens installed browser or native WebView as GUI
|
- **Build Tool**: electron-vite (Vite-based, handles main + preload builds)
|
||||||
- **Build**: Turborepo
|
- **Packager**: electron-builder (installers, signing, auto-update)
|
||||||
|
- **Orchestration**: Turborepo
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- **Server-driven design**: The desktop app is a "thin" native shell. It does not contain UI or business logic; it opens a browser window pointing to the `apps/server` TanStack Start application.
|
- **Server-driven design**: The desktop app is a "thin" native shell. It does not contain UI or business logic; it opens a BrowserWindow pointing to the `apps/server` TanStack Start application.
|
||||||
- **Dev mode**: Waits for the Vite dev server at `localhost:3000`, then opens a WebUI window. Requires `apps/server` to be running separately.
|
- **Dev mode**: Opens a BrowserWindow pointing to `localhost:3000`. Requires `apps/server` to be running separately (Turbo handles this).
|
||||||
- **WebUI**: Uses `@webui-dev/bun-webui` (FFI-based C library binding). Auto-detects and opens the best available browser in a private profile, or uses the system WebView.
|
- **Production mode**: Spawns a compiled server binary (from `resources/`) as a sidecar process, waits for readiness, then loads its URL.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun dev # Open WebUI window (requires server dev running)
|
bun dev # electron-vite dev (requires server dev running)
|
||||||
|
bun build # electron-vite build (main + preload)
|
||||||
|
bun build:win # Build + package for Windows
|
||||||
|
bun build:mac # Build + package for macOS
|
||||||
|
bun build:linux # Build + package for Linux
|
||||||
bun fix # Biome auto-fix
|
bun fix # Biome auto-fix
|
||||||
bun typecheck # TypeScript check
|
bun typecheck # TypeScript check
|
||||||
```
|
```
|
||||||
@@ -31,27 +36,40 @@ bun typecheck # TypeScript check
|
|||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── src/
|
├── src/
|
||||||
│ └── bun/
|
│ ├── main/
|
||||||
│ └── index.ts # Main process entry (server readiness check + WebUI window)
|
│ │ └── index.ts # Main process (server lifecycle + BrowserWindow)
|
||||||
├── package.json # Scripts and dependencies
|
│ └── preload/
|
||||||
├── turbo.json # Dev pipeline (depends on server dev)
|
│ └── index.ts # Preload script (security isolation)
|
||||||
└── AGENTS.md # Desktop guidelines (this file)
|
├── resources/ # Sidecar binaries (gitignored, copied from server build)
|
||||||
|
├── out/ # electron-vite build output (gitignored)
|
||||||
|
├── electron.vite.config.ts
|
||||||
|
├── electron-builder.yml # Packaging configuration
|
||||||
|
├── package.json
|
||||||
|
├── turbo.json
|
||||||
|
└── AGENTS.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
1. **Start server**: In `apps/server`, run `bun dev`.
|
1. **Start server**: `bun dev` in `apps/server` (or use root `bun dev` via Turbo).
|
||||||
2. **Start desktop**: In `apps/desktop`, run `bun dev`.
|
2. **Start desktop**: `bun dev` in `apps/desktop`.
|
||||||
3. **Connection**: The desktop app polls `localhost:3000` until responsive, then opens the browser window.
|
3. **Connection**: Main process polls `localhost:3000` until responsive, then opens BrowserWindow.
|
||||||
|
|
||||||
|
## Production Build Workflow
|
||||||
|
|
||||||
|
1. **Build server**: `bun build` in `apps/server` → `.output/`
|
||||||
|
2. **Compile server**: `bun compile` in `apps/server` → `out/server-{platform}`
|
||||||
|
3. **Copy binary**: Copy the platform-appropriate binary to `apps/desktop/resources/server`
|
||||||
|
4. **Package desktop**: `bun build:linux` (or `:mac` / `:win`) in `apps/desktop`
|
||||||
|
|
||||||
## Critical Rules
|
## Critical Rules
|
||||||
|
|
||||||
**DO:**
|
**DO:**
|
||||||
- Use arrow functions for all utility functions.
|
- Use arrow functions for all utility functions.
|
||||||
- Ensure `apps/server` dev server is running before starting desktop.
|
- Keep the desktop app as a thin shell — no UI or business logic.
|
||||||
- Use `catalog:` for all dependency versions in `package.json`.
|
- Use `catalog:` for all dependency versions in `package.json`.
|
||||||
|
|
||||||
**DON'T:**
|
**DON'T:**
|
||||||
- Use `npm`, `npx`, `node`, `yarn`, or `pnpm`. Always use `bun`.
|
- Use `npm`, `npx`, `yarn`, or `pnpm`. Use `bun` for package management.
|
||||||
- Include UI components or business logic in the desktop app (keep it thin).
|
- Include UI components or business logic in the desktop app.
|
||||||
- Use `as any` or `@ts-ignore`.
|
- Use `as any` or `@ts-ignore`.
|
||||||
|
|||||||
9
apps/desktop/biome.json
Normal file
9
apps/desktop/biome.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
|
"extends": "//",
|
||||||
|
"css": {
|
||||||
|
"parser": {
|
||||||
|
"tailwindDirectives": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
apps/desktop/electron-builder.yml
Normal file
41
apps/desktop/electron-builder.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
appId: com.furtherverse.app
|
||||||
|
productName: Furtherverse
|
||||||
|
directories:
|
||||||
|
buildResources: build
|
||||||
|
files:
|
||||||
|
- '!**/.vscode/*'
|
||||||
|
- '!src/*'
|
||||||
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
|
- '!{.env,.env.*,bun.lock}'
|
||||||
|
- '!{tsconfig.json,tsconfig.node.json}'
|
||||||
|
- '!{AGENTS.md,README.md,CHANGELOG.md}'
|
||||||
|
asarUnpack:
|
||||||
|
- resources/**
|
||||||
|
win:
|
||||||
|
executableName: Furtherverse
|
||||||
|
extraResources:
|
||||||
|
- from: ../server/out/server-windows-x64.exe
|
||||||
|
to: server.exe
|
||||||
|
nsis:
|
||||||
|
artifactName: ${productName}-${version}-setup.${ext}
|
||||||
|
shortcutName: ${productName}
|
||||||
|
uninstallDisplayName: ${productName}
|
||||||
|
createDesktopShortcut: always
|
||||||
|
mac:
|
||||||
|
category: public.app-category.productivity
|
||||||
|
extraResources:
|
||||||
|
- from: ../server/out/server-darwin-arm64
|
||||||
|
to: server
|
||||||
|
dmg:
|
||||||
|
artifactName: ${productName}-${version}.${ext}
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
maintainer: furtherverse.com
|
||||||
|
category: Utility
|
||||||
|
extraResources:
|
||||||
|
- from: ../server/out/server-linux-x64
|
||||||
|
to: server
|
||||||
|
appImage:
|
||||||
|
artifactName: ${productName}-${version}.${ext}
|
||||||
|
npmRebuild: false
|
||||||
10
apps/desktop/electron.vite.config.ts
Normal file
10
apps/desktop/electron.vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import { defineConfig } from 'electron-vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {},
|
||||||
|
preload: {},
|
||||||
|
renderer: {
|
||||||
|
plugins: [tailwindcss()],
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -2,17 +2,27 @@
|
|||||||
"name": "@furtherverse/desktop",
|
"name": "@furtherverse/desktop",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"description": "Furtherverse desktop app",
|
||||||
|
"homepage": "https://furtherverse.com",
|
||||||
|
"author": "Furtherverse",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "out/main/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun src/bun/index.ts",
|
"build": "electron-vite build",
|
||||||
|
"build:linux": "electron-vite build && electron-builder --linux --config",
|
||||||
|
"build:mac": "electron-vite build && electron-builder --mac --config",
|
||||||
|
"build:win": "electron-vite build && electron-builder --win --config",
|
||||||
|
"dev": "electron-vite dev",
|
||||||
"fix": "biome check --write",
|
"fix": "biome check --write",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"@webui-dev/bun-webui": "catalog:"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@furtherverse/tsconfig": "workspace:*",
|
"@furtherverse/tsconfig": "workspace:*",
|
||||||
"@types/bun": "catalog:"
|
"@tailwindcss/vite": "catalog:",
|
||||||
|
"@types/node": "catalog:",
|
||||||
|
"electron": "catalog:",
|
||||||
|
"electron-builder": "catalog:",
|
||||||
|
"electron-vite": "catalog:",
|
||||||
|
"tailwindcss": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import { WebUI } from '@webui-dev/bun-webui'
|
|
||||||
|
|
||||||
const DEV_SERVER_URL = 'http://localhost:3000'
|
|
||||||
const SERVER_READY_TIMEOUT_MS = 10_000
|
|
||||||
|
|
||||||
const isServerReady = async (url: string): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, { method: 'HEAD' })
|
|
||||||
return response.ok
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const waitForServer = async (
|
|
||||||
url: string,
|
|
||||||
timeoutMs = SERVER_READY_TIMEOUT_MS,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const start = Date.now()
|
|
||||||
while (Date.now() - start < timeoutMs) {
|
|
||||||
if (await isServerReady(url)) return true
|
|
||||||
await Bun.sleep(100)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
console.log('Starting Furtherverse Desktop...')
|
|
||||||
|
|
||||||
console.log('Waiting for server at', DEV_SERVER_URL)
|
|
||||||
const ready = await waitForServer(DEV_SERVER_URL)
|
|
||||||
if (!ready) {
|
|
||||||
console.error(
|
|
||||||
'Dev server not responding. Run `bun dev` in apps/server first.',
|
|
||||||
)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Opening window at', DEV_SERVER_URL)
|
|
||||||
|
|
||||||
const win = new WebUI()
|
|
||||||
win.setSize(1200, 800)
|
|
||||||
win.setCenter()
|
|
||||||
await win.show(DEV_SERVER_URL)
|
|
||||||
|
|
||||||
console.log('Window opened. Waiting for close...')
|
|
||||||
await WebUI.wait()
|
|
||||||
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error('Failed to start:', error)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
143
apps/desktop/src/main/index.ts
Normal file
143
apps/desktop/src/main/index.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { spawn } from 'node:child_process'
|
||||||
|
import { createServer } from 'node:net'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { app, BrowserWindow, shell } from 'electron'
|
||||||
|
|
||||||
|
const DEV_SERVER_URL = 'http://localhost:3000'
|
||||||
|
|
||||||
|
let mainWindow: BrowserWindow | null = null
|
||||||
|
let serverProcess: ReturnType<typeof spawn> | null = null
|
||||||
|
|
||||||
|
const getAvailablePort = (): Promise<number> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const server = createServer()
|
||||||
|
server.listen(0, () => {
|
||||||
|
const addr = server.address()
|
||||||
|
if (!addr || typeof addr === 'string') {
|
||||||
|
server.close()
|
||||||
|
reject(new Error('Failed to resolve port'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.close(() => resolve(addr.port))
|
||||||
|
})
|
||||||
|
server.on('error', reject)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isServerReady = async (url: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { method: 'HEAD' })
|
||||||
|
return response.ok
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForServer = async (
|
||||||
|
url: string,
|
||||||
|
timeoutMs = 15_000,
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const start = Date.now()
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
if (await isServerReady(url)) return true
|
||||||
|
await new Promise<void>((resolve) => setTimeout(resolve, 200))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const spawnServer = (port: number): string => {
|
||||||
|
const binaryName = process.platform === 'win32' ? 'server.exe' : 'server'
|
||||||
|
const binaryPath = join(process.resourcesPath, binaryName)
|
||||||
|
|
||||||
|
serverProcess = spawn(binaryPath, [], {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PORT: String(port),
|
||||||
|
HOST: '127.0.0.1',
|
||||||
|
},
|
||||||
|
stdio: 'pipe',
|
||||||
|
})
|
||||||
|
|
||||||
|
serverProcess.stdout?.on('data', (data: Buffer) => {
|
||||||
|
console.log(`[server] ${data.toString().trim()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
serverProcess.stderr?.on('data', (data: Buffer) => {
|
||||||
|
console.error(`[server] ${data.toString().trim()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
serverProcess.on('error', (err) => {
|
||||||
|
console.error('Failed to start server:', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return `http://127.0.0.1:${port}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getServerUrl = async (): Promise<string> => {
|
||||||
|
if (!app.isPackaged) {
|
||||||
|
return DEV_SERVER_URL
|
||||||
|
}
|
||||||
|
const port = await getAvailablePort()
|
||||||
|
return spawnServer(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createWindow = async () => {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, '../preload/index.mjs'),
|
||||||
|
sandbox: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
|
shell.openExternal(url)
|
||||||
|
return { action: 'deny' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (process.env.ELECTRON_RENDERER_URL) {
|
||||||
|
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||||
|
} else {
|
||||||
|
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||||
|
}
|
||||||
|
mainWindow.show()
|
||||||
|
|
||||||
|
const serverUrl = await getServerUrl()
|
||||||
|
|
||||||
|
console.log(`Waiting for server at ${serverUrl}...`)
|
||||||
|
const ready = await waitForServer(serverUrl)
|
||||||
|
if (!ready) {
|
||||||
|
console.error(
|
||||||
|
app.isPackaged
|
||||||
|
? 'Server binary did not start in time.'
|
||||||
|
: 'Dev server not responding. Run `bun dev` in apps/server first.',
|
||||||
|
)
|
||||||
|
app.quit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Loading ${serverUrl}`)
|
||||||
|
mainWindow.loadURL(serverUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(createWindow)
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
if (serverProcess) {
|
||||||
|
serverProcess.kill()
|
||||||
|
serverProcess = null
|
||||||
|
}
|
||||||
|
})
|
||||||
1
apps/desktop/src/preload/index.ts
Normal file
1
apps/desktop/src/preload/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {}
|
||||||
18
apps/desktop/src/renderer/index.html
Normal file
18
apps/desktop/src/renderer/index.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Furtherverse</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body class="bg-white h-screen w-screen flex flex-col items-center justify-center overflow-hidden select-none cursor-default font-sans antialiased m-0">
|
||||||
|
<div class="flex flex-col items-center gap-8 animate-fade-in">
|
||||||
|
<h1 class="text-3xl font-medium tracking-tight text-zinc-900">Furtherverse</h1>
|
||||||
|
<div class="w-24 h-[2px] bg-zinc-100 rounded-full overflow-hidden relative">
|
||||||
|
<div class="h-full w-full bg-zinc-800 origin-left animate-loading-bar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] uppercase tracking-widest text-zinc-400 font-medium animate-pulse-slow">Starting</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
apps/desktop/src/renderer/styles.css
Normal file
40
apps/desktop/src/renderer/styles.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--animate-fade-in: fade-in 1.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
|
--animate-pulse-slow: pulse-slow 3s ease-in-out infinite;
|
||||||
|
--animate-loading-bar: loading-bar 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-slow {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-bar {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "@furtherverse/tsconfig/bun.json",
|
"extends": "@furtherverse/tsconfig/base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext", "DOM", "DOM.Iterable"]
|
"types": ["node"]
|
||||||
}
|
},
|
||||||
|
"include": ["src/main/**/*", "src/preload/**/*", "electron.vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../node_modules/turbo/schema.json",
|
"$schema": "../../node_modules/turbo/schema.json",
|
||||||
"extends": ["//"],
|
"extends": ["//"],
|
||||||
"tasks": {}
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["out/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,10 @@
|
|||||||
"extends": "//",
|
"extends": "//",
|
||||||
"files": {
|
"files": {
|
||||||
"includes": ["**", "!**/routeTree.gen.ts"]
|
"includes": ["**", "!**/routeTree.gen.ts"]
|
||||||
|
},
|
||||||
|
"css": {
|
||||||
|
"parser": {
|
||||||
|
"tailwindDirectives": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"compile": "bun build.ts",
|
"compile": "bun compile.ts",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
|
|||||||
@@ -44,7 +44,9 @@
|
|||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"drizzle-zod": "^0.8.3",
|
"drizzle-zod": "^0.8.3",
|
||||||
"@webui-dev/bun-webui": "^2.5.4",
|
"electron": "^34.0.0",
|
||||||
|
"electron-builder": "^26.0.0",
|
||||||
|
"electron-vite": "^5.0.0",
|
||||||
"nitro": "npm:nitro-nightly@3.0.1-20260206-171553-bc737c0c",
|
"nitro": "npm:nitro-nightly@3.0.1-20260206-171553-bc737c0c",
|
||||||
"ohash": "^2.0.11",
|
"ohash": "^2.0.11",
|
||||||
"postgres": "^3.4.8",
|
"postgres": "^3.4.8",
|
||||||
|
|||||||
Reference in New Issue
Block a user