Skip to content

04 quest game ui

pkdje edited this page Jun 4, 2026 · 2 revisions

Quest Game UI Overlay โ€” ์„ค๊ณ„ (Week 2 ์ถ”๊ฐ€ ๊ฒฐ์ •)

์ž‘์„ฑ: ๋ฐ•๋™์ œ (pkdje) ยท 2026-06-02 ยท Week 2 ์‹œ์ž‘ ์‹œ์ ์— ์ถ”๊ฐ€๋œ ๊ฒฐ์ •

1. ๋™๊ธฐ

๊ธฐ์กด 01-project-plan.md ์˜ DAG ์‹œ๊ฐํ™”๋Š” "๋…ธ๋“œ + ์—ฃ์ง€ + ์ƒํƒœ๋ณ„ ์ƒ‰" ์˜ ๊น”๋”ํ•œ todo ํ†ค์ด์—ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜(์„ ํ–‰ ์ž‘์—… ์™„๋ฃŒ โ†’ ํ›„์† ์ž‘์—… ํ•ด๊ธˆ)์„ ๊ฒŒ์ž„ ํ€˜์ŠคํŠธ ์ง„ํ–‰ ๋А๋‚Œ์œผ๋กœ ์žฌํ•ด์„ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฒฐ์ •ํ•จ. ๋™์‹œ์— ๊ธฐ์กด ๋ชจ๋ธ์ด ๋‹ค๋ฃจ์ง€ ์•Š๋˜ "์„ ํ›„๊ด€๊ณ„๊ฐ€ ์—†๋Š” ๋…๋ฆฝ ํƒœ์Šคํฌ"์˜ ๋ถ„๋ฅ˜๋„ ๋ช…ํ™•ํžˆ ํ•œ๋‹ค.

์ด ๋ฌธ์„œ๋Š” ๊ทธ ๊ฒฐ์ •์„ ๊ตฌ์ฒดํ™”ํ•œ๋‹ค. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์€ ๊ฑฐ์˜ ๊ทธ๋Œ€๋กœ ๋‘๊ณ , ์‹œ๊ฐ/์ธํ„ฐ๋ž™์…˜ ๋ ˆ์ด์–ด๋ฅผ ํ€˜์ŠคํŠธ ํ†ค์œผ๋กœ ํ†ต์ผํ•œ๋‹ค.

2. ํ•ฉ์˜๋œ ๊ฒฐ์ • (์š”์•ฝ)

ํ•ญ๋ชฉ ๊ฒฐ์ •
๊ฒŒ์ด๋ฏธํ”ผ์ผ€์ด์…˜ ๊ฐ•๋„ C โ€” ์ค‘๊ฐ„ (ํ€˜์ŠคํŠธ ์นด๋“œ ํ†ค). ์นด๋“œ ๋””์ž์ธ + ๋ผ๋ฒจ(REWARD, LOCKED, CLEARED) + ํ•ด๊ธˆ ์• ๋‹ˆ๋ฉ”์ด์…˜ + QUEST LINE ์ง„ํ–‰๋ฅ .
๋…๋ฆฝ ํƒœ์Šคํฌ ๋ถ„๋ฅ˜ B + D. ์ขŒ์ธก ํŒจ๋„ ํ•˜๋‹จ์— ๋ณ„๋„ "์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ" ์„น์…˜. ์ƒˆ ํƒœ์Šคํฌ ์ƒ์„ฑ ์‹œ prerequisite ๋น„์šฐ๋ฉด ์ž๋™ ๋ถ„๋ฅ˜.
ํ€˜์ŠคํŠธ ๋ผ์ธ ์ •์˜ DAG ์˜ ์—ฐ๊ฒฐ ์ปดํฌ๋„ŒํŠธ = 1 ํ€˜์ŠคํŠธ ๋ผ์ธ. ํฌ๊ธฐ 1 = ์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ.
REWARD ํ…์ŠคํŠธ DAG ๊ตฌ์กฐ์—์„œ ์ž๋™ ์ƒ์„ฑ (์ง์† ํ›„์† ๋…ธ๋“œ ๊ธฐ๋ฐ˜). ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์—†์Œ.
์•„์ด์ฝ˜ ์„ ํƒ emoji ํ•„๋“œ (icon?: string). ๊ธฐ๋ณธ โšก. ์ƒˆ ํƒœ์Šคํฌ/ํŽธ์ง‘ ์‹œ picker.
์ถ”์ฒœ ํŒจ๋„ ํ†ตํ•ฉ A โ€” ์ถ”์ฒœ ํŒจ๋„์„ "โ–ถ NEXT QUEST" ๋กœ ๋ฆฌ์Šคํ‚จ. ์ „์ฒด UI ํ†ค ํ†ต์ผ.
์Šค์ฝ”ํ”„ Week 3 ์•ˆ์— ๋ผ์›€. #16(๊ธฐ์กด ๋…ธ๋“œ polish) ํก์ˆ˜ + ์‹ ๊ทœ #21โ€“#23 ์ถ”๊ฐ€.

3. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๋ณ€๊ฒฝ

type Task = {
  // ... ๊ธฐ์กด ํ•„๋“œ ๊ทธ๋Œ€๋กœ
  icon?: string   // ์ถ”๊ฐ€. ์ด๋ชจ์ง€ 1์ž. ๋ฏธ์ง€์ • ์‹œ ๋ Œ๋” ์‹œ โšก fallback.
}
  • ๋‹ค๋ฅธ ํ•„๋“œ๋Š” ๋ณ€๋™ ์—†์Œ.
  • ๋ชจ๋“  ํ€˜์ŠคํŠธ ๋ผ์ธ/REWARD ์ •๋ณด๋Š” ์ €์žฅํ•˜์ง€ ์•Š๊ณ  DAG ๊ตฌ์กฐ์—์„œ ํŒŒ์ƒ ๊ณ„์‚ฐ.
  • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ถˆํ•„์š” (๊ธฐ์กด task ๋“ค์€ icon ์—†์ด๋„ โšก ๋กœ ๋ Œ๋”).

4. ํŒŒ์ƒ ๊ณ„์‚ฐ (lib/quest.ts)

๋ชจ๋‘ ์ˆœ์ˆ˜ ํ•จ์ˆ˜. TDD ๋Œ€์ƒ.

// 4.1 ์—ฐ๊ฒฐ ์ปดํฌ๋„ŒํŠธ (Union-Find ๋˜๋Š” BFS)
function connectedComponents(tasks: Task[]): Component[]
// Component { id: string, taskIds: TaskId[] }
// ์‚ฌ์ด์ฆˆ 1 = ์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ.

// 4.2 ์ปดํฌ๋„ŒํŠธ ์ง„์ฒ™๋„
function questLineProgress(componentId, components, tasks): { done: number, total: number }

// 4.3 REWARD ํ…์ŠคํŠธ ์ž๋™ ์ƒ์„ฑ
function rewardText(task: Task, tasks: Task[]): string
// ๊ทœ์น™:
//   ์ง์† ํ›„์† 1๊ฐœ   โ†’ "{์ด๋ฆ„} ํ•ด๊ธˆ"
//   ์ง์† ํ›„์† 2~3๊ฐœ โ†’ "{์ด๋ฆ„1}, {์ด๋ฆ„2} ํ•ด๊ธˆ"
//   ์ง์† ํ›„์† 4๊ฐœ+ โ†’ "{N}๊ฐœ ์ž‘์—… ํ•ด๊ธˆ"
//   ํ›„์† ์—†์Œ + ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ > 1 โ†’ "๋ฉ”์ธ ํ€˜์ŠคํŠธ X/Y ์™„๋ฃŒ"
//   ํ›„์† ์—†์Œ + ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ == 1 โ†’ "์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ ์™„๋ฃŒ"

// 4.4 ํ•ด๊ธˆ ๊ฐ์ง€ (์• ๋‹ˆ๋ฉ”์ด์…˜ ํŠธ๋ฆฌ๊ฑฐ)
function detectUnlocks(prev: Task[], next: Task[]): TaskId[]
// ์ด์ „์— ๋ง‰ํž˜(prereq ๋ฏธ์™„), ์ง€๊ธˆ์€ ready ์ธ task ๋“ค.
// ๋‹ค์Œ ๋ Œ๋” ์‚ฌ์ดํด์—์„œ 0.9์ดˆ ํŽ„์Šค ์• ๋‹ˆ๋ฉ”์ด์…˜.

๋ณต์žก๋„: ๋ชจ๋‘ O(V+E). ์ˆ˜๋ฐฑ ๋…ธ๋“œ ๊ธฐ์ค€ ์ฆ‰์‹œ ๋๋‚จ.

5. UI ๋ณ€๊ฒฝ

5.1 DAG ์บ”๋ฒ„์Šค โ€” ํ€˜์ŠคํŠธ ์นด๋“œ ๋…ธ๋“œ

React Flow ์˜ Custom Node ๋กœ ๊ตฌํ˜„. ๊ธฐ์กด ๋‹จ์ˆœ ๋ฐ•์Šค ๋…ธ๋“œ ๊ต์ฒด.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐ŸŽจ priority โ˜…โ˜…โ˜…โ˜…โ˜…      โ”‚  ์•„์ด์ฝ˜ + ์šฐ์„ ์ˆœ์œ„
โ”‚ ๋””์ž์ธ                   โ”‚  ์ œ๋ชฉ (๊ตต๊ฒŒ)
โ”‚ D-3 ยท score 7.4          โ”‚  ๋งˆ๊ฐ + (์ถ”์ฒœ ํ›„๋ณด์ผ ๋•Œ) ์ ์ˆ˜
โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€       โ”‚
โ”‚ โ–ท REWARD                โ”‚
โ”‚ ํ”„๋ก ํŠธ, ๋ฐฑ์—”๋“œ ํ•ด๊ธˆ        โ”‚  ์ž๋™ ์ƒ์„ฑ
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

์ƒํƒœ๋ณ„ ๋ณ€ํ˜•:

์ƒํƒœ ์‹œ๊ฐ
todo (ready) ํฐ ๋ฐฐ๊ฒฝ, ๊ธฐ๋ณธ ํ…Œ๋‘๋ฆฌ
todo (locked โ€” prereq ๋ฏธ์™„) ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ, opacity 0.6, ์ขŒ์ƒ๋‹จ ๐Ÿ”’ LOCKED ๋ฐฐ์ง€
in_progress ํŒŒ๋ž€ ํ†ค
done ์ดˆ๋ก ํ†ค, ์šฐ์ƒ๋‹จ โœ“ CLEARED ๋ฐฐ์ง€
just unlocked (1 ์‚ฌ์ดํด) 0.9 ์ดˆ scale+rotate+๋…ธ๋ž€ ๊ฐ•์กฐ ํŽ„์Šค

5.2 ์ขŒ์ธก ํŒจ๋„ โ€” "ํ€˜์ŠคํŠธ ๋กœ๊ทธ"

๊ธฐ์กด ์ถ”์ฒœ ํŒจ๋„์„ NEXT QUEST ๋กœ ๋ฆฌ์Šคํ‚จ. ๊ทธ ์•„๋ž˜ SIDE QUESTS ์„น์…˜ ์ถ”๊ฐ€. ๋งจ ์•„๋ž˜ ์ง„ํ–‰ ํ˜„ํ™ฉ.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ–ถ NEXT QUEST (Top 3)   โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚ โ”‚ ๐ŸŽจ ๋””์ž์ธ score 8.2โ”‚   โ”‚  โ† ์นด๋“œ ํ†ค ํ†ต์ผ
โ”‚ โ”‚ โ˜…โ˜…โ˜…โ˜…โ˜…            โ”‚   โ”‚
โ”‚ โ”‚ โ–ท ํ”„๋ก ํŠธ, ํ†ตํ•ฉ ํ•ด๊ธˆ โ”‚   โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚ ...                    โ”‚
โ”‚                        โ”‚
โ”‚ โšก SIDE QUESTS (5)     โ”‚  โ† ์‹ ๊ทœ
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚ โ”‚ โšก ์šฐ์œ  ์‚ฌ๊ธฐ      โ”‚   โ”‚  โ† ๋‹จ์ผ ๋…ธ๋“œ ์ปดํฌ๋„ŒํŠธ
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚ ...                    โ”‚
โ”‚                        โ”‚
โ”‚ โ”€ ์ง„ํ–‰ ํ˜„ํ™ฉ โ”€           โ”‚
โ”‚ ๋ฉ”์ธ ํ€˜์ŠคํŠธ 4/12       โ”‚
โ”‚ ์‚ฌ์ด๋“œ 2/5             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

NEXT QUEST ์นด๋“œ:

  • ๊ธฐ์กด ์ถ”์ฒœ ์ ์ˆ˜ + ๋ณ„ + ํ•€/snooze ์•ก์…˜ (#15) ๊ทธ๋Œ€๋กœ ์œ ์ง€
  • ์‹œ๊ฐ๋งŒ ํ€˜์ŠคํŠธ ์นด๋“œ ํ†ค
  • ํด๋ฆญ โ†’ React Flow ์บ”๋ฒ„์Šค์—์„œ ํ•ด๋‹น ๋…ธ๋“œ๋กœ ์นด๋ฉ”๋ผ ์ด๋™ + ๊ฐ•์กฐ

SIDE QUESTS ์นด๋“œ:

  • icon + ์ œ๋ชฉ๋งŒ (์ ์ˆ˜/๋ณ„ ํ‘œ์‹œ ์•ˆ ํ•จ, ์งง์€ ๊ฐ€๋กœ ์นด๋“œ)
  • ํด๋ฆญ โ†’ ์บ”๋ฒ„์Šค์—์„œ ํ•ด๋‹น ๋…ธ๋“œ ๊ฐ•์กฐ

์ง„ํ–‰ ํ˜„ํ™ฉ ๋ฐ•์Šค:

  • ๋ฉ”์ธ ํ€˜์ŠคํŠธ: ์‚ฌ์ด์ฆˆ โ‰ฅ 2 ์ธ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ done/total ํ•ฉ์‚ฐ
  • ์‚ฌ์ด๋“œ: ์‚ฌ์ด์ฆˆ == 1 ์ธ ์ปดํฌ๋„ŒํŠธ์˜ done/total ํ•ฉ์‚ฐ

5.3 ๋ชจ๋‹ฌ / ๋“œ๋กœ์–ด

  • ์ƒˆ ํƒœ์Šคํฌ ๋ชจ๋‹ฌ: ์ œ๋ชฉ ์ž…๋ ฅ ์œ„์— ์ด๋ชจ์ง€ picker 1์ค„. ์ถ”์ฒœ 8 ~ 12 ๊ฐœ (โšก ๐ŸŽจ ๐Ÿ’ป ๐Ÿ”ฌ ๐Ÿ“ ๐Ÿƒ ๐ŸŽฏ ๐Ÿ›  ๐ŸŽ ๐Ÿ“ž ๐Ÿด ๐Ÿ›’) + "๋‹ค๋ฅธ ์ด๋ชจ์ง€" ์ž…๋ ฅ. ๊ธฐ๋ณธ โšก.
  • ํŽธ์ง‘ ๋“œ๋กœ์–ด: ๊ฐ™์€ picker ์ถ”๊ฐ€.
  • prerequisite ๋น„์–ด์žˆ๋Š” ์ƒํƒœ๋กœ ์ €์žฅ โ†’ ์ž๋™์œผ๋กœ ์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ ๋ถ„๋ฅ˜ (๋ณ„๋„ ํ† ๊ธ€ ์—†์Œ).

5.4 ๊ธฐ์กด ์ธํ„ฐ๋ž™์…˜ ๋ณด์กด

  • ๋…ธ๋“œ ํด๋ฆญ โ†’ ํŽธ์ง‘ ๋“œ๋กœ์–ด
  • ๋…ธ๋“œ ๋”๋ธ”ํด๋ฆญ โ†’ ์ƒํƒœ ์ˆœํ™˜
  • ๋…ธ๋“œ ๋ ๋“œ๋ž˜๊ทธ โ†’ ์˜์กด์„ฑ ์—ฃ์ง€ ์ƒ์„ฑ

6. ์Šค์ฝ”ํ”„ ๋ณ€๊ฒฝ

Week 2 โ€” ์ตœ์†Œ ์˜ํ–ฅ

  • #6 Task CRUD API: icon?: string ํ•„๋“œ ์ถ”๊ฐ€ (์ˆ˜์šฉ/์ €์žฅ๋งŒ, ๊ฒ€์ฆ ๋ถˆํ•„์š”).

Week 3 โ€” 6 โ†’ 8 ์ด์Šˆ

์ด์Šˆ ๋ณ€๊ฒฝ
#15 ์ถ”์ฒœ ํ•€ / Snooze ๊ทธ๋Œ€๋กœ
#16 ๋…ธ๋“œ ์‹œ๊ฐ ๋‹ค๋“ฌ๊ธฐ ์‚ญ์ œ, #22 ์— ํก์ˆ˜
#17 ๊ฐ€์ค‘์น˜ ์Šฌ๋ผ์ด๋” ๊ทธ๋Œ€๋กœ
#18 ๋นˆ/๋กœ๋”ฉ/์—๋Ÿฌ UI ๊ทธ๋Œ€๋กœ
#19 ๋ฐฐํฌ ๊ทธ๋Œ€๋กœ
#20 ํšŒ๊ณ  + ์›Œํฌํ”Œ๋กœ์šฐ doc ๊ทธ๋Œ€๋กœ
#21 lib/quest.ts + ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (์‹ ๊ทœ) ํŒŒ์ƒ ํ•จ์ˆ˜ 4๊ฐœ. TDD. โ‰ˆ 1์ผ.
#22 ํ€˜์ŠคํŠธ ์นด๋“œ ๋…ธ๋“œ (React Flow custom node) (์‹ ๊ทœ) ์นด๋“œ ๋””์ž์ธ + ์ƒํƒœ๋ณ„ ๋ณ€ํ˜• + ํ•ด๊ธˆ ํŽ„์Šค ์• ๋‹ˆ๋ฉ”์ด์…˜. โ˜…/D-n/๋ง‰ํž˜ ํ๋ฆผ ํก์ˆ˜. โ‰ˆ 1.5์ผ.
#23 ์ขŒ์ธก ํŒจ๋„ ๋ฆฌ์Šคํ‚จ + ์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ + ์ด๋ชจ์ง€ picker (์‹ ๊ทœ) NEXT QUEST ๋ฆฌ์Šคํ‚จ, SIDE QUESTS ์„น์…˜, ์ง„ํ–‰ ํ˜„ํ™ฉ, ๋ชจ๋‹ฌ/๋“œ๋กœ์–ด picker. โ‰ˆ 1์ผ.

ํ•ฉ๊ณ„: +2 ~ 2.5์ผ.

์˜์กด์„ฑ

#21 (lib/quest.ts) โ”€โ”ฌโ”€ #22 (์นด๋“œ ๋…ธ๋“œ โ€” REWARD ๋“ฑ ์†Œ๋น„)
                    โ””โ”€ #23 (์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ ๋ถ„๋ฅ˜ โ€” connectedComponents ์†Œ๋น„)

#22 โ†โ”€ #8 (๊ธฐ์กด DAG ๋ Œ๋”) ์˜ ํ™•์žฅ
#23 โ†โ”€ #14 (๊ธฐ์กด ์ถ”์ฒœ ํŒจ๋„) ์˜ ํ™•์žฅ

7. ์œ„ํ—˜

์œ„ํ—˜ ๋Œ€์‘
Week 3 ๋นก๋นกํ•จ (+2 ~ 2.5์ผ) Week 2 ๊ฐ€ 1์ผ์ด๋ผ๋„ ๋ฐ€๋ฆฌ๋ฉด #15(ํ•€/snooze) ๋˜๋Š” #17(๊ฐ€์ค‘์น˜ ์Šฌ๋ผ์ด๋”) ๋ฅผ Week 4 ํ›„ ์ถ”๊ฐ€ ์ž‘์—…์œผ๋กœ ๋ฏธ๋ฃธ.
ํ€˜์ŠคํŠธ ์นด๋“œ ํ†ค์ด ํฐ ๊ทธ๋ž˜ํ”„์—์„œ ์‹œ๊ฐ ๋…ธ์ด์ฆˆ ์ŠคํŒŒ์ดํฌ #2 (React Flow + dagre) ์—์„œ ์นด๋“œ ํ†ค mock ์œผ๋กœ ๊ฒ€์ฆ. 50+ ๋…ธ๋“œ์—์„œ ๊ฒฌ๋””๋Š”์ง€.
ํ•ด๊ธˆ ํŽ„์Šค ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋งค ํด๋ฆญ๋งˆ๋‹ค ๋ฐœ๋™๋˜์–ด ์‚ฐ๋งŒ detectUnlocks ๊ฐ€ ์ƒํƒœ๊ฐ€ just-transitioned ์ธ ๊ฒฝ์šฐ๋งŒ ๋ฐ˜ํ™˜. ์ผ๋ฐ˜ ๋ฆฌ๋ Œ๋”์—๋Š” ํŠธ๋ฆฌ๊ฑฐ ์•ˆ ๋จ.
REWARD ํ…์ŠคํŠธ๊ฐ€ ํ›„์† ์ด๋ฆ„ ๊ธธ๋ฉด ์นด๋“œ ๊นจ์ง 2 ~ 3๊ฐœ ์ด๋ฆ„ ์ง์ ‘ ๋…ธ์ถœ ํ›„ ๊ทธ ์ด์ƒ์€ "{N}๊ฐœ ํ•ด๊ธˆ" ์œผ๋กœ ์ถ•์•ฝ.
์ด๋ชจ์ง€ picker ๊ฐ€ ๋ชจ๋ฐ”์ผ ํ‚ค๋ณด๋“œ UX ์™€ ์ถฉ๋Œ 1์ฃผ์ฐจ์— ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜•์€ ์Šค์ฝ”ํ”„ ์™ธ. ๋ฐ์Šคํฌํƒ‘ ๊ธฐ์ค€๋งŒ.

8. Implementation Hints (Spike #2 ๊ฒฐ๊ณผ ๋ฐ˜์˜)

Spike #2 (React Flow + dagre) ์—์„œ ๊ฒ€์ฆ๋œ ๊ตฌ์ฒด ์„ค์ •๊ฐ’. #8 (DAG ๋ Œ๋”), #22 (ํ€˜์ŠคํŠธ ์นด๋“œ ๋…ธ๋“œ) ๊ตฌํ˜„ ์‹œ ์‹œ์ž‘์ ์œผ๋กœ ์‚ฌ์šฉ. ์ „์ฒด findings: spike/SPIKE.md (๋ธŒ๋žœ์น˜).

8.1 dagre ์ž๋™ ๋ ˆ์ด์•„์›ƒ ์„ค์ •

const dagreConfig = {
  rankdir: 'TB',           // Top โ†’ Bottom (LR ๋„ ์˜ต์…˜)
  nodesep: 12,             // ๊ฐ€๋กœ ๊ฐ„๊ฒฉ. ๊ธฐ๋ณธ 30 ๋„ˆ๋ฌด spacious
  ranksep: 28,             // ์„ธ๋กœ ๊ฐ„๊ฒฉ. ๊ธฐ๋ณธ 60 ๋„ˆ๋ฌด spacious
  ranker: 'tight-tree',    // 'network-simplex' ๋ณด๋‹ค ์ปดํŒฉํŠธ
  marginx: 16,
  marginy: 16,
};

์ด ๊ฐ’์—์„œ 50 ~ 80 ๋…ธ๋“œ ๊ทธ๋ž˜ํ”„๊ฐ€ ์‹œ๊ฐ์ ์œผ๋กœ ๊ฒฌ๋”ค. ๋” spacious ๊ฐ€ ํ•„์š”ํ•ด์ง€๋ฉด ์‚ฌ์šฉ์ž ์„ค์ •์œผ๋กœ ๋…ธ์ถœ ๊ฐ€๋Šฅ (3์ฃผ ์Šค์ฝ”ํ”„ ์™ธ).

8.2 ํ€˜์ŠคํŠธ ์นด๋“œ dagre ๋…ธ๋“œ ํฌ๊ธฐ

dagre ์— ์ „๋‹ฌํ•  ๋…ธ๋“œ ๋ฐ•์Šค ์‚ฌ์ด์ฆˆ (์‹ค์ œ ๋ Œ๋” ์นด๋“œ ์‚ฌ์ด์ฆˆ์™€ ์ผ์น˜):

g.setNode(taskId, { width: 150, height: 76 });

ํฐํŠธ: ๋ณธ๋ฌธ 11px / ๋ฉ”ํƒ€ยทREWARD 9px. ํŒจ๋”ฉ 6/10 px. (์ž์„ธํ•œ ์นด๋“œ ๋””์ž์ธ์€ ยง5.1.)

8.3 React Flow fitView ์„ค์ •

<ReactFlow
  fitView
  fitViewOptions={{
    padding: 0.08,
    minZoom: 0.6,   // ๊ธ€์ž ๊ฐ€๋…์„ฑ ํ•œ๊ณ„
    maxZoom: 1.5,
  }}
  minZoom={0.1}      // ์‚ฌ์šฉ์ž ์คŒ ์•„์›ƒ ํ•œ๊ณ„
  maxZoom={2}
/>

fitViewOptions.minZoom: 0.6 ์ด ์ค‘์š”. ์ด ๊ฐ’ ์—†์ด fitView ์‚ฌ์šฉํ•˜๋ฉด ๋…ธ๋“œ ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚ ์ˆ˜๋ก ๊ธ€์ž ์•ˆ ๋ณด์ผ ์ •๋„๋กœ ์ถ•์†Œ๋จ.

8.4 MiniMap ๊ฐ€์‹œ์„ฑ

<MiniMap
  pannable          // ๋ฏธ๋‹ˆ๋งต ๋“œ๋ž˜๊ทธ๋กœ ๋ฉ”์ธ ๋ทฐํฌํŠธ ์ด๋™
  zoomable          // ์Šคํฌ๋กค๋กœ ์คŒ
  nodeStrokeColor="#37474f"
  nodeStrokeWidth={1}
  maskColor="rgba(15,23,42,0.55)"
  style={{
    backgroundColor: '#fff',
    border: '2px solid #455a64',
    borderRadius: 6,
    boxShadow: '0 4px 12px rgba(0,0,0,0.18)',
  }}
  nodeColor={(n) =>
    n.data?.status === 'done' ? '#a5d6a7'
      : n.id === selectedId ? '#ff6f00'
      : '#90a4ae'
  }
/>

pannable + zoomable ๋กœ ๋ฏธ๋‹ˆ๋งต์ด ๋‹จ์ˆœ ํ‘œ์‹œ์—์„œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ปจํŠธ๋กค๋กœ ๊ฒฉ์ƒ. ํ…Œ๋‘๋ฆฌ/๊ทธ๋ฆผ์ž/ํฐ๋ฐฐ๊ฒฝ ์œผ๋กœ ๋ฉ”์ธ ํ™”๋ฉด๊ณผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ.

8.5 ๋…ธ๋“œ ํด๋ฆญ โ†’ ์—ฐ๊ฒฐ ์—ฃ์ง€/๋…ธ๋“œ ๊ฐ•์กฐ (์ธ์ง€ ๋ถ€ํ•˜ ํ•ด์†Œ)

์—ฃ์ง€ ๊ต์ฐจ๋Š” DAG ๊ตฌ์กฐ์—์„œ ์˜ค๋Š” inherent ํŠน์„ฑ์ด๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์™„์ „ํžˆ ์—†์•จ ์ˆ˜ ์—†์Œ. ๋Œ€์‹  "์ง€๊ธˆ ๋ณด๊ณ  ์‹ถ์€ ์—ฃ์ง€๋งŒ" ๋ถ„๋ฆฌํ•ด์„œ ๋ณด๋Š” UX ๋กœ ํ•ด์†Œ:

const [selectedId, setSelectedId] = useState<string | null>(null);

// onNodeClick โ†’ setSelectedId(node.id)
// onPaneClick โ†’ setSelectedId(null)

const connectedIds = useMemo(() => {
  if (!selectedId) return null;
  const s = new Set([selectedId]);
  edges.forEach(e => {
    if (e.source === selectedId) s.add(e.target);
    if (e.target === selectedId) s.add(e.source);
  });
  return s;
}, [selectedId, edges]);

// ๋…ธ๋“œ: ๋น„์—ฐ๊ฒฐ์€ opacity 0.18
// ์—ฐ๊ฒฐ ์—ฃ์ง€: ์ฃผํ™ฉ(#ff6f00) + strokeWidth 2.5 + animated
// ๋น„์—ฐ๊ฒฐ ์—ฃ์ง€: ํšŒ์ƒ‰ + opacity 0.3

์ด ์ธํ„ฐ๋ž™์…˜์€ #22 ์•ˆ์— ํฌํ•จ (ํ€˜์ŠคํŠธ ์นด๋“œ ๋…ธ๋“œ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ผ๋ถ€๋กœ ๊ตฌํ˜„).

8.6 ์„ฑ๋Šฅ ์ธก์ • (์ŠคํŒŒ์ดํฌ)

๋…ธ๋“œ ์ˆ˜ layout ์‹œ๊ฐ„ (Mac M-series)
50 < 5 ms
80 < 10 ms
120 < 25 ms

3์ฃผ ์Šค์ฝ”ํ”„(์ˆ˜์‹ญ ~ 100 ๋…ธ๋“œ)์—์„œ ์ถฉ๋ถ„. 200+ ๋…ธ๋“œ ์ธก์ •์€ Week 3 ํšŒ๊ณ ์—์„œ.

8.7 ๊ฒ€์ฆ ์•ˆ ๋œ ํ•ญ๋ชฉ (๊ตฌํ˜„ ์ค‘ ํ™•์ธ)

  • ์‹ค์ œ CRUD ์™€ ๊ฒฐํ•ฉํ•œ ์ธํ„ฐ๋ž™์…˜ ์„ฑ๋Šฅ (์ •์  ๋ Œ๋”๋งŒ ์ธก์ •)
  • ์‚ฌ์ดํด ์‹œ๋„ ์‹œ ๊ฑฐ๋ถ€ ํ”ผ๋“œ๋ฐฑ UX
  • ๋…ธ๋“œ ์ถ”๊ฐ€/์‚ญ์ œ ์‹œ ์ž๋™ ๋ ˆ์ด์•„์›ƒ ์žฌ๊ณ„์‚ฐ์˜ ์‹œ๊ฐ์  ๋ถ€๋“œ๋Ÿฌ์›€ (์ฆ‰์‹œ vs ํŠธ๋žœ์ง€์…˜)
  • ๋ชจ๋ฐ”์ผ (์Šค์ฝ”ํ”„ ์™ธ)
  • ๋‹คํฌ ๋ชจ๋“œ (์Šค์ฝ”ํ”„ ์™ธ)

9. ํšŒ๊ณ ์—์„œ ํ™•์ธํ•  ๊ฒƒ

  • ๊ฒŒ์ด๋ฏธํ”ผ์ผ€์ด์…˜ ํ†ค์ด ์‹ค์ œ ์‚ฌ์šฉ์—์„œ ์žฌ๋ฏธ ์ธ๊ฐ€ ๋…ธ์ด์ฆˆ ์ธ๊ฐ€? (๋ณธ์ธ ์‚ฌ์šฉ ํ›„ ํ‰๊ฐ€)
  • ์‚ฌ์ด๋“œ ํ€˜์ŠคํŠธ ์ž๋™ ๋ถ„๋ฅ˜๊ฐ€ ์ง๊ด€์ ์ด์—ˆ๋Š”๊ฐ€? ๋ณ„๋„ ํ† ๊ธ€์ด ํ•„์š”ํ•œ๊ฐ€?
  • ํ•ด๊ธˆ ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ๋นˆ๋„/๊ธธ์ด๊ฐ€ ์ ์ ˆํ–ˆ๋Š”๊ฐ€?
  • Week 3 ์‹œ๊ฐ„ ์˜ˆ์‚ฐ ๋Œ€๋น„ +2.5์ผ์ด ์‹ค์ œ๋กœ ์–ผ๋งˆ์˜€๋Š”๊ฐ€?

Clone this wiki locally