# Project export: Emanduite

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Emanduite is Developer Tools for Desktop Low-Code Generator for Next.js, built with Tauri (Rust). It brings the powerful, local-first workflow of classic tools like PHP Maker into the modern web era.
- Devpost: https://devpost.com/software/emanduite
- GitHub: https://github.com/iannstronaut/Emanduite
- Video: https://www.youtube.com/embed/xuSxRuk_07Y?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Iannstronaut (10 commits)

## Devpost submission (written by the team)

### What it does

Emanduite adalah workspace desktop-first Development Tools untuk merancang database dan admin panel sebelum aplikasi web dibuat. Proyek ini dibuat untuk mengurangi pekerjaan berulang saat memulai aplikasi operasional: merancang tabel, relasi, peran, hak akses, menu, serta CRUD secara manual dan tidak konsisten. Pengguna bekerja dari aplikasi desktop untuk membuat sebuah Blueprint v1 berbasis SQLite. Blueprint tersebut menjadi sumber konfigurasi yang dapat ditinjau, dimigrasikan secara aman, dan pada tahap berikutnya digenerate menjadi aplikasi admin Next.js. Pendekatan ini menjaga desain database dan aturan akses tetap jelas sebelum kode frontend/backend dihasilkan.

## README (from the GitHub repository)

# Emanduite

Emanduite adalah workspace **desktop-first** untuk merancang database dan
admin panel sebelum aplikasi web dibuat. Proyek ini dibuat untuk mengurangi
pekerjaan berulang saat memulai aplikasi operasional: merancang tabel,
relasi, peran, hak akses, menu, serta CRUD secara manual dan tidak konsisten.

Pengguna bekerja dari aplikasi desktop untuk membuat sebuah Blueprint v1
berbasis SQLite. Blueprint tersebut menjadi sumber konfigurasi yang dapat
ditinjau, dimigrasikan secara aman, dan pada tahap berikutnya digenerate
menjadi aplikasi admin Next.js. Pendekatan ini menjaga desain database dan
aturan akses tetap jelas sebelum kode frontend/backend dihasilkan.

## Yang dikerjakan Emanduite

- Membuat dan membuka proyek SQLite, termasuk tabel sistem dan akun
  superadmin bawaan.
- Merancang entitas, kolom, relasi, menu, role, dan permission dari satu
  Blueprint v1.
- Menjadikan entitas yang tampil sebagai menu sebagai resource akses, sehingga
  permission dapat diberikan per role tanpa memasukkan resource satu per satu.
- Menjalankan preview dan perubahan skema SQLite dengan alur yang terkontrol.
- Menghasilkan starter admin Next.js dengan Prisma, autentikasi, akses berbasis
  role, dan antarmuka bergaya shadcn/ui.
- Menyediakan AI Design untuk menyusun usulan skema yang tetap harus ditinjau
  dan disetujui pengguna sebelum diterapkan.

Milestone A mencakup Blueprint v1, workspace SQLite, configuration tools, safe
migration, workflow runner terkontrol, diagnostics, recovery, dan redacted
support bundle. Generator Next.js dikembangkan setelah fondasi desktop ini
stabil.

## Penggunaan OpenAI GPT-5.6 Sol

AI Design dapat menggunakan OpenAI melalui protokol **OpenAI Compatible / Chat
Completions**. Untuk memakai GPT-5.6 Sol, buka **Settings > AI Provider** pada
proyek yang sedang aktif, lalu isi:

| Field | Nilai |
| --- | --- |
| Base URL | `https://api.openai.com/v1` |
| Model | `gpt-5.6-sol` |
| API key | API key OpenAI Anda |

Klik **Save AI settings**. Emanduite menyimpan API key di OS keyring, bukan di
file Blueprint. Setelah koneksi tersimpan, daftar model akan dimuat dari
provider; Anda dapat memilih `gpt-5.6-sol` dari dropdown atau mengisinya
langsung bila belum muncul di daftar.

Selanjutnya buka **AI Design**, tulis kebutuhan aplikasi dalam bahasa biasa,
misalnya tabel produk, pemasok, dan pergerakan stok. Emanduite mengirimkan
permintaan desain beserta konteks Blueprint yang ringkas ke endpoint
`/chat/completions` pada Base URL tersebut. Hasilnya berupa usulan tabel dan
relasi yang dapat diedit, dipreview, lalu **Apply** hanya setelah pengguna
menyetujui perubahan.

Jangan memasukkan API key ke repository, `.env` proyek, atau file Blueprint.
Jika key perlu diganti atau dicabut, gunakan **Remove stored key** pada halaman
AI Provider, kemudian buat API key baru di akun OpenAI.

## Development

Prasyarat: Node.js 22+, Rust 1.97+, dan dependency sistem Tauri v2.

```powershell
npm install
npm run tauri dev
```

Quality gate lokal:

```powershell
npm run phase1:check
npm run phase2:check
npm run phase3:check
npm run phase4:check
cargo clippy --manifest-path .\src-tauri\Cargo.toml --all-targets -- -D warnings
npm run tauri -- build --no-bundle
```

Konteks implementasi tersedia di `docs/dev/Phase-1.md` sampai
`docs/dev/Phase-4.md`.

## Lisensi

Proyek ini dilisensikan di bawah [GNU Affero General Public License v3.0](LICENSE) (AGPL-3.0-or-later).


## Detected evidence (automated analysis)

Indexed codebase: 62 recognized source files, 590 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- React (technology) — detected in the code
- Rust (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Next.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (73 of 73)

```
.gitignore
index.html
package.json
public/splashscreen.css
public/splashscreen.html
README.md
schemas/emanduite-project.schema.json
src-tauri/build.rs
src-tauri/capabilities/default.json
src-tauri/Cargo.lock
src-tauri/Cargo.toml
src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml
src-tauri/icons/android/values/ic_launcher_background.xml
src-tauri/icons/icon.icns
src-tauri/src/bin/export-schema.rs
src-tauri/src/bin/generate-phase5-fixture.rs
src-tauri/src/blueprint/migration.rs
src-tauri/src/blueprint/mod.rs
src-tauri/src/blueprint/model.rs
src-tauri/src/blueprint/persistence.rs
src-tauri/src/blueprint/validation.rs
src-tauri/src/commands.rs
src-tauri/src/database/mod.rs
src-tauri/src/database/sqlite.rs
src-tauri/src/error.rs
src-tauri/src/extension.rs
src-tauri/src/generator/mod.rs
src-tauri/src/generator/ownership.rs
src-tauri/src/generator/render.rs
src-tauri/src/lib.rs
src-tauri/src/logging.rs
src-tauri/src/main.rs
src-tauri/src/recovery.rs
src-tauri/src/secret.rs
src-tauri/src/workflow.rs
src-tauri/src/workspace.rs
src-tauri/tauri.conf.json
src/app/AiDesigner.test.ts
src/app/AiDesigner.tsx
src/app/AiSettings.tsx
src/app/App.tsx
src/app/ConfigEditors.tsx
src/app/ConnectionManager.tsx
src/app/ErrorBoundary.tsx
src/app/ExtensionEditor.tsx
src/app/GeneratorPanel.tsx
src/app/MenuIconPicker.tsx
src/app/ModalProvider.tsx
src/app/ProjectManager.tsx
src/app/SchemaEditor.tsx
src/app/SchemaExplorer.tsx
src/app/useWorkspace.ts
src/app/WorkflowPanel.tsx
src/contracts/blueprint.ts
src/contracts/commands.ts
src/contracts/generator.ts
src/contracts/schema-editor.ts
src/contracts/workflow.ts
src/contracts/workspace.ts
src/lib/tauri.ts
src/main.tsx
src/styles.css
src/vite-env.d.ts
tests/app.test.tsx
tests/generator-panel.test.tsx
tests/phase3-editors.test.tsx
tests/schema-explorer.test.tsx
tests/setup.ts
tests/workflow-panel.test.tsx
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts
```

### Dependencies

- package.json: @tailwindcss/vite@^4.3.2, @tauri-apps/api@^2.8.0, @tauri-apps/cli@^2.8.0, @tauri-apps/plugin-dialog@^2.7.1, @testing-library/jest-dom@^6.9.0, @testing-library/react@^16.3.0, @types/node@^22.0.0, @types/react@^19.2.0, @types/react-dom@^19.2.0, @vitejs/plugin-react@^5.0.0, iconsax-reactjs@^0.0.8, jsdom@^27.0.0, react@^19.2.0, react-dom@^19.2.0, tailwindcss@^4.3.2, typescript@^5.9.0, vite@^7.2.0, vitest@^4.0.0
- src-tauri/Cargo.toml: async-trait@0.1, bcrypt@0.17, chrono@0.4, keyring@3, reqwest@0.12, schemars@0.8, serde@1, serde_json@1, sha2@0.10, sqlx@0.8, tauri@2, tauri-build@2, tauri-plugin-dialog@2, tempfile@3, thiserror@2, tokio@1, tokio@1, tracing@0.1, tracing-subscriber@0.3, url@2, uuid@1

### Recent commits (newest first)

- v0.5
- Enhance user management features and improve permission handling
- fix and improve
- Fase 7 — Provider Matrix dan Release V1
- Fase 6 — Auth, Permissions, Hooks, dan Preview
- Fase 5 — Generator Next.js dan CRUD SQLite
- Fase 4 — Workflow Runner dan Desktop Stabilization
- Fase 3 — Desktop Configuration Tools dan Safe SQLite Migration
- Fase 2 — Project Manager, SQLite Introspection, dan Schema Explorer
- Fase 1 — Tauri, Blueprint, Secure Storage, SQLite
- chore: initialize project with README and ignore docs directory

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "emanduite",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview",
    "test": "vitest run",
    "test:watch": "vitest",
    "check": "npm run test && npm run build",
    "tauri": "tauri",
    "test:rust": "cargo test --manifest-path src-tauri/Cargo.toml --lib",
    "schema:export": "cargo run --manifest-path src-tauri/Cargo.toml --bin export-schema -- schemas/emanduite-project.schema.json",
    "phase1:check": "npm run check && npm run test:rust",
    "phase2:check": "npm run check && npm run test:rust",
    "phase3:check": "npm run check && npm run test:rust",
    "phase4:check": "npm run check && npm run test:rust",
    "phase5:fixture": "cargo run --manifest-path src-tauri/Cargo.toml --bin generate-phase5-fixture -- .phase5-generated",
    "phase5:generated:check": "npm run typecheck --prefix .phase5-generated/app && npm run lint --prefix .phase5-generated/app && npm run test --prefix .phase5-generated/app && npm run build --prefix .phase5-generated/app",
    "phase5:check": "npm run check && npm run test:rust"
  },
  "dependencies": {
    "@tauri-apps/api": "^2.8.0",
    "@tauri-apps/plugin-dialog": "^2.7.1",
    "iconsax-reactjs": "^0.0.8",
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  },
  "devDependencies": {
    "@tailwindcss/vite": "^4.3.2",
    "@tauri-apps/cli": "^2.8.0",
    "@testing-library/jest-dom": "^6.9.0",
    "@testing-library/react": "^16.3.0",
    "@types/node": "^22.0.0",
    "@types/react": "^19.2.0",
    "@types/react-dom": "^19.2.0",
    "@vitejs/plugin-react": "^5.0.0",
    "jsdom": "^27.0.0",
    "tailwindcss": "^4.3.2",
    "typescript": "^5.9.0",
    "vite": "^7.2.0",
    "vitest": "^4.0.0"
  }
}

```

### src-tauri/Cargo.toml

```
[package]
name = "emanduite"
version = "0.1.0"
description = "Emanduite desktop workspace"
authors = ["Emanduite Contributors"]
edition = "2021"
rust-version = "1.97"
default-run = "emanduite"

[lib]
name = "emanduite_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
async-trait = "0.1"
bcrypt = "0.17"
chrono = { version = "0.4", features = ["serde"] }
keyring = { version = "3", features = ["windows-native", "apple-native", "sync-secret-service"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
schemars = { version = "0.8", features = ["uuid1", "chrono"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "postgres", "mysql"] }
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2"
thiserror = "2"
tokio = { version = "1", features = ["io-util", "macros", "process", "rt-multi-thread", "sync", "time"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
url = "2"
uuid = { version = "1", features = ["v4", "v5", "serde"] }

[dev-dependencies]
tempfile = "3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

```

### src/main.tsx

```typescript
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./app/App";
import "./styles.css";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>
);

```

### src-tauri/src/main.rs

```rust
fn main() {
    emanduite_lib::run();
}

```

### src/app/App.tsx

```typescript
import { useEffect, useState } from "react";
import { Code, Data, Diagram, Edit, Folder, Global, LoginCurve, Magicpen, Monitor, Moon, Profile2User, SecuritySafe, Sun1 } from "iconsax-reactjs";
import { ConnectionManager } from "./ConnectionManager";
import { ErrorBoundary } from "./ErrorBoundary";
import { ProjectManager } from "./ProjectManager";
import { SchemaExplorer } from "./SchemaExplorer";
import { useWorkspace } from "./useWorkspace";
import { finishAppStartup, selectBlueprintFile } from "../lib/tauri";
import { SchemaEditor } from "./SchemaEditor";
import { AuthEditor, EntityEditor, GlobalEditor, PermissionEditor, RoleEditor } from "./ConfigEditors";
import { ExtensionEditor } from "./ExtensionEditor";
import { WorkflowPanel } from "./WorkflowPanel";
import { GeneratorPanel } from "./GeneratorPanel";
import { AiDesigner } from "./AiDesigner";
import { AiSettings } from "./AiSettings";
import { ModalProvider, useModal } from "./ModalProvider";
import activeLogo from "../../assets/emanduite.svg";
import inactiveLogo from "../../assets/emanduite_inactive.svg";

export function App() {
  return <ModalProvider><ErrorBoundary><Workspace /></ErrorBoundary></ModalProvider>;
}

function Workspace() {
  const workspace = useWorkspace();
  const { inform } = useModal();
  const [palette, setPalette] = useState(false);
  const [theme, setTheme] = useState<"light" | "dark">(() => localStorage.getItem("emanduite-theme") === "dark" ? "dark" : "light");

  useEffect(() => {
    const keydown = (event: KeyboardEvent) => {
      if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
        event.preventDefault(); setPalette((value) => !value);
      }
      if (event.key === "Escape") setPalette(false);
    };
    window.addEventListener("keydown", keydown);
    return () => window.removeEventListener("keydown", keydown);
  }, []);

  useEffect(() => {
    document.documentElement.dataset.theme = theme;
    localStorage.setItem("emanduite-theme", theme);
  }, [theme]);

  useEffect(() => {
    if ("__TAURI_INTERNALS__" in window) void finishAppStartup().catch(() => undefined);
  }, []);

  useEffect(() => {
    if (!workspace.error) return;
    void inform({ title: "Operation needs attention", description: workspace.error, tone: "error" });
    workspace.setError(null);
  }, [inform, workspace.error, workspace.setError]);

  const openFromDisk = async () => {
    const path = await selectBlueprintFile();
    if (path) await workspace.openProject(path);
    setPalette(false);
  };

  const navigationGroup = workspace.view === "projects" ? "project" : workspace.view === "generator" ? "generate" : ["database", "schema", "editor", "ai"].includes(workspace.view) ? "connection" : ["entities", "roles", "permissions", "auth"].includes(workspace.view) ? "access" : "settings";
  const hasSecondaryNavigation = navigationGroup === "connection" || navigationGroup === "access" || navigationGroup === "settings";
  const openGroup = (group: "project" | "connection" | "access" | "settings" | "generate") => {
    if (group === "project") workspace.setView("projects");
    if (group === "connection") workspace.setView("database");
    if (group === "access") workspace.setView("entities");
    if (group === "settings") workspace.setView("global");
    if (group === "generate") workspace.setView("generator");
  };

  return <main className={`workspace-shell${hasSecondaryNavigation ? " has-secondary-navigation" : ""}`}>
    <img className="app-logo" src={workspace.session ? activeLogo : inactiveLogo} alt="Emanduite" />
    <header className="titlebar"><span className="brand">Emanduite</span><span className="phase-badge">{workspace.info.phase}</span><span className="project-context">{workspace.session?.blueprint.projectName ?? "No active project"}</span><button className="theme-toggle" type="button" title={`Switch to ${theme === "light" ? "dark" : "light"} mode`} aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`} onClick={() => setTheme((value) => value === "light" ? "dark" : "light")}>{theme === "light" ? <Moon size={16} variant="Linear" /> : <Sun1 size={16} variant="Linear" />}<span>{theme === "light" ? "Dark" : "Light"}</span></button><button className="command-trigger" onClick={() => setPalette(true)}>⌘ Commands <kbd>Ctrl K</kbd></button></header>
    <aside className="primary-navigation" aria-label="Primary navigation">
      <button className={navigationGroup === "project" ? "primary-nav-item active" : "primary-nav-item"} onClick={() => openGroup("project")}><Folder size={19} variant={navigationGroup === "project" ? "Bold" : "Linear"} /><span>Project</span></button>
      <button className={navigationGroup === "connection" ? "primary-nav-item active" : "primary-nav-item"} disabled={!workspace.session} onClick={() => openGroup("connection")}><Data size={19} variant={navigationGroup === "connection" ? "Bold" : "Linear"} /><span>Connection</span></button>
      <button className={navigationGroup === "access" ? "primary-nav-item active" : "primary-nav-item"} disabled={!workspace.session} onClick={() => openGroup("access")}><SecuritySafe size={19} variant={navigationGroup === "access" ? "Bold" : "Linear"} /><span>Access</span></button>
      <button className={navigationGroup === "settings" ? "primary-nav-item active" : "primary-nav-item"} disabled={!workspace.session} onClick={() => openGroup("settings")}><Global size={19} variant={navigationGroup === "settings" ? "Bold" : "Linear"} /><span>Settings</span></button>
      <button className={navigationGroup === "generate" ? "primary-nav-item active" : "primary-nav-item"} disabled={!workspace.session} onClick={() => openGroup("generate")}><Magicpen size={19} variant={navigationGroup === "generate" ? "Bold" : "Linear"} /><span>Generate</span></button>
    </aside>
    {hasSecondaryNavigation && <aside className="navigation secondary-navigation" aria-label={`${navigationGroup} navigation`}>
      <div className="secondary-navigatio
[truncated — 8039 more characters]
```

### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="theme-color" content="#05080a" />
    <title>Emanduite Workspace</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### vite.config.ts

```typescript
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

const host = process.env.TAURI_DEV_HOST;

export default defineConfig({
  plugins: [react(), tailwindcss()],
  clearScreen: false,
  server: {
    port: 1420,
    strictPort: true,
    host: host || false,
    hmr: host ? { protocol: "ws", host, port: 1421 } : undefined,
    watch: { ignored: ["**/src-tauri/**"] }
  },
  test: {
    environment: "jsdom",
    setupFiles: ["./tests/setup.ts"],
    css: true,
    exclude: ["**/node_modules/**", "**/.phase5-*/**", "**/phase5-gate/**"]
  }
});

```

### src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />

```

### src-tauri/build.rs

```rust
fn main() {
    tauri_build::build()
}

```

### tests/setup.ts

```typescript
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";

afterEach(cleanup);

```

[50 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]