sh-ui-cli 0.87.0 → 0.88.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,32 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
4
4
  "versions": [
5
+ {
6
+ "version": "0.88.1",
7
+ "date": "2026-05-14",
8
+ "title": "vite-standalone 템플릿 모바일/Tauri WebView 친화 — viewport-fit + theme-color + tap-highlight reset",
9
+ "type": "patch",
10
+ "highlights": [
11
+ "**`index.html` viewport meta 확장** — `viewport-fit=cover` 추가 (iOS safe-area inset / Android edge-to-edge / Tauri 모바일 호환). `theme-color` 메타도 light/dark prefers-color-scheme 분기로 emit — 모바일 브라우저 status bar, PWA, Tauri WebView 의 chrome 톤이 앱 톤을 따라감.",
12
+ "**globals.css 에 WebView base reset** — `-webkit-tap-highlight-color: transparent` (탭 시 회색 박스 차단), `-webkit-user-select: none` (모바일 long-press 컨텍스트 메뉴 차단), `touch-action: manipulation` (300ms 더블탭 줌 지연 제거). `input`/`textarea`/`[contenteditable]` 은 명시적으로 `user-select: text` 로 복원해 폼은 정상 동작. fsd + flat 양쪽 arch overlay 에 동일 emit.",
13
+ "**ai-org Tauri 통일 전략 대응 (참고용)** — Phase 1.5 desktop + Phase 3 mobile 통일 Tauri 2 워크플로우의 WebView 친화 디폴트. sh-ui 컴포넌트 자체의 touch-target (44pt iOS / 48dp Android) 분기는 별도 PR — `--control-md-touch` 류 토큰 신설 + 컴포넌트 디폴트 API 결정 필요.",
14
+ "**smoke V8 + V9 회귀 가드** — V8 은 viewport-fit + 양쪽 theme-color 매체 쿼리 존재, V9 는 fsd + flat globals.css 양쪽에 sentinel 블록 + 핵심 reset 라인 존재를 단언. 텍스트 입력 복원 라인도 따로 가드 (input/textarea 셀렉트 회귀 차단)."
15
+ ],
16
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.88.1"
17
+ },
18
+ {
19
+ "version": "0.88.0",
20
+ "date": "2026-05-14",
21
+ "title": "Tauri 2.x 데스크탑 셸 — sh_ui_create_project platform=vite tauri=true",
22
+ "type": "minor",
23
+ "highlights": [
24
+ "**`--tauri` 플래그 + MCP `tauri: true` 옵션** — `sh-ui-cli create --platform vite --structure standalone --tauri` 한 줄로 Vite SPA + Tauri 2.x 네이티브 셸 (`src-tauri/`) 일괄 emit. ai-org 같은 local-first 데스크탑 앱의 1급 진입점. v0.86.0 의 vite 프리셋과 짝 (Q1-Q5 design decisions 모두 confirmed: assume Rust installed, `src-tauri/` sibling layout, minimal capabilities, identifier placeholder, Tauri 2.x only).",
25
+ "**Tauri 2.x idioms 표준 준수** — `Cargo.toml` 의 lib name 은 snake_case (`{tauri_crate_name}_lib`), `tauri.conf.json` 의 `identifier` 는 `app.{crate}.dev` 플레이스홀더 (README 에 production 전 교체 TODO 명시), `capabilities/default.json` 은 `core:default` + `opener:default` 만 — fs/dialog/shell 권한은 사용자가 명시적 opt-in.",
26
+ "**Vite 패치 자동 적용** — Tauri 권장값(`clearScreen: false`, `server.strictPort: true`, `server.host: false`, `server.port: 5173`) 을 `vite.config.ts` 에 emit. `@tauri-apps/cli` (devDep), `@tauri-apps/api` + `@tauri-apps/plugin-opener` (dep) + `tauri`/`tauri:dev`/`tauri:build` scripts 자동 추가. `.gitignore` 에 `src-tauri/target/` 추가 (Rust 빌드 산출물 보호).",
27
+ "**가드 + 회귀 테스트** — `tauri: true + platform != vite` 또는 `tauri: true + structure == monorepo` 는 CLI 진입점 + MCP 진입점 양쪽에서 명시적 Korean 에러. monorepo + Tauri 는 v0.89 후속. smoke V7a-f 6 개 시나리오로 회귀 가드 (file emission, placeholder substitution, package/vite/gitignore patches, opt-out 회귀, 잘못된 조합 거부)."
28
+ ],
29
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.88.0"
30
+ },
5
31
  {
6
32
  "version": "0.87.0",
7
33
  "date": "2026-05-14",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.87.0",
3
+ "version": "0.88.1",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,7 +13,7 @@ const VALID_PLUGINS = allPlugins.map((p) => p.name);
13
13
  const VALID_ARCHES = allArchitectures.map((a) => a.name);
14
14
 
15
15
  const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch', 'port'];
16
- const BOOL_FLAGS = ['yes', 'help', 'dry-run'];
16
+ const BOOL_FLAGS = ['yes', 'help', 'dry-run', 'tauri'];
17
17
 
18
18
  const SUBCOMMANDS = ['add-app', 'add-component'];
19
19
 
@@ -35,6 +35,7 @@ import { CSS_FRAMEWORK_DEFAULT } from '../constants.js';
35
35
  * @property {'tailwind'|'plain'|'css-modules'} [cssFramework]
36
36
  * @property {string} [projectName]
37
37
  * @property {string} [appName] monorepo 첫 앱 이름. 기본 'web'
38
+ * @property {boolean} [tauri] platform=vite + structure=standalone 일 때 Tauri 2.x 셸 같이 emit
38
39
  */
39
40
 
40
41
  /**
@@ -62,6 +63,7 @@ export function describeTemplate(opts = {}) {
62
63
  plugins: pluginNames = [],
63
64
  cssFramework = CSS_FRAMEWORK_DEFAULT,
64
65
  appName: rawAppName = 'web',
66
+ tauri = false,
65
67
  } = opts;
66
68
 
67
69
  if (platform === 'flutter') {
@@ -85,10 +87,19 @@ export function describeTemplate(opts = {}) {
85
87
  }
86
88
  const baseFiles = tpl.base.slice();
87
89
  const archFiles = (tpl.arches?.[safeArchName] ?? []).slice();
88
- return finalize([
90
+ const groups = [
89
91
  makeGroup('base', '베이스 (vite-standalone)', baseFiles),
90
92
  makeGroup('arch', `Arch (${safeArchName})`, archFiles),
91
- ]);
93
+ ];
94
+ if (tauri) {
95
+ const tauriTpl = TEMPLATE_MANIFEST['tauri-shell'];
96
+ if (!tauriTpl) {
97
+ throw new Error("Template manifest missing entry for 'tauri-shell'.");
98
+ }
99
+ const tauriFiles = tauriTpl.base.map((p) => `src-tauri/${p}`);
100
+ groups.push(makeGroup('tauri', 'Tauri 셸 (src-tauri/)', tauriFiles));
101
+ }
102
+ return finalize(groups);
92
103
  }
93
104
 
94
105
  // monorepo — vite app 변형. Next monorepo 브랜치와 동일 구조이지만 vite-app 템플릿
@@ -213,6 +213,23 @@ export async function createProject(options = {}) {
213
213
  ],
214
214
  });
215
215
 
216
+ // tauri 옵션은 platform=vite + structure=standalone 일 때만 의미. 다른 조합은 명시적 에러.
217
+ // (MCP 진입점은 mcp.mjs 가 이미 동일 가드 — CLI 직접 호출에도 동일 안전망.)
218
+ if (options.tauri) {
219
+ if (platform !== 'vite') {
220
+ throw new Error(
221
+ `tauri: true 는 platform=vite 일 때만 지원합니다 (현재 platform=${platform}). ` +
222
+ `--tauri 옵션 제거 또는 --platform vite 사용.`,
223
+ );
224
+ }
225
+ if (options.structure === 'monorepo') {
226
+ throw new Error(
227
+ 'platform=vite + structure=monorepo + tauri=true 는 아직 지원 안 함 (v0.89 후속). ' +
228
+ 'standalone 으로 시도하거나 tauri 옵션 제거.',
229
+ );
230
+ }
231
+ }
232
+
216
233
  // arch 결정 — platform 확정 후. 사용자가 --arch 미지정 시:
217
234
  // - next → DEFAULT_ARCH ('fsd')
218
235
  // - flutter → 현재 Flutter arch 디스크립터 없음 → null. 미래에 flutter arch 추가되면
@@ -333,8 +350,15 @@ export async function createProject(options = {}) {
333
350
  });
334
351
 
335
352
  if (projectType === 'standalone') {
336
- await generateViteStandalone(targetDir, projectName, theme, cssFramework, arch, themeBase);
353
+ await generateViteStandalone(targetDir, projectName, theme, cssFramework, arch, themeBase, { tauri: !!options.tauri });
337
354
  } else {
355
+ // monorepo + tauri 는 v0.89 후속 — 명시적 에러
356
+ if (options.tauri) {
357
+ throw new Error(
358
+ 'platform=vite + structure=monorepo + tauri=true 는 아직 지원 안 함 (v0.89 후속). ' +
359
+ 'standalone 으로 시도하거나 tauri 옵션 제거.',
360
+ );
361
+ }
338
362
  await generateMonorepo(targetDir, projectName, [], { yes: options.yes, theme, css: cssFramework, arch, themeBase, platform: 'vite' });
339
363
  }
340
364
 
@@ -353,6 +377,11 @@ export async function createProject(options = {}) {
353
377
  console.log(`\n cd ${projectName}`);
354
378
  console.log(' pnpm install');
355
379
  console.log(' pnpm dev\n');
380
+ if (options.tauri && projectType === 'standalone') {
381
+ console.log('Tauri 데스크탑 셸:');
382
+ console.log(' pnpm tauri dev # Rust 처음 빌드는 5~10분 — 캐시 후 5~10초');
383
+ console.log(' (Rust 미설치 시 https://rustup.rs/ 참고)\n');
384
+ }
356
385
  console.log('다음 단계 — 베이스 컴포넌트 추가 (예시):');
357
386
  console.log(' npx sh-ui-cli add button card input dialog\n');
358
387
  return;
@@ -731,7 +760,7 @@ async function generateStandalone(targetDir, projectName, plugins, theme, css, a
731
760
  await patchShUiConfig(path.join(targetDir, 'sh-ui.config.json'), css, themeBase);
732
761
  }
733
762
 
734
- async function generateViteStandalone(targetDir, projectName, theme, css, arch, themeBase) {
763
+ async function generateViteStandalone(targetDir, projectName, theme, css, arch, themeBase, { tauri = false } = {}) {
735
764
  // 베이스 (arch-neutral) + arch 오버레이 — generateStandalone 과 같은 패턴.
736
765
  await fs.copy(path.join(TEMPLATES_DIR, 'vite-standalone'), targetDir, {
737
766
  filter: (src) => !src.includes(`${path.sep}_arch${path.sep}`) && !src.endsWith(`${path.sep}_arch`),
@@ -766,6 +795,106 @@ async function generateViteStandalone(targetDir, projectName, theme, css, arch,
766
795
  await applyCssFrameworkVariant(targetDir, css, { isMonorepo: false, plugins: [], arch });
767
796
  await injectCssTheme(targetDir, theme);
768
797
  await patchShUiConfig(path.join(targetDir, 'sh-ui.config.json'), css, themeBase);
798
+
799
+ // Tauri 셸 emit (옵션) — vite SPA + native window. standalone 만 v1 지원.
800
+ if (tauri) {
801
+ await emitTauri(targetDir, projectName);
802
+ await patchViteForTauri(targetDir);
803
+ }
804
+ }
805
+
806
+ /**
807
+ * tauri-shell 템플릿을 `<targetDir>/src-tauri/` 로 복사하고 placeholder 치환.
808
+ *
809
+ * - `{{project_name}}` → projectName (kebab-case 유지, npm 패키지명과 동일)
810
+ * - `{{tauri_crate_name}}` → snake_case 변환 (Rust crate name 규칙: 영숫자+언더스코어).
811
+ * 하이픈/점/대문자가 들어 있으면 모두 안전한 형태로 정규화.
812
+ *
813
+ * generateViteStandalone 에서 tauri: true 인 경우 호출. monorepo+tauri 는 v0.89 후속.
814
+ */
815
+ async function emitTauri(targetDir, projectName) {
816
+ const srcTauriDir = path.join(targetDir, 'src-tauri');
817
+ await fs.copy(path.join(TEMPLATES_DIR, 'tauri-shell'), srcTauriDir);
818
+
819
+ // crate name: snake_case 강제 — Rust 식별자는 영숫자+'_' 만 허용
820
+ const tauriCrateName = projectName
821
+ .toLowerCase()
822
+ .replace(/[^a-z0-9]+/g, '_')
823
+ .replace(/^_+|_+$/g, '');
824
+
825
+ await replaceInAllFiles(srcTauriDir, '{{tauri_crate_name}}', tauriCrateName);
826
+ await replaceInAllFiles(srcTauriDir, '{{project_name}}', projectName);
827
+ }
828
+
829
+ /**
830
+ * vite 앱의 package.json + vite.config.ts 를 Tauri 친화적으로 패치.
831
+ *
832
+ * - package.json: `@tauri-apps/cli` (devDep), `@tauri-apps/api` (dep), `tauri`/`tauri:dev`/`tauri:build` scripts 추가
833
+ * - vite.config.ts: Tauri 공식 권장값 추가 — `clearScreen: false`, `server.strictPort: true`,
834
+ * `server.host: false`, `server.port: 5173`. 그래야 Tauri 가 dev server 를 안정적으로 wrap.
835
+ *
836
+ * NOTE: vite.config.ts 를 전부 재작성한다. 현재 base template 의 vite.config.ts 는 arch-neutral
837
+ * 이라 안전. 후속 task 에서 arch-specific vite.config.ts overlay 가 생기면 이 자리에서 머지 전략
838
+ * 필요 (현재는 단순 overwrite).
839
+ */
840
+ async function patchViteForTauri(targetDir) {
841
+ const pkgPath = path.join(targetDir, 'package.json');
842
+ const pkg = await fs.readJson(pkgPath);
843
+
844
+ pkg.dependencies = pkg.dependencies ?? {};
845
+ pkg.devDependencies = pkg.devDependencies ?? {};
846
+ pkg.scripts = pkg.scripts ?? {};
847
+
848
+ pkg.dependencies['@tauri-apps/api'] = '^2.0.0';
849
+ pkg.dependencies['@tauri-apps/plugin-opener'] = '^2.0.0';
850
+ pkg.devDependencies['@tauri-apps/cli'] = '^2.0.0';
851
+
852
+ pkg.scripts.tauri = 'tauri';
853
+ pkg.scripts['tauri:dev'] = 'tauri dev';
854
+ pkg.scripts['tauri:build'] = 'tauri build';
855
+
856
+ pkg.dependencies = sortObjectKeys(pkg.dependencies);
857
+ pkg.devDependencies = sortObjectKeys(pkg.devDependencies);
858
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
859
+
860
+ // vite.config.ts 재작성 — Tauri 공식 권장 설정 추가.
861
+ const viteCfgPath = path.join(targetDir, 'vite.config.ts');
862
+ const viteCfg = `import { defineConfig } from 'vite';
863
+ import react from '@vitejs/plugin-react';
864
+ import tailwindcss from '@tailwindcss/vite';
865
+ import tsconfigPaths from 'vite-tsconfig-paths';
866
+
867
+ // Tauri 권장 설정 (https://v2.tauri.app/start/frontend/vite/)
868
+ // - clearScreen: false — Rust 컴파일 에러가 터미널을 가리지 않게
869
+ // - server.strictPort — Tauri 가 사용할 포트를 고정 (충돌 시 에러)
870
+ // - server.host: false — Tauri dev 가 host network 안 열어도 됨
871
+ export default defineConfig({
872
+ plugins: [react(), tailwindcss(), tsconfigPaths()],
873
+ clearScreen: false,
874
+ server: {
875
+ port: 5173,
876
+ strictPort: true,
877
+ host: false,
878
+ },
879
+ });
880
+ `;
881
+ await fs.writeFile(viteCfgPath, viteCfg);
882
+
883
+ // .gitignore 에 src-tauri/target 추가 — Rust 빌드 산출물 (수 GB 가능).
884
+ // 스캐폴드 단계에서는 파일명이 `gitignore` (점 없음); finalizeProject 가 나중에 `.gitignore` 로 rename.
885
+ // 양쪽 이름 모두 시도해서 호출 순서가 달라져도 안전하게 적용.
886
+ const gitignoreCandidates = ['.gitignore', 'gitignore'];
887
+ for (const name of gitignoreCandidates) {
888
+ const p = path.join(targetDir, name);
889
+ if (await fs.pathExists(p)) {
890
+ let ignore = await fs.readFile(p, 'utf-8');
891
+ if (!ignore.includes('src-tauri/target')) {
892
+ ignore += `\n# Tauri build artifacts\nsrc-tauri/target/\n`;
893
+ await fs.writeFile(p, ignore);
894
+ }
895
+ break;
896
+ }
897
+ }
769
898
  }
770
899
 
771
900
  async function generateMonorepo(targetDir, projectName, plugins, { yes = false, theme, css, arch, themeBase, platform = 'next' } = {}) {
@@ -89,6 +89,7 @@ export async function runCreate(rest) {
89
89
  arch: flags.arch,
90
90
  theme: flags.theme,
91
91
  css: flags.css,
92
+ tauri: flags.tauri,
92
93
  yes: flags.yes,
93
94
  dryRun: flags.dryRun,
94
95
  });
@@ -314,6 +314,18 @@ export const TEMPLATE_MANIFEST = {
314
314
  ]
315
315
  }
316
316
  },
317
+ "tauri-shell": {
318
+ "base": [
319
+ ".gitignore",
320
+ "Cargo.toml",
321
+ "README.md",
322
+ "build.rs",
323
+ "capabilities/default.json",
324
+ "src/lib.rs",
325
+ "src/main.rs",
326
+ "tauri.conf.json"
327
+ ]
328
+ },
317
329
  "ui-app-template": {
318
330
  "base": [
319
331
  "eslint.config.js",
package/src/mcp.mjs CHANGED
@@ -367,6 +367,7 @@ export async function startMcpServer() {
367
367
  {
368
368
  description:
369
369
  "빈 폴더에 sh-ui 프로젝트 스캐폴드 — Next.js (standalone/monorepo) | Vite (standalone/monorepo) | Flutter. " +
370
+ "+ vite + standalone 인 경우 `tauri: true` 로 Tauri 2.x 데스크탑 셸 (`src-tauri/`) 까지 한 번에 emit (Rust toolchain 필요). " +
370
371
  `FSD 폴더 구조 + 토큰 + sh-ui.config.json 일괄 생성. 사용자가 '새 프로젝트' / '빈 폴더' / '스캐폴드부터' 류 요청을 하면 이 툴 사용 (Bash 로 npx ${cliName} create 직접 호출보다 우선). ` +
371
372
  "**단일 진입점** — theme/plugins/cssFramework/structure 모두 호출 시점에 정해서 한 번에 박는다. 호출 후 sh-ui.config.json/tokens.css 를 손으로 패치하지 말 것 (다음 재스캐폴드 시 유실). " +
372
373
  "산출물: theme 인자가 프리셋이면 sh-ui.config.json 의 theme.base 가 그 이름, base64 면 'custom'. paths.styles · paths.tokens 도 자동 박혀서 sh_ui_add_component 가 사후 패치 없이 동작.",
@@ -394,6 +395,12 @@ export async function startMcpServer() {
394
395
  .describe("부모 디렉토리. 기본 process.cwd()"),
395
396
  force: z.boolean().optional()
396
397
  .describe("기존 디렉토리 덮어쓰기. 기본 false (안전)"),
398
+ tauri: z.boolean().optional()
399
+ .describe(
400
+ "Tauri 2.x 데스크탑 셸 (`src-tauri/`) 함께 emit. platform=vite + structure=standalone 일 때만 지원. " +
401
+ "Rust toolchain (`cargo`/`rustc`) 가 시스템에 설치되어 있어야 첫 `pnpm tauri dev` 가 동작. " +
402
+ "기본 false. monorepo + tauri 는 v0.89 후속.",
403
+ ),
397
404
  },
398
405
  },
399
406
  async (input) => {
@@ -415,6 +422,24 @@ export async function startMcpServer() {
415
422
  } catch (e) {
416
423
  return { isError: true, content: [{ type: "text", text: e.message }] };
417
424
  }
425
+ if (input.tauri && input.platform !== "vite") {
426
+ return {
427
+ isError: true,
428
+ content: [{
429
+ type: "text",
430
+ text: "tauri: true 는 platform=vite 일 때만 지원합니다 (현재 platform=" + input.platform + ").",
431
+ }],
432
+ };
433
+ }
434
+ if (input.tauri && input.structure === "monorepo") {
435
+ return {
436
+ isError: true,
437
+ content: [{
438
+ type: "text",
439
+ text: "platform=vite + structure=monorepo + tauri=true 는 아직 지원 안 함 (v0.89 후속). standalone 으로 시도하거나 tauri 옵션 제거.",
440
+ }],
441
+ };
442
+ }
418
443
  const targetParent = resolveCwd(input);
419
444
  const targetDir = resolve(targetParent, input.name);
420
445
  if (existsSync(targetDir) && !input.force) {
@@ -440,6 +465,7 @@ export async function startMcpServer() {
440
465
  arch: input.arch,
441
466
  theme: input.theme,
442
467
  css: input.cssFramework,
468
+ tauri: input.tauri,
443
469
  yes: true, // 사전 검사를 마쳤으니 generator 의 confirm 프롬프트 우회
444
470
  }),
445
471
  );
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "{{tauri_crate_name}}"
3
+ version = "0.1.0"
4
+ description = "{{project_name}} desktop shell"
5
+ authors = ["you"]
6
+ edition = "2021"
7
+
8
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9
+
10
+ [lib]
11
+ name = "{{tauri_crate_name}}_lib"
12
+ crate-type = ["staticlib", "cdylib", "rlib"]
13
+
14
+ [build-dependencies]
15
+ tauri-build = { version = "2", features = [] }
16
+
17
+ [dependencies]
18
+ tauri = { version = "2", features = [] }
19
+ tauri-plugin-opener = "2"
20
+ serde = { version = "1", features = ["derive"] }
21
+ serde_json = "1"
@@ -0,0 +1,49 @@
1
+ # {{project_name}} — Tauri 데스크탑 셸
2
+
3
+ 이 디렉토리는 Tauri 2.x 의 Rust 코드입니다. 부모 디렉토리의 vite SPA 가
4
+ 프론트엔드, 이 디렉토리가 native 윈도우 셸.
5
+
6
+ ## 첫 실행
7
+
8
+ ```bash
9
+ # 부모 디렉토리에서 (vite 앱 루트)
10
+ pnpm install
11
+ pnpm tauri dev # Rust 처음 빌드는 5~10분 — 캐시되면 이후 5~10초
12
+ ```
13
+
14
+ Rust toolchain (`cargo`, `rustc`) 이 시스템에 설치되어 있어야 합니다. 없으면
15
+ https://rustup.rs/ 참고.
16
+
17
+ ## 프로덕션 빌드 전 체크리스트
18
+
19
+ 1. **Bundle identifier** — `tauri.conf.json` 의 `identifier: "app.{{tauri_crate_name}}.dev"` 를
20
+ 실제 도메인 기반 unique ID 로 교체 (예: `com.yourcompany.{{tauri_crate_name}}`).
21
+ 동일 ID 로 publish 된 다른 앱과 충돌 시 OS install 이 깨질 수 있음.
22
+ 2. **Icons** — `tauri.conf.json` 의 `bundle.icon: []` 가 비어 있습니다. 프로덕션 빌드 시:
23
+ - 1024x1024 PNG 준비 (square, 투명 배경 권장)
24
+ - 부모 디렉토리에서 `pnpm tauri icon path/to/source.png` 실행 — `src-tauri/icons/` 에
25
+ 플랫폼별 variants 자동 emit + `bundle.icon` 자동 채워짐
26
+ 3. **Capabilities** — `capabilities/default.json` 은 최소 권한. fs / dialog / shell 등
27
+ 확장 API 가 필요하면 https://v2.tauri.app/security/ 참고.
28
+ 4. **Window 옵션** — `tauri.conf.json` 의 `app.windows[0]` 에 `decorations`, `transparent`,
29
+ `alwaysOnTop` 등 추가 가능.
30
+
31
+ ## Rust 코드 추가
32
+
33
+ `src/lib.rs` 의 `invoke_handler![]` 안에 새 command 등록:
34
+
35
+ ```rust
36
+ #[tauri::command]
37
+ fn my_command(name: &str) -> String {
38
+ format!("Hello, {}!", name)
39
+ }
40
+
41
+ // run() 안의 .invoke_handler 에:
42
+ .invoke_handler(tauri::generate_handler![my_command])
43
+ ```
44
+
45
+ 프론트엔드에서:
46
+ ```ts
47
+ import { invoke } from '@tauri-apps/api/core';
48
+ const greeting = await invoke<string>('my_command', { name: 'World' });
49
+ ```
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "../gen/schemas/desktop-schema.json",
3
+ "identifier": "default",
4
+ "description": "기본 capability — 핵심 Tauri API 만 허용. 추가 권한 (fs / dialog / shell 등) 은 https://v2.tauri.app/security/ 참고해서 명시적 추가.",
5
+ "windows": [
6
+ "main"
7
+ ],
8
+ "permissions": [
9
+ "core:default",
10
+ "opener:default"
11
+ ]
12
+ }
@@ -0,0 +1,8 @@
1
+ #[cfg_attr(mobile, tauri::mobile_entry_point)]
2
+ pub fn run() {
3
+ tauri::Builder::default()
4
+ .plugin(tauri_plugin_opener::init())
5
+ .invoke_handler(tauri::generate_handler![])
6
+ .run(tauri::generate_context!())
7
+ .expect("error while running tauri application");
8
+ }
@@ -0,0 +1,6 @@
1
+ // Prevents additional console window on Windows in release.
2
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
+
4
+ fn main() {
5
+ {{tauri_crate_name}}_lib::run()
6
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://schema.tauri.app/config/2",
3
+ "productName": "{{project_name}}",
4
+ "version": "0.1.0",
5
+ "identifier": "app.{{tauri_crate_name}}.dev",
6
+ "build": {
7
+ "beforeDevCommand": "pnpm dev",
8
+ "devUrl": "http://localhost:5173",
9
+ "beforeBuildCommand": "pnpm build",
10
+ "frontendDist": "../dist"
11
+ },
12
+ "app": {
13
+ "windows": [
14
+ {
15
+ "title": "{{project_name}}",
16
+ "width": 1200,
17
+ "height": 800
18
+ }
19
+ ],
20
+ "security": {
21
+ "csp": null
22
+ }
23
+ },
24
+ "bundle": {
25
+ "active": true,
26
+ "targets": "all",
27
+ "icon": []
28
+ }
29
+ }
@@ -33,3 +33,29 @@
33
33
  --color-info-foreground: var(--info-foreground);
34
34
  --color-sidebar-bg: var(--sidebar-bg);
35
35
  }
36
+
37
+ /* sh-ui:webview-base-start — 모바일 브라우저 / Tauri WebView / PWA 공통 기본값.
38
+ * v0.88.1+: Tauri 통합 + iOS/Android 친화. 사용자가 텍스트 셀렉트 필요한 곳은 input/textarea/
39
+ * contenteditable 에 명시적으로 user-select: text 로 복원. 토큰이 아니라 reset 성 규칙이라
40
+ * @theme inline 밖에 둠. */
41
+ @layer base {
42
+ * {
43
+ -webkit-tap-highlight-color: transparent;
44
+ }
45
+ body {
46
+ -webkit-user-select: none;
47
+ user-select: none;
48
+ -webkit-touch-callout: none;
49
+ }
50
+ input,
51
+ textarea,
52
+ [contenteditable],
53
+ [contenteditable='true'] {
54
+ -webkit-user-select: text;
55
+ user-select: text;
56
+ }
57
+ html {
58
+ touch-action: manipulation;
59
+ }
60
+ }
61
+ /* sh-ui:webview-base-end */
@@ -33,3 +33,29 @@
33
33
  --color-info-foreground: var(--info-foreground);
34
34
  --color-sidebar-bg: var(--sidebar-bg);
35
35
  }
36
+
37
+ /* sh-ui:webview-base-start — 모바일 브라우저 / Tauri WebView / PWA 공통 기본값.
38
+ * v0.88.1+: Tauri 통합 + iOS/Android 친화. 사용자가 텍스트 셀렉트 필요한 곳은 input/textarea/
39
+ * contenteditable 에 명시적으로 user-select: text 로 복원. 토큰이 아니라 reset 성 규칙이라
40
+ * @theme inline 밖에 둠. */
41
+ @layer base {
42
+ * {
43
+ -webkit-tap-highlight-color: transparent;
44
+ }
45
+ body {
46
+ -webkit-user-select: none;
47
+ user-select: none;
48
+ -webkit-touch-callout: none;
49
+ }
50
+ input,
51
+ textarea,
52
+ [contenteditable],
53
+ [contenteditable='true'] {
54
+ -webkit-user-select: text;
55
+ user-select: text;
56
+ }
57
+ html {
58
+ touch-action: manipulation;
59
+ }
60
+ }
61
+ /* sh-ui:webview-base-end */
@@ -3,7 +3,11 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <!-- viewport-fit=cover: iOS safe-area inset / Android edge-to-edge / Tauri mobile 호환 -->
7
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
8
+ <!-- theme-color: 모바일 브라우저 / PWA / Tauri WebView 의 status bar 톤. dark default 에 맞춰 어두운 톤. -->
9
+ <meta name="theme-color" content="#0A0A0A" media="(prefers-color-scheme: dark)" />
10
+ <meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
7
11
  <title>sh-ui app</title>
8
12
  <script>
9
13
  // FOUC 차단 — ThemeProvider mount 전에 첫 paint 에 dark class 박기.