RepositoryMonitor๋ ๋จ์ผ Tauri 2 ํ๋ก์ธ์ค ์์์ Rust ๋ฐฑ์๋(์ ์ฅ์ ๋ฐ๊ฒฌยท์ํ ์ฝ๊ธฐยท์ง๊ณ)์ Svelte 5 ํ๋ก ํธ์๋(๋ ๋)๊ฐ IPC๋ก ์ฐ๊ฒฐ๋ ๊ตฌ์กฐ๋ค. ๋ฐ์ดํฐ๋ ๋ฐฑ์๋ โ ํ๋ก ํธ ๋จ๋ฐฉํฅ push(repos_updated ์ด๋ฒคํธ), ์ ์ด๋ ํ๋ก ํธ โ ๋ฐฑ์๋ command invoke๋ก๋ง ํ๋ฅธ๋ค.
- ๋ก์ปฌ ์ ์ฉ / ๋คํธ์ํฌ 0 โ
git statusยทsvn status๋ฑ read-only ๋ก์ปฌ ๋ช ๋ น๋ง. fetch/pull/push ์์. - VCS ์ถ์ํ โ
VcsKind { Git, Svn }๋ก git/svn์ ํต์ผ๋RepoStatus๋ก ํํ. reader๋ง ๋ถ๊ธฐ. - ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ โ ํ๋ก ํธ์ git ๋ก์ง์ด ์๋ค. ๋ฐฑ์๋๊ฐ ์ง์ค์ ์์ฒ(์ค๋ ์ท)์ push.
- ์์ ๋ก์ง ๋ถ๋ฆฌ โ ํ์ ๊ท์น(clean ์ ์ดยท์ ๋ ฌยทํํฐยท๊ทธ๋ฃน)๊ณผ ํ์ฑ(porcelain v2ยทsvn status)์ ์์ ํจ์๋ก ๋ถ๋ฆฌํด ๋จ์ ํ ์คํธ.
RepositoryMonitor/
โโโ src/ # Svelte 5 ํ๋ก ํธ
โ โโโ App.svelte # ์ง์
์ : store ์ด๊ธฐํ, EmptyState/Grid ๋ถ๊ธฐ, ์ปจํ
์คํธ ๋ฉ๋ด
โ โโโ lib/
โ โ โโโ types.ts # Rust ํ์
๋ฏธ๋ฌ(snake_case)
โ โ โโโ tauri.ts # invoke/listen ๋ํผ (๋ฐฑ์๋ ์์กด ๋จ์ผ ์ง์ )
โ โ โโโ logic.ts # ์์ ํ์ ๋ก์ง (clean/rank/filter/format/group)
โ โ โโโ store.svelte.ts # reactive store (repos_updated ๊ตฌ๋
, seq ํ๊ธฐ)
โ โ โโโ theme.svelte.ts # ๋คํฌ/๋ผ์ดํธ (system/light/dark, localStorage)
โ โโโ components/ # RepoCard ยท Grid ยท Header ยท Settings ยท EmptyState
โ โโโ app.css # CSS ๋ณ์ ํ๋ ํธ (๋ผ์ดํธ/๋คํฌ)
โโโ src-tauri/ # Rust ๋ฐฑ์๋ + Tauri ์
ธ
โ โโโ src/
โ โ โโโ main.rs # repositorymonitor::run() ํธ์ถ
โ โ โโโ lib.rs # ๋ชจ๋ ์ ์ธ + run() (Builder ๋ฐฐ์ )
โ โ โโโ model.rs # VcsKind ยท RepoRef ยท RepoStatus ยท RepoState ยท ActionKind ยท RepoSnapshot
โ โ โโโ config.rs # Config ยท TerminalApp ยท ๋ก๋/์ ์ฅ(๋ฒ์ ๋ยท๋ฐฑ์
)
โ โ โโโ discovery.rs # ๋ฃจํธ ์ค์บ ยท ์ ์ธ ๊ธ๋กญ ยท ์นดํ
๊ณ ๋ฆฌ ยท .git/.svn ํ์
โ โ โโโ git_reader.rs # porcelain v2 ํ์ + stash/state/worktree/fetch
โ โ โโโ svn_reader.rs # svn status/info ํ์ (๋ก์ปฌ ์ ์ฉ)
โ โ โโโ batch.rs # ๋น๋๊ธฐ ์ํ ๋ฐฐ์น (semaphore + timeout, vcs ๋์คํจ์น)
โ โ โโโ scheduler.rs # ํด๋ง ํ๋จ (should_run_poll)
โ โ โโโ emit_gate.rs # ์ค๋
์ท ๋ณ๊ฒฝ ๊ฐ์ง (should_emit)
โ โ โโโ snapshot.rs # ์คํจ repo ์ง์ ๊ฐ ๋จธ์ง
โ โ โโโ app_state.rs # AppState (.manage)
โ โ โโโ commands.rs # IPC ์ปค๋งจ๋ 5์ข
+ do_scan/do_refresh
โ โโโ tests/ # ํตํฉ ํ
์คํธ (discovery/git_reader/batch/svn)
โ โโโ capabilities/ # Tauri ๊ถํ (window/event/dialog)
โ โโโ tauri.conf.json # ๋จ์ผ main ์๋์ฐ, identifier com.dgitx.repositorymonitor
โโโ docs/ # ๋ณธ ๋ฌธ์๋ค
| ๋ชจ๋ | ์ฑ ์ |
|---|---|
config |
Config(rootsยทmanual_pathsยทexclude_globsยทpoll_interval_secsยทscan_depthยทstale_fetch_daysยทterminal_app) ๋ก๋/์ ์ฅ. ๊ฒฝ๋ก๋ dirs_next::config_dir()/RepositoryMonitor/config.json. forward-compat ์ญ์ง๋ ฌํ(#[serde(default)]), ์์ ์ .bak ๋ฐฑ์
ํ ๊ธฐ๋ณธ๊ฐ ์ฌ์์ฑ |
discovery |
๋ฃจํธ๋ฅผ walk(๊น์ด ์ ํยทnode_modules/target/Pods/.build/.git/.svn pruneยท์ฌ๋งํฌ ๋ฏธ์ถ์ )ํ๋ฉฐ .git(๋๋ ํ ๋ฆฌ)=git, .svn=svn ์ผ๋ก RepoRef{path,name,category,vcs} ์ฐ์ถ. ์ ์ธ ๊ธ๋กญ์ repo ์ ๋๊ฒฝ๋ก์ globset(globstarยท๋์๋ฌธ์๋ฌด์) ๋งค์นญ. ์นดํ
๊ณ ๋ฆฌ=๋ฃจํธ ๊ธฐ์ค ์๋๊ฒฝ๋ก ์ฒซ ์ธ๊ทธ๋จผํธ |
git_reader |
git status --porcelain=v2 --branch ๋จ์ผ ํ์ฑ(๋ธ๋์นยทupstreamยทahead/behindยทXY ์ฝ๋). ๋ณด์กฐ: stash(stash list ์ค ์), state ๋ง์ปค(rev-parse --git-path), worktree(worktree list), last_fetch(FETCH_HEAD mtime) |
svn_reader |
svn status(๋ก์ปฌ) ํ์ฑ(M/A/D/R/!/~โmodified, ?โuntracked, Cโconflict) + svn info --show-item relative-url๋ก ๋ธ๋์น ๋์ถ. ahead/behind/staged/stash ์์ |
batch |
RepoRef[]๋ฅผ tokio ๋ธ๋กํน ํ์คํฌ๋ก ๋ณ๋ ฌ ์คํ(Semaphore ๋์ ์ํ 8, repo๋น 5์ด timeout). repo.vcs๋ก git/svn reader ๋์คํจ์น. ํ์์์/์คํจ๋ error RepoStatus |
scheduler |
should_run_poll(polling_active, in_flight) โ ์ฐฝ ํฌ์ปค์ค ์ค์ด๊ณ ์งํ ๋ฐฐ์น๊ฐ ์์ ๋๋ง ํด๋ง. ์ค์ ๋ฃจํ๋ lib.rs setup()์ด WindowEvent::Focused๋ก polling_active๋ฅผ ํ ๊ธํ๋ฉฐ ๊ตฌ๋ |
emit_gate |
should_emit(prev, next) โ last_checked๋ฅผ ์ ์ธํ ์๋ฏธ ๋น๊ต๋ก ๋ฌด์๋ฏธ emit ์ฐจ๋จ |
snapshot |
merge_failed_with_previous โ ์ผ์ ์คํจ repo๋ ์ง์ ์ค๋
์ท ์์น๋ฅผ ์ ์งํ๊ณ error/last_checked๋ง ๊ฐฑ์ |
app_state |
AppState{ config, repos, last_snapshot, polling_active, in_flight, seq } โ .manage(Arc<AppState>)๋ก ๊ณต์ |
commands |
IPC 5์ข
+ do_scan/do_refresh(ํ
์คํธ ๊ฐ๋ฅํ๋๋ก emit์ ์ฝ๋ฐฑ์ผ๋ก ๋ถ๋ฆฌํ do_refresh_inner) |
- ๋จ์ผ in-flight โ
do_refresh๋in_flight๋ฅผswap(true)๋ก ํ๋(์ด๋ฏธ ์งํ ์ค์ด๋ฉด coalesce). RAII ๊ฐ๋(Drop)๋ก ์ด๋ค ๊ฒฝ๋ก๋ก ๋น ์ ธ๋false๋ณต์ โ "in_flight ์๊ตฌ true" ๋ฐ๋๋ฝ ๋ฐฉ์ง. - ํฌ์ปค์ค ๊ฒ์ดํ
โ
WindowEvent::Focused(true)โ ํด๋ง ON + ์ฆ์ 1ํ,Focused(false)โ OFF. ์ฃผ๊ธฐ๋poll_interval_secs(clamp 10โ300). - seq โ emit๋ง๋ค
seq์ฆ๊ฐ. ํ๋ก ํธ๋seq < lastSeq์ธ ์ค๋๋ ์ค๋ ์ท์ ํ๊ธฐ.
| ํ์ผ | ์ฑ ์ |
|---|---|
lib/types.ts |
Rust serde ์ง๋ ฌํ์ 1:1 ๋ฏธ๋ฌ(snake_case ํ๋, enum snake_case). ์ด๋ฒคํธ payload๋ camelCase ์๋๋ณํ์ด ์์ผ๋ฏ๋ก snake_case ์ ์ง |
lib/tauri.ts |
invoke/listen ๋ํผ โ ๋ฐฑ์๋ ์์กด ๋จ์ผ ์ง์ . ์ปค๋งจ๋ ์ธ์๋ Tauri๊ฐ camelCase ๋ณํ(repo_pathโrepoPath) |
lib/logic.ts |
isClean ๋จ์ผ ์ ์ด, rank+compareRepos(์ ๋ ฌ), filterProblems, formatFetched/isStale, groupByCategory, summarize โ ์ ๋ถ ์์, vitest ์ปค๋ฒ |
lib/store.svelte.ts |
$state repos/config/lastSeq, init(๊ตฌ๋
ยทconfig ๋ก๋)/dispose, refresh/rescan/saveConfig/excludeRepo |
lib/theme.svelte.ts |
system/light/dark ์ ํ์ localStorage ์์ + <html data-theme> ์ ์ฉ |
components/RepoCard |
ยง7 ์ ํธ ๋ ๋ + ํธ๋ฒ ์ก์ + ์ฐํด๋ฆญ(onContext) + clean/dirty/conflict ๊ฐ์กฐ |
components/Grid |
filterProblems โ ๊ฒ์ โ groupByCategory(๋ด๋ถ compareRepos ์ ๋ ฌ) |
components/Header |
๊ฒ์ยท๋ฌธ์ ๋งยทํ ๋งํ ๊ธยท์๋ก๊ณ ์นจยท์ค์ ยท์์ฝ |
components/Settings |
๋ฃจํธ(dialog ํด๋์ ํ)ยท์ ์ธ๊ธ๋กญยท์ฃผ๊ธฐยท๊น์ดยทstaleยทํฐ๋ฏธ๋ ์ฑ |
components/EmptyState |
๋ฃจํธ 0๊ฐ first-run CTA |
- M1 ๋ฐฑ์๋ ์ฝ์ด(config/discovery/git_reader)
- M2 Tauri ํตํฉ(AppStateยทIPCยท์ด๋ฒคํธยท์ค์ผ์ค๋ฌยท๋ฐฐ์นยท์ก์ )
- M3 Svelte 5 ํ๋ก ํธ(๊ทธ๋ฆฌ๋ UI)
- M4 SVN ์ถ์ (VcsKind ์ถ์ํยทsvn_reader)
- ์ดํ UI ๋ฐ๋ณต: ๋คํฌ/๋ผ์ดํธ, ๋นํด๋ฆฐ ๊ฐ์กฐ, ์ฐํด๋ฆญ ์ปจํ ์คํธ ๋ฉ๋ด, ๋ฆฌ์คํธ ๊ฑฐํฐ
๋ณธ ํ๋ก์ ํธ๋ M1~M4 ๊ธฐ๊ฐ ๋์
GitMonitor๋ผ๋ ์ด๋ฆ์ด์๊ณ , SVN ์ถ๊ฐ ์ดํRepositoryMonitor๋ก ๊ฐ๋ช ๋์๋ค.docs/superpowers/์ ์ค๊ณ์ยท๊ณํ์๋ ๊ทธ ์์ ์ ๊ธฐ๋ก์ด๋ผ ์ ์ด๋ฆ์ ๋ณด์กดํ๋ค.