Skip to content

Latest commit

ย 

History

History
89 lines (73 loc) ยท 13.8 KB

File metadata and controls

89 lines (73 loc) ยท 13.8 KB

AGENTS.md

๋‹ฌ๋ ˆ ์Šคํ„ฐ๋”” ํ”ผ๋“œ๋ฐฑ (https://feedback.dalestudy.com) โ€” ์Šคํ„ฐ๋””ยทํ”„๋กœ์ ํŠธ ํšŒ๊ณ  ์„ค๋ฌธ ์‚ฌ์ดํŠธ.

Docs

  • docs/database.md โ€” ํ…Œ์ด๋ธ”, ๊ด€๊ณ„, ์ œ์•ฝ, ์ž์ฃผ ์“ฐ๋Š” ์กฐํšŒ
  • docs/questions.md โ€” ๋ฌธํ•ญ ์œ ํ˜•, config, ๊ณตํ†ต ๋ฌธํ•ญ๊ณผ vars, ์œ ํ˜• ์ถ”๊ฐ€ ์ ˆ์ฐจ

Commands

bun install
bun run dev                 # Dev server on port 3000
bun run build
bun run test                # tsc --noEmit + bun test
bun run cf-typegen          # wrangler.jsonc ๋ณ€๊ฒฝ ํ›„ worker-configuration.d.ts ์žฌ์ƒ์„ฑ
bun run db:migrate:local    # ๋กœ์ปฌ D1 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
bun run db:seed:local <json> # ์„ค๋ฌธ JSON(ํŽธ์ง‘์žยท๋ฌธํ•ญ)์„ ๋กœ์ปฌ D1 ์— ๋„ฃ๊ธฐ (seed/example.json)
bunx drizzle-kit generate --name <desc>   # ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ํ›„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ
bun run db:migrate:remote   # ํ”„๋กœ๋•์…˜ D1 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์•„๋ž˜ Gotchas ์˜ ์ˆœ์„œ๋ฅผ ์ง€ํ‚จ๋‹ค)
bun run db:migrate:preview  # ๋ฏธ๋ฆฌ๋ณด๊ธฐ D1 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
bun run deploy:preview      # ์ง€๊ธˆ ๋ธŒ๋žœ์น˜์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ˆ˜๋™ ๋ฐฐํฌ (๋ณดํ†ต์€ push ๋•Œ Workers Builds ๊ฐ€ ํ•œ๋‹ค)

Local setup

  • ๋กœ์ปฌ ์‹œํฌ๋ฆฟ์€ .dev.vars (git ์— ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š์Œ): GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET โ€” DaleStudy GitHub App ์˜ Client IDยทsecret. ์—†์œผ๋ฉด ๋กœ๊ทธ์ธ ๋Œ€์‹  usersยทsessions ์— ํ–‰์„ ์ง์ ‘ ๋„ฃ๊ณ  Cookie: __Host-session=<id> ๋กœ ์š”์ฒญํ•œ๋‹ค. ์‹ค์ œ GitHub ๋กœ๊ทธ์ธ์€ PR ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ๋œ๋‹ค.
  • __Host- ์ ‘๋‘์‚ฌ ์ฟ ํ‚ค๋Š” Secure ๊ฐ€ ํ•„์ˆ˜๋ผ, localhost ๋ฅผ secure context ๋กœ ์ทจ๊ธ‰ํ•˜๋Š” Chrome/Firefox ์—์„œ ๊ฐœ๋ฐœํ•œ๋‹ค.
  • seed JSON(seed/*.json, ํ˜•์‹์€ seed/example.json)์€ ๋กœ์ปฌ D1 ์šฉ์ด๋‹ค. ํ”„๋กœ๋•์…˜ ์„ค๋ฌธ์€ ํ™”๋ฉด์—์„œ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋ณต์ œํ•œ๋‹ค. editors ๊ฐ€ ํŽธ์ง‘์ž, survey.id ๋ฅผ ๋นผ๋ฉด nanoid ๋กœ ๋งŒ๋“ ๋‹ค. ๊ฐ™์€ id ๊ฐ€ ์žˆ์œผ๋ฉด ์‹คํŒจํ•œ๋‹ค. ๋ฌธํ•ญ์€ { "common": "<key>", "required"? }(๊ณตํ†ต ๋ฌธํ•ญ, ๋ฌธ๊ตฌ๋Š” vars ๋กœ ์ฑ„์›€) ๋˜๋Š” { "type", "label", "required"?, "config"? }(์ด ์„ค๋ฌธ๋งŒ์˜ ๋ฌธํ•ญ). ์œ ํ˜•๋ณ„ ์ €์žฅ๊ฐ’ยท๊ฒ€์ฆยทconfig ๋Š” docs/questions.md.

Deployment

  • ๋ณ€๊ฒฝ์€ ๋ธŒ๋žœ์น˜ โ†’ PR โ†’ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ํ™•์ธ โ†’ squash merge. main ์— ์ง์ ‘ pushยทforce push ํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ๋‹ค๋ฅธ ์‚ฌ๋žŒ๊ณผ ํ•จ๊ป˜ ์“ฐ๋Š” ์ €์žฅ์†Œ๊ฐ€ ๋๊ณ , ๋จธ์ง€ ๊ธฐ๋ก์ด ๊ณง ๋ฐฐํฌ ๊ธฐ๋ก์ด๋‹ค.
  • main ์— ๋จธ์ง€๋˜๋ฉด Cloudflare Workers Builds ๊ฐ€ bun run build โ†’ npx wrangler deploy. ๋นŒ๋“œ ๊ฒฐ๊ณผ๋Š” ์ปค๋ฐ‹์˜ check run Workers Builds: feedback ์œผ๋กœ ํ™•์ธํ•œ๋‹ค. bun run deploy ๋Š” ์ˆ˜๋™ fallback.
  • ๋ฏธ๋ฆฌ๋ณด๊ธฐ: main ์ด ์•„๋‹Œ ๋ธŒ๋žœ์น˜๋ฅผ push ํ•˜๋ฉด Workers Builds ๊ฐ€ bun run db:migrate:preview && bun run deploy:preview(Workers Previews, wrangler preview)๋กœ https://<๋ธŒ๋žœ์น˜>.feedback-preview.dalestudy.com ์„ ๋งŒ๋“ ๋‹ค. ๋ชจ๋“  ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” D1 feedback-preview ๋ฅผ ํ•จ๊ป˜ ์“ด๋‹ค โ€” ํ”„๋กœ๋•์…˜ ์‘๋‹ต์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” ๊ฒŒ ๋ชฉ์ ์ด๋‹ค.
    • wrangler.jsonc ์˜ previews ๋ธ”๋ก์— D1 ์„ ๋‹ค์‹œ ์„ ์–ธํ•œ๋‹ค(์ƒ์†๋˜์ง€ ์•Š๋Š”๋‹ค). ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „์šฉ ํ™˜๊ฒฝ env.migrate-preview ๋กœ ํ•œ๋‹ค โ€” previews ๋ธ”๋ก์˜ D1 ์€ d1 migrations apply ๊ฐ€ ์ฐพ์ง€ ๋ชปํ•˜๊ณ , preview_database_id ๋ฅผ ์“ฐ๋ฉด ๋กœ์ปฌ ๊ฐœ๋ฐœ๊นŒ์ง€ ๊ทธ id ์˜ ๋นˆ DB ๋ฅผ ์“ฐ๊ฒŒ ๋œ๋‹ค.
    • versions upload ์˜ ๋ฒ„์ „ URL ์€ ํ”„๋กœ๋•์…˜ D1 ์„ ์จ์„œ ์“ฐ์ง€ ์•Š๋Š”๋‹ค(preview_urls ๋„ ๋”).
    • ๋ธŒ๋žœ์น˜๋ณ„ ํŠธ๋ฆฌ๊ฑฐ์™€ ์‹œํฌ๋ฆฟ์€ ๋ธŒ๋žœ์น˜๊ฐ€ ์ฒ˜์Œ ๋นŒ๋“œ๋  ๋•Œ์˜ base config ๋ฅผ ๋ณต์‚ฌํ•œ๋‹ค. ๋Œ€์‹œ๋ณด๋“œ์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ช…๋ น์ด๋‚˜ wrangler preview base-config secret put ์„ ๋ฐ”๊พผ ๋’ค์—๋Š” ๊ธฐ์กด ๋ธŒ๋žœ์น˜์— ๋”ฐ๋กœ ๋„ฃ๋Š”๋‹ค(wrangler preview secret put <KEY> --name <๋ธŒ๋žœ์น˜>). ์ฆ์ƒ์€ GitHub ๋กœ client_id=undefined ๊ฐ€ ๊ฐ€๋Š” ๊ฒƒ.
  • ์ด๋ฏธ ์„ค์ •๋œ ๊ฒƒ: D1 feedbackยทfeedback-preview(APAC, id ๋Š” wrangler.jsonc), ์‹œํฌ๋ฆฟ GITHUB_CLIENT_IDยทGITHUB_CLIENT_SECRET, ์ปค์Šคํ…€ ๋„๋ฉ”์ธ(wrangler.jsonc ์˜ routes), ๋นŒ๋“œ ๋ณ€์ˆ˜ BUN_VERSION=1.4.0(๋นŒ๋“œ ์ด๋ฏธ์ง€ ๊ธฐ๋ณธ bun ์€ lockfile v2 ๋ฅผ ๋ชป ์ฝ๋Š”๋‹ค. ๋กœ์ปฌ bun ์„ ์˜ฌ๋ฆฌ๋ฉด ๊ฐ™์ด ์˜ฌ๋ฆฐ๋‹ค).
  • GitHub App ์˜ Callback URL ์€ localhost, https://feedback.dalestudy.com/auth/callback, https://feedback-preview.dalestudy.com/auth/callback(์™€์ผ๋“œ์นด๋“œ) ์…‹. ์•ฑ์€ ๊ณต๊ฐœ(public) ์—ฌ์•ผ ํ•œ๋‹ค โ€” ๋น„๊ณต๊ฐœ ์•ฑ์€ ์†Œ์œ  ์กฐ์ง ๋ฉค๋ฒ„๋งŒ authorize ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์กฐ์ง ๋ฐ– ์ฐธ๊ฐ€์ž๊ฐ€ GitHub ์˜ authorize ํŽ˜์ด์ง€์—์„œ 404 ๋ฅผ ๋ณธ๋‹ค(์šฐ๋ฆฌ ๋กœ๊ทธ์—๋Š” ์•ˆ ๋‚จ๋Š”๋‹ค).
  • ์ €์žฅ์†Œ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค๋ฉด Workers Builds ์—ฐ๊ฒฐ์ด ๋Š๊ธด๋‹ค(๋Œ€์‹œ๋ณด๋“œ์—๋Š” ์ด๋ฆ„์ด ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ๋„). ๋Œ€์‹œ๋ณด๋“œ์—์„œ Disconnect โ†’ Connect ๋กœ ๋‹ค์‹œ ์ž‡๋Š”๋‹ค.

Architecture

TanStack Start on Cloudflare Workers, D1 + Drizzle, daleui.

  • src/routes/ ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…. login.tsx, auth.callback.tsx ๋Š” server.handlers ๋กœ ์ •์˜ํ•œ ์„œ๋ฒ„ ๋ผ์šฐํŠธ.
  • src/routes/_authed.tsx ๋Š” ๋ฏธ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ฅผ /login ์œผ๋กœ ๋ณด๋‚ด๋Š” UX ๊ฐ€๋“œ์ผ ๋ฟ์ด๋‹ค. ๋ณด์•ˆ ๊ฒฝ๊ณ„๋Š” ์„œ๋ฒ„ ํ•จ์ˆ˜์˜ authMiddleware (src/server/auth/middleware.ts). ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ์ง€๋Š” ์„œ๋ฒ„ ํ•จ์ˆ˜์—๋Š” ์ „๋ถ€ ๋ถ™์ธ๋‹ค. ์˜ˆ์™ธ๋Š” getSurveyPreview ํ•˜๋‚˜ โ€” ๋กœ๊ทธ์ธ ์ „ ์†Œ๊ฐœ์™€ SNS ๋ฏธ๋ฆฌ๋ณด๊ธฐ์šฉ์œผ๋กœ ์ œ๋ชฉยท์„ค๋ช…ยท๋งˆ๊ฐ๋งŒ ์ค€๋‹ค.
  • ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” createServerFn + .validator() + .handler(). ๋ฐ”์ธ๋”ฉ์€ import { env } from 'cloudflare:workers' โ€” env.DB, ์‹œํฌ๋ฆฟ์€ env.GITHUB_CLIENT_ID ๋“ฑ (src/env.d.ts ์—์„œ ํƒ€์ž… ๋ณด๊ฐ•).
  • ์„ธ์…˜์€ D1 sessions ํ…Œ์ด๋ธ”์˜ opaque id ๋ฅผ __Host-session HttpOnly ์ฟ ํ‚ค์— ๋‹ด๋Š”๋‹ค. 30์ผ.
  • ์‘๋‹ต์ž๋Š” responses.user_id. ์ต๋ช… ๋ชจ๋“œ๋Š” ์—†๋‹ค โ€” GitHub ๋กœ ๋กœ๊ทธ์ธํ•ด ๋‹ตํ•˜๋Š” ์„ค๋ฌธ์„ ์ต๋ช…์ด๋ผ ๋ฏฟ๋Š” ์‚ฌ๋žŒ์€ ๋“œ๋ฌผ๊ณ , ์ต๋ช… ๋ถ„๊ธฐ(HMAC ํ‚คยท์ž ๊ธˆยท์•ˆ๋‚ด ๋ฌธ๊ตฌ)์˜ ๋น„์šฉ์ด ์ปธ๋‹ค. ์‹ค๋ช…์ด ๋ถ€๋‹ด์Šค๋Ÿฝ๋‹ค๋Š” ์˜๊ฒฌ์ด ๋‚˜์˜ค๋ฉด ๋‹ค์‹œ ๋„ฃ๋Š”๋‹ค. ๋Œ€์‹  ๊ฒฐ๊ณผ ํ™”๋ฉด(getSurveyResults)์€ ๋ˆ„๊ฐ€ ์ฐธ์—ฌํ–ˆ๋Š”์ง€(์ด๋ฆ„์ˆœ ๋ช…๋‹จ, invited ์„ค๋ฌธ์€ ์•„์ง ์•ˆ ํ•œ ์‚ฌ๋žŒ)๋งŒ ์ฃผ๊ณ  ๋ˆ„๊ฐ€ ๋ฌด์—‡์„ ๋‹ตํ–ˆ๋Š”์ง€๋Š” ์ž‡์ง€ ์•Š๋Š”๋‹ค โ€” ์ฐธ์—ฌ ๋…๋ ค์—๋Š” ๋ช…๋‹จ์ด ํ•„์š”ํ•˜์ง€๋งŒ ๋‹ต๊ณผ ์ด์–ด์ง€๋ฉด ์†”์งํ•œ ํ”ผ๋“œ๋ฐฑ์ด ์ค„์–ด๋“ ๋‹ค. ์˜ˆ์™ธ๋Š” questions.identified ๋ฌธํ•ญ(์šด์˜์ง„ ๋ชจ์ง‘์ฒ˜๋Ÿผ ์—ฐ๋ฝํ•ด์•ผ ํ•˜๋Š” ๋ฌธํ•ญ)๋ฟ์ด๋‹ค.
  • ๋ฌธํ•ญ ์œ ํ˜•์€ src/questions/registry.tsx ์— ๋ชจ์—ฌ ์žˆ๋‹ค. ๊ฒ€์ฆ(validateAnswer)ยท์‘๋‹ต UI(QuestionInput)ยท๊ฒฐ๊ณผ UI(QuestionResult, ์ˆซ์žํ˜•/์„œ์ˆ ํ˜• ๊ตฌ์—ญ resultSection)ยท์‘๋‹ต ํ™”๋ฉด์˜ ๋™์ž‘(์ž๋™ ๋„˜๊น€ยท์กฐ์ž‘ ์•ˆ๋‚ด questionBehavior, ์˜ˆ์ƒ ์‹œ๊ฐ„ estimateMinutes)์€ ์ „๋ถ€ registry ๋ฅผ ๊ฑฐ์นœ๋‹ค. ์œ ํ˜•๋ณ„ switch ๋ฅผ ๋‹ค๋ฅธ ๊ณณ์— ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค.
  • ์‘๋‹ต ํ™”๋ฉด(/$surveyId)๊ณผ ํŽธ์ง‘ ํ™”๋ฉด(/$surveyId/edit)์€ staticData: { bare: true } ๋กœ ์‚ฌ์ดํŠธ ํ—ค๋” ์—†์ด ํ™”๋ฉด ์ „์ฒด๋ฅผ ์“ด๋‹ค (__root.tsx ๊ฐ€ ์ฝ๋Š”๋‹ค).
  • ์‘๋‹ต ํ™”๋ฉด(/$surveyId)๊ณผ ๊ฒฐ๊ณผ ํ™”๋ฉด(/$surveyId/results)์€ ๋กœ๊ทธ์ธ ๊ฐ€๋“œ(_authed) ๋ฐ–์— ์žˆ๋‹ค. ๋กœ๊ทธ์ธ ์ „์—๋Š” getSurveyPreview ๋กœ ์†Œ๊ฐœ์™€ ๋กœ๊ทธ์ธ ๋งํฌ๋ฅผ ๋ณด์—ฌ ์ฃผ๊ณ , SNS ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ด‡๋„ ์ œ๋ชฉยท์„ค๋ช…์„ ์ฝ๋Š”๋‹ค. ๋งํฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ๋œจ๋ ค๋ฉด ์ด ๋ผ์šฐํŠธ๋“ค์„ ๊ฐ€๋“œ ์•ˆ์œผ๋กœ ์˜ฎ๊ธฐ๋ฉด ์•ˆ ๋œ๋‹ค โ€” ๊ฒฐ๊ณผ ๋งํฌ๋„ ์šด์˜์ง„๋ผ๋ฆฌ ์ฑ„ํŒ…์— ๋‚˜๋ˆ„๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  • ๋ฉ”ํƒ€ ํƒœ๊ทธ๋Š” src/lib/seo.ts ์˜ pageHead ๋กœ. OG ์ด๋ฏธ์ง€ยทํŠธ์œ„ํ„ฐ ์นด๋“œ ๊ฐ™์€ ๊ณตํ†ต ๊ฐ’์€ __root.tsx, ํŽ˜์ด์ง€๋Š” ์ œ๋ชฉยท์„ค๋ช…ยท์ฃผ์†Œยทnoindex ๋งŒ ์ •ํ•œ๋‹ค. ๊ฒ€์ƒ‰์— ๋‚˜์˜ค๋Š” ๊ฑด ํ™ˆ๋ฟ์ด๋‹ค. ์ •์  ํŒŒ์ผ(og.jpg, favicon.svg)์€ public/.
  • ํ™”๋ฉด์€ daleui ์ปดํฌ๋„ŒํŠธ๋กœ ์ง ๋‹ค. ๋ผ์šฐํ„ฐ ์ด๋™์ด ํ•„์š”ํ•œ ๋งํฌ๋Š” src/components/AppLink.tsx(daleui Link + createLink). ์ƒ‰ยท๊ฐ„๊ฒฉ์€ var(--colors-โ€ฆ), var(--spacing-โ€ฆ) ํ† ํฐ.
  • ํ™”๋ฉด ์šฉ์–ด: ๋งŒ๋“ค๊ณ ยท์ง€์šฐ๊ณ ยท๋งˆ๊ฐ๋˜๋Š” ์–‘์‹์€ ์„ค๋ฌธ, ์ฐธ๊ฐ€์ž๊ฐ€ ๋‚จ๊ธฐ๋Š” ๋‚ด์šฉ์€ ํ”ผ๋“œ๋ฐฑ ("ํ”ผ๋“œ๋ฐฑ์„ ๋‚จ๊ธฐ๋‹ค", ์‚ฌ์ดํŠธ ์ด๋ฆ„). ์ฝ”๋“œยทDBยทURL ์€ survey.
  • ๊ณตํ†ต ๋ฌธํ•ญ์€ src/questions/common.ts. questions.key ๋กœ ์„ค๋ฌธ์„ ๊ฐ€๋กœ์งˆ๋Ÿฌ ๊ฐ™์€ ๋ฌธํ•ญ์„ ์ฐพ๋Š”๋‹ค. ๋ฌธ๊ตฌ ๋ณ€๊ฒฝ์€ ๊ณง ๋น„๊ต ๋‹จ์ ˆ์ด๋‹ค.
  • ์„ค๋ฌธ์€ ๊ธฐ์ˆ˜ยทํ”„๋กœ๊ทธ๋žจ ์—†์ด ํ™€๋กœ ์„ ๋‹ค โ€” ์„ค๋ฌธ๋ผ๋ฆฌ ๋น„๊ต์— ํ•„์š”ํ•œ ๊ฑด ๊ณตํ†ต ๋ฌธํ•ญ key ๋ฟ์ด๊ณ , ๊ธฐ์ˆ˜ ํ‘œ๋Š” ๊ธฐ์ˆ˜๋งˆ๋‹ค seed ๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•˜๋Š” ๋น„์šฉ๋งŒ ์ปธ๋‹ค. ์ƒˆ ์„ค๋ฌธ์€ DaleStudy maintainer ํŒ€๋งŒ ๋งŒ๋“ ๋‹ค (๋กœ๊ทธ์ธ ๋•Œ ํ™•์ธํ•ด users.can_create_surveys, src/server/auth/github.ts ์˜ SURVEY_CREATORS). ๊ณ ์น˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋Š” ์‚ฌ๋žŒ์€ ์šด์˜์ง„ ์ „์›๊ณผ survey_editors (src/server/access.ts ์˜ isEditor) โ€” ์šด์˜์€ ํŒ€์œผ๋กœ ํ•˜๋ฏ€๋กœ ๋งŒ๋“  ์‚ฌ๋žŒ๋งŒ ๊ณ ์น  ์ˆ˜ ์žˆ์œผ๋ฉด ๋ง‰ํžŒ๋‹ค. survey_editors ๋Š” ์šด์˜์ง„์ด ์•„๋‹Œ ์‚ฌ๋žŒ(ํ”„๋กœ๊ทธ๋žจ ๋ฆฌ๋” ๋“ฑ)์—๊ฒŒ ๋งก๊ธธ ๋•Œ ์“ด๋‹ค. ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” src/server/functions/manage.ts, ์ž…๋ ฅ ๊ฒ€์ฆ์€ src/server/survey-input.ts ์˜ ์ˆœ์ˆ˜ ํ•จ์ˆ˜์— ๋ชจ์—ฌ ์žˆ๊ณ  seed ๋„ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์“ด๋‹ค.
  • ํ…œํ”Œ๋ฆฟ์€ ๋”ฐ๋กœ ๋‘์ง€ ์•Š๊ณ  ๊ธฐ์กด ์„ค๋ฌธ์„ ๊ณ ๋ฅธ๋‹ค (createSurvey ์˜ from, ๊ด€๋ฆฌ ๋ชฉ๋ก์˜ "๋ณต์ œ"). ๋ฌธํ•ญยท๊ณต๊ฐœ ๋ฒ”์œ„ยทvarsยท๋Œ€์ƒ์„ ์˜ฎ๊ธฐ๊ณ  ์‘๋‹ตยทํŽธ์ง‘์žยท๋งˆ๊ฐ์€ ์˜ฎ๊ธฐ์ง€ ์•Š๋Š”๋‹ค. ๊ณตํ†ต ๋ฌธํ•ญ์€ copyQuestions ๊ฐ€ ์ง€๊ธˆ์˜ ์ •์˜๋กœ ๋‹ค์‹œ ์จ์„œ ์˜› ๋ฌธ๊ตฌ๊ฐ€ ํผ์ง€์ง€ ์•Š๋Š”๋‹ค. ํ…œํ”Œ๋ฆฟ ํ‘œ๋ฅผ ๋”ฐ๋กœ ๋‘๋ฉด ์„ค๋ฌธ๊ณผ ๊ฐ™์€ ํŽธ์ง‘๊ธฐยท๊ฒ€์ฆ์„ ํ•œ ๋ฒŒ ๋” ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์‹ค์ œ๋กœ ํ•„์š”ํ•œ ๊ฑด "์ง€๋‚œ๋ฒˆ ๊ทธ ์„ค๋ฌธ์ฒ˜๋Ÿผ"์ด์—ˆ๋‹ค.
  • ๊ณต๊ฐœ ๋ฒ”์œ„๋Š” surveys.visibility. homeยทlink ๋Š” ์‘๋‹ต ์ž๊ฒฉ์ด ๋”ฐ๋กœ ์—†๋‹ค(๋งํฌ + GitHub ๋กœ๊ทธ์ธ + ์ค‘๋ณต ๋ฐฉ์ง€). invited ๋Š” survey_invitees ์˜ ์‚ฌ๋žŒ(login)ยทํŒ€(slug)๊ณผ ํŽธ์ง‘์ž๋งŒ ํ™ˆ์—์„œ ๋ณด๊ณ  ๋‹ตํ•œ๋‹ค (src/server/access.ts ์˜ canRespond, invitedSurveyIds). ํŒ€์€ ๋กœ๊ทธ์ธ ๋•Œ ๋ฐ›์€ users.teams ๋กœ ๋งž์ถฐ ๋ณด๋ฏ€๋กœ ํŒ€์ด ๋ฐ”๋€Œ๋ฉด ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•œ๋‹ค. invited ๋Š” ์šด์˜์ง„์ฒ˜๋Ÿผ ๋Œ€์ƒ์ด ์ ๊ณ  GitHub ์ด๋ฆ„์„ ์•„๋Š” ์„ค๋ฌธ์— ์“ด๋‹ค. ์ฐธ์—ฌ ํšŒ๊ณ  ๊ฐ™์€ ํฐ ์„ค๋ฌธ์— ์ฐธ๊ฐ€์ž ๋ช…๋‹จ์€ ๋‘์ง€ ์•Š๋Š”๋‹ค โ€” ๋งํฌ๋Š” ํ”„๋กœ๊ทธ๋žจ ์ฑ„๋„์—๋งŒ ๊ณต์œ ๋˜๊ณ , Discordโ†”GitHub ๋งคํ•‘์„ ์œ ์ง€ํ•˜๋Š” ๋น„์šฉ์ด ๋ช…๋‹จ์ด ์ฃผ๋Š” ์•ˆ์ „๋ณด๋‹ค ํฌ๋‹ค(๋ธ”๋กœ๊ทธ 1๊ธฐ 24๋ช… ์ค‘ 7๋ช… ๋งคํ•‘ ์‹คํŒจ).
  • ์„ค๊ณ„๋ฅผ ๋ฐ”๊พธ๋ฉด ์ด์œ ๋ฅผ PR ์„ค๋ช…์— ๋‚จ๊ธฐ๊ณ , ์ง€์ผœ์•ผ ํ•  ๊ทœ์น™์ด ์ƒ๊ธฐ๋ฉด ์ด ๋ฌธ์„œ์˜ ํ•ด๋‹น ์ค„์— ํ•œ ์ค„๋กœ ์ด์œ ๋ฅผ ๋ถ™์ธ๋‹ค.

Gotchas

  • D1 ํŒŒ๋ผ๋ฏธํ„ฐ ~100๊ฐœ ์ œํ•œ: ๋‹ต๋ณ€ยท๋ฌธํ•ญ ์ผ๊ด„ INSERT ๋Š” 10ํ–‰์”ฉ ์ฒญํฌ. submitResponse, saveQuestions ์ฐธ๊ณ .
  • ๋งˆ๊ฐ๋œ ์„ค๋ฌธ์€ ๊ณ ์น  ์ˆ˜ ์—†๋‹ค (manage.ts ์˜ loadOpenSurvey). ํŽธ์ง‘ ํ™”๋ฉด์€ ๊ด€๋ฆฌ ๋ชฉ๋ก์œผ๋กœ ๋Œ๋ ค๋ณด๋‚ด๊ณ , ์ˆ˜์ • ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” ๊ฑฐ์ ˆํ•œ๋‹ค. ๊ด€๋ฆฌ ๋ชฉ๋ก์˜ ์Šค์œ„์น˜๋กœ ๋‹ค์‹œ ์—ด๋ฉด ํ’€๋ฆฐ๋‹ค.
  • ์‘๋‹ต์ด 1๊ฑด์ด๋ผ๋„ ์žˆ์œผ๋ฉด ๋ฌธํ•ญยทvars ๋Š” ์ž ๊ธด๋‹ค (manage.ts ์˜ locked). answers ๊ฐ€ question id ๋ฅผ ์ฐธ์กฐํ•ด์„œ๋‹ค. ๋ฌธํ•ญ ์ €์žฅ์€ ์ „์ฒด ๊ต์ฒด(delete + insert)๋ผ ์ž ๊ธˆ ์ „์—๋งŒ ํ—ˆ์šฉํ•œ๋‹ค.
  • ์‘๋‹ต ๊ณ ์น˜๊ธฐ๋Š” ๋งˆ๊ฐ ์ „๊นŒ์ง€ (submitResponse). ์‘๋‹ต ํ–‰์„ ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ๋‹ต๋งŒ ๋ฐ”๊ฟ”์„œ ์‘๋‹ต ์ˆ˜์™€ ๋ฌธํ•ญ ์ž ๊ธˆ์ด ๊ทธ๋Œ€๋กœ๋‹ค. ์ทจ์†Œ(์‚ญ์ œ)๋Š” ๋‘์ง€ ์•Š๋Š”๋‹ค โ€” ํŽธ์ง‘์ž๊ฐ€ ์ด๋ฏธ ๋ณธ ๊ฒฐ๊ณผ๋Š” ์ง€์›Œ์ง€์ง€ ์•Š๊ณ , ๋งˆ์ง€๋ง‰ ์‘๋‹ต์ด ์‚ฌ๋ผ์ง€๋ฉด ๋ฌธํ•ญ ์ž ๊ธˆ์ด ํ’€๋ฆฐ๋‹ค.
  • SQLite ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: ๊ธฐ์กด ํ…Œ์ด๋ธ”์— NOT NULL ์ปฌ๋Ÿผ์„ DEFAULT ์—†์ด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค.
  • TypeScript 7: baseUrl ์˜ต์…˜์ด ์‚ฌ๋ผ์กŒ๋‹ค. paths ๋งŒ ์“ด๋‹ค.
  • drizzle-kit ์ด ๋งŒ๋“  SQLite rename/์žฌ์ƒ์„ฑ SQL ์€ D1 ์—์„œ ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค. ํŠธ๋žœ์žญ์…˜ ์•ˆ์˜ PRAGMA foreign_keys=OFF ๊ฐ€ ๋ฌด์‹œ๋œ๋‹ค. ์ƒ์„ฑ๋œ SQL ์„ ์ฝ๊ณ  ๋„ค์ดํ‹ฐ๋ธŒ ALTER TABLE โ€ฆ RENAME ์œผ๋กœ ๋ฐ”๊ฟ” ์“ด ์ ์ด ๋‘ ๋ฒˆ ์žˆ๋‹ค (0004, 0006). rename ํ”„๋กฌํ”„ํŠธ๋Š” add/drop ๋‘ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์œผ๋กœ ๋‚˜๋ˆ ๋„ ๋œ๋‹ค.
  • ์Šคํ‚ค๋งˆ PR ์€ ๋จธ์ง€ โ†’ ๋นŒ๋“œ ์™„๋ฃŒ ํ™•์ธ โ†’ bun run db:migrate:remote ๋ฅผ ๋ฐ”๋กœ. ์ปฌ๋Ÿผ ์ถ”๊ฐ€๋Š” ์ฝ”๋“œ๋ณด๋‹ค ๋จผ์ € ๋Œ๋ ค๋„ ๋˜์ง€๋งŒ, ํŒŒ๊ดด์  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ์ƒˆ ์ฝ”๋“œ๊ฐ€ ๋ฐฐํฌ๋œ ๋’ค์— ๋Œ๋ฆฐ๋‹ค.
  • Tailwind ์˜ base ๋ ˆ์ด์–ด๊ฐ€ daleui ์˜ reset ๋ ˆ์ด์–ด๋ณด๋‹ค ๋’ค๋ผ ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ๊ธ€๊ผด์„ ์‹œ์Šคํ…œ ๊ธ€๊ผด๋กœ ๋ฎ์–ด์“ด๋‹ค. src/styles.css ์˜ @theme ์—์„œ --font-sans ๋ฅผ daleui ํ† ํฐ์œผ๋กœ ๋งž์ถ˜๋‹ค.
  • daleui Link ๋Š” className ์„ ๋ฐ›์œผ๋ฉด ์ž๊ธฐ ์Šคํƒ€์ผ ํด๋ž˜์Šค๋ฅผ ๋ฒ„๋ฆฐ๋‹ค. ๋ผ์šฐํ„ฐ๊ฐ€ ํ™œ์„ฑ ๋งํฌ์— ๋„˜๊ธฐ๋Š” className: 'active' ๋•Œ๋ฌธ์— AppLink ๋Š” activeProps ๋ฅผ ๋น„์šด๋‹ค. daleui ์ปดํฌ๋„ŒํŠธ์— className ์„ ๋„˜๊ธธ ์ผ์ด ์žˆ์œผ๋ฉด ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ์˜์‹ฌํ•œ๋‹ค (๊ณ ์นจ: daleui #1300).
  • --colors-bg-neutral ์€ ๋ผ์ดํŠธ ํ…Œ๋งˆ์—์„œ ํฐ์ƒ‰์ด๋‹ค. ์˜…์€ ํšŒ์ƒ‰ ๋ฉด์ด ์•„๋‹ˆ๋ผ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ(CardยทTextInput ๋ฐ”ํƒ•) ํ† ํฐ์ด๋‹ค (daleui #363). ํšŒ์ƒ‰ ๋ฉด์ด ํ•„์š”ํ•˜๋ฉด ํ…Œ๋‘๋ฆฌ(--colors-border-neutral)๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค. ํšŒ์ƒ‰ ๋ฉด ํ† ํฐ์€ daleui #1303.

Deferred

  • ์กฐ์ง ๋ฉค๋ฒ„ ๊ฒŒ์ดํŠธ โ€” ๋กœ๊ทธ์ธ ์‹œ DaleStudy ์กฐ์ง ๋ฉค๋ฒ„๋งŒ ํ†ต๊ณผ์‹œํ‚ค๋Š” ๊ฒƒ. ์ง€๊ธˆ ์ผœ๋ฉด ์‹ค์ œ ์ฐธ๊ฐ€์ž๊ฐ€ ๋ง‰ํžŒ๋‹ค(๋ธ”๋กœ๊ทธ 1๊ธฐ ์ฐธ๊ฐ€์ž 17๋ช… ์ค‘ 7๋ช…์ด ์กฐ์ง ๋ฉค๋ฒ„๊ฐ€ ์•„๋‹ˆ๋‹ค). ์ฐธ์—ฌ ์ ˆ์ฐจ์— ์กฐ์ง ์ดˆ๋Œ€๋ฅผ ๋„ฃ์€ ๋’ค ์ผ ๋‹ค. ์•ฑ์— members ๊ถŒํ•œ์ด ์žˆ์œผ๋‹ˆ ๋กœ๊ทธ์ธ ์ฝœ๋ฐฑ์—์„œ GET /user/memberships/orgs/DaleStudy ๋กœ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค(isTeamMember ์™€ ๊ฐ™์€ ์ž๋ฆฌ).
  • daleui ๊ฐญ โ€” ๋‹ฌ๋ ˆ UI ์— ๋“ค์–ด์˜ค๋ฉด ์ž„์‹œ ๊ตฌํ˜„ยท์šฐํšŒ๋ฅผ ๊ฑท์–ด๋‚ธ๋‹ค. ์˜ฌ๋ฆฐ ๊ฒƒ๊ณผ ๊ฑธ๋ฆฐ ๊ณณ:
    • #1299 ์•„์ด์ฝ˜ calendarยทlinkยทplus โ€” ๊ด€๋ฆฌ ๋ชฉ๋ก์˜ ๋งํฌ ๋ณต์‚ฌ๊ฐ€ lucide-react ๋ฅผ ์ง์ ‘ ์“ด๋‹ค. ๋ฆด๋ฆฌ์Šค๋˜๋ฉด <Icon name="link" /> ๋กœ ๋ฐ”๊พธ๊ณ  lucide-react ์˜์กด์„ฑ์„ ๋บ€๋‹ค.
    • #1300 className ๋ณ‘ํ•ฉ โ€” AppLink ์˜ activeProps ๋น„์šฐ๊ธฐ (Gotchas ์ฐธ๊ณ ).
    • #1093 Textarea, #1098 Switch โ€” src/components/ ์˜ ์ž„์‹œ ๊ตฌํ˜„.
    • #1301 ์„ค๋ฌธ ๋ณด๊ธฐ์šฉ ํฐ ์„ ํƒ ํƒ€์ผ โ€” ์ง€๊ธˆ์€ Button ์— role="radio".
    • #1231 asChild โ€” ๋ฒ„ํŠผ ๋ชจ์–‘ ๋งํฌ(/login, ํ™ˆ ์นด๋“œ์˜ "ํ”ผ๋“œ๋ฐฑ ๋‚จ๊ธฐ๊ธฐ")๋ฅผ ํ† ํฐ์œผ๋กœ ํ‰๋‚ด ๋‚ธ๋‹ค.
    • #1203 ๋ณธ๋ฌธ keep-all โ€” ํ•œ๊ตญ์–ด ๋ณธ๋ฌธ์„ ๊ฐ์‹ธ๋Š” ๊ณณ์— break-keep ์„ ์ค€๋‹ค.
    • #1302 ghost/outline ๋น„ํ™œ์„ฑ ๋ชจ์–‘ โ€” ์“ธ ์ˆ˜ ์—†๋Š” ๋ฒ„ํŠผ์€ ์ˆจ๊ธด๋‹ค.