Sprint 23/24 — Tauri v2 앱 래핑 + salsa 0.16 증분 쿼리 백엔드
All checks were successful
Publish ParaWiki / build-and-deploy (push) Successful in 34s

Sprint 23: cimery-app을 Tauri v2 앱으로 전환.
- tauri.conf.json, capabilities/default.json, frontend/index.html 추가
- src/commands.rs: 7개 IPC 커맨드 (launch_viewer, 프로젝트 관리, USD/CSV 익스포트)
- 뷰어 사이드카: std::process::Command 방식 (PATH + exe-dir 탐색)
- release.yml: 3단계 멀티플랫폼 릴리스 워크플로로 교체

Sprint 24: cimery-incremental에 salsa 0.16 백엔드 추가.
- salsa_db.rs: BridgeQueryGroup + SalsaIncrementalDb<K>
- --features salsa-backend 로 활성화 (기본값: 수동 tracking, WASM 안전)
- IR 전 구조체 + Mesh + KernelError에 PartialEq/Eq 추가
- 테스트 20개 전부 통과 (수동 12 + salsa 8)
- cargo check --workspace 0 errors/warnings

기타: viewer/dsl 컴파일 경고 제거, wiki 실행 가이드 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-15 09:09:47 +09:00
parent 1f9ca3a00f
commit 824c18610b
24 changed files with 1743 additions and 138 deletions

View File

@@ -0,0 +1,340 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cimery</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', 'Malgun Gothic', sans-serif;
background: #1a1d23;
color: #d4d8e0;
height: 100vh;
display: flex;
flex-direction: column;
}
/* ── Titlebar ─────────────────────────────────────────────── */
.titlebar {
background: #13161b;
border-bottom: 1px solid #2c3040;
padding: 10px 20px;
display: flex;
align-items: center;
gap: 12px;
user-select: none;
}
.titlebar .logo {
font-size: 18px;
font-weight: 700;
color: #5b9bd5;
letter-spacing: 1px;
}
.titlebar .subtitle {
font-size: 12px;
color: #6b7280;
}
/* ── Main layout ──────────────────────────────────────────── */
.main {
flex: 1;
display: flex;
overflow: hidden;
}
/* ── Sidebar ──────────────────────────────────────────────── */
.sidebar {
width: 220px;
background: #13161b;
border-right: 1px solid #2c3040;
padding: 16px 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.sidebar-item {
padding: 9px 20px;
font-size: 13px;
cursor: pointer;
border-left: 3px solid transparent;
transition: background 0.15s;
}
.sidebar-item:hover { background: #1f2330; }
.sidebar-item.active {
background: #1f2330;
border-left-color: #5b9bd5;
color: #fff;
}
.sidebar-section {
padding: 16px 20px 6px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: #4b5563;
}
/* ── Content ──────────────────────────────────────────────── */
.content {
flex: 1;
padding: 32px 40px;
overflow-y: auto;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #e5e7eb;
margin-bottom: 24px;
}
/* ── Card grid ────────────────────────────────────────────── */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.card {
background: #1f2330;
border: 1px solid #2c3040;
border-radius: 8px;
padding: 20px;
cursor: pointer;
transition: border-color 0.15s, transform 0.1s;
}
.card:hover {
border-color: #5b9bd5;
transform: translateY(-1px);
}
.card h3 { font-size: 14px; color: #e5e7eb; margin-bottom: 6px; }
.card p { font-size: 12px; color: #6b7280; line-height: 1.5; }
.card .icon { font-size: 24px; margin-bottom: 10px; }
/* ── Action buttons ───────────────────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 18px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
border: none;
transition: background 0.15s;
}
.btn-primary { background: #2563eb; color: #fff; }
.btn-primary:hover { background: #1d4ed8; }
.btn-secondary { background: #1f2330; color: #d4d8e0; border: 1px solid #2c3040; }
.btn-secondary:hover { background: #2c3040; }
.btn-group { display: flex; gap: 10px; margin-bottom: 28px; }
/* ── Status bar ───────────────────────────────────────────── */
.statusbar {
background: #13161b;
border-top: 1px solid #2c3040;
padding: 6px 20px;
font-size: 11px;
color: #4b5563;
display: flex;
gap: 20px;
}
.status-ok { color: #22c55e; }
.status-warn { color: #f59e0b; }
/* ── Recent projects table ────────────────────────────────── */
.recent-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.recent-table th {
text-align: left;
padding: 8px 12px;
color: #6b7280;
font-weight: 400;
border-bottom: 1px solid #2c3040;
}
.recent-table td {
padding: 10px 12px;
border-bottom: 1px solid #1f2330;
color: #d4d8e0;
}
.recent-table tr:hover td { background: #1f2330; cursor: pointer; }
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
background: #1e3a5f;
color: #5b9bd5;
}
</style>
</head>
<body>
<!-- Titlebar -->
<div class="titlebar">
<span class="logo">cimery</span>
<span class="subtitle">Civil Parametric BIM v0.1.0</span>
</div>
<!-- Main layout -->
<div class="main">
<!-- Sidebar -->
<nav class="sidebar">
<div class="sidebar-section">작업공간</div>
<div class="sidebar-item active" onclick="showPage('home')"></div>
<div class="sidebar-item" onclick="showPage('projects')">프로젝트</div>
<div class="sidebar-item" onclick="launchViewer()">3D 뷰어 열기</div>
<div class="sidebar-section">내보내기</div>
<div class="sidebar-item" onclick="exportUSD()">USD 익스포트</div>
<div class="sidebar-item" onclick="exportCSV()">CSV 템플릿</div>
<div class="sidebar-section">도움말</div>
<div class="sidebar-item" onclick="showPage('about')">정보</div>
</nav>
<!-- Content area -->
<div class="content" id="content">
<!-- Populated by JS showPage() -->
</div>
</div>
<!-- Status bar -->
<div class="statusbar">
<span id="status-kernel" class="status-ok">커널: PureRust</span>
<span id="status-version">v0.1.0</span>
<span id="status-msg"></span>
</div>
<script>
// ── Tauri IPC helper ─────────────────────────────────────────
async function invoke(cmd, args = {}) {
try {
const { invoke: tauriInvoke } = await import('https://unpkg.com/@tauri-apps/api@2/core');
return await tauriInvoke(cmd, args);
} catch (e) {
// Dev fallback when running outside Tauri (browser preview)
console.warn('Tauri IPC not available — dev fallback', cmd, args);
return { ok: false, error: 'dev_mode' };
}
}
async function launchViewer() {
setStatus('3D 뷰어 실행 중…');
const result = await invoke('launch_viewer');
if (result && result.ok) {
setStatus('뷰어 실행됨');
} else {
setStatus('뷰어 실행 실패: ' + (result?.error ?? '알 수 없는 오류'), true);
}
}
async function exportUSD() {
setStatus('USD 익스포트 중…');
const result = await invoke('export_usd_default');
setStatus(result?.ok ? 'USD 익스포트 완료' : ('USD 실패: ' + result?.error), !result?.ok);
}
async function exportCSV() {
setStatus('CSV 템플릿 생성 중…');
const result = await invoke('export_csv_template');
setStatus(result?.ok ? 'CSV 템플릿 생성 완료' : ('CSV 실패: ' + result?.error), !result?.ok);
}
function setStatus(msg, warn = false) {
const el = document.getElementById('status-msg');
el.textContent = msg;
el.className = warn ? 'status-warn' : 'status-ok';
}
// ── Pages ────────────────────────────────────────────────────
const pages = {
home: `
<div class="section-title">시작하기</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="launchViewer()">3D 뷰어 열기</button>
<button class="btn btn-secondary" onclick="newProject()">새 프로젝트</button>
<button class="btn btn-secondary" onclick="openProject()">프로젝트 열기</button>
</div>
<div class="card-grid">
<div class="card" onclick="launchViewer()">
<div class="icon">🏗️</div>
<h3>거더교 3D 뷰어</h3>
<p>egui+wgpu 기반 실시간 파라메트릭 뷰어</p>
</div>
<div class="card" onclick="exportUSD()">
<div class="icon">📦</div>
<h3>USD 익스포트</h3>
<p>교량 씬 전체를 USD 텍스트 포맷으로 저장</p>
</div>
<div class="card" onclick="exportCSV()">
<div class="icon">📋</div>
<h3>CSV 템플릿</h3>
<p>거더 파라미터 템플릿을 CSV로 생성</p>
</div>
<div class="card" onclick="showPage('about')">
<div class="icon"></div>
<h3>cimery 정보</h3>
<p>버전·기술 스택·라이선스</p>
</div>
</div>
`,
projects: `
<div class="section-title">프로젝트</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="newProject()">새 프로젝트</button>
<button class="btn btn-secondary" onclick="openProject()">열기</button>
</div>
<table class="recent-table">
<thead>
<tr><th>이름</th><th>마지막 수정</th><th>유형</th></tr>
</thead>
<tbody id="project-list">
<tr><td colspan="3" style="color:#4b5563;padding:20px 12px;">저장된 프로젝트 없음</td></tr>
</tbody>
</table>
`,
about: `
<div class="section-title">cimery 정보</div>
<div class="card" style="max-width:480px">
<h3 style="font-size:16px;margin-bottom:12px">cimery v0.1.0</h3>
<p style="margin-bottom:8px">Civil + BIM + -ery</p>
<p style="margin-bottom:16px;line-height:1.8">
토목 엔지니어링 특성을 반영한 파라메트릭 모델링 도구.<br>
MVP: 거더교 (PSC-I 거더, 교각, 교대, 받침, 교량받침).
</p>
<p style="font-size:11px;color:#4b5563">
Tauri v2 · egui 0.29 · wgpu 22 · Rust 2021<br>
License: MIT OR Apache-2.0
</p>
</div>
`,
};
function showPage(name) {
document.querySelectorAll('.sidebar-item').forEach(el => el.classList.remove('active'));
event?.target?.classList.add('active');
document.getElementById('content').innerHTML = pages[name] ?? pages.home;
}
async function newProject() {
const result = await invoke('new_project');
setStatus(result?.ok ? '새 프로젝트 생성됨' : '새 프로젝트 실패');
}
async function openProject() {
const result = await invoke('open_project_dialog');
if (result?.ok) setStatus('프로젝트 열림: ' + result.path);
}
// Init
showPage('home');
</script>
</body>
</html>