> ## Documentation Index
> Fetch the complete documentation index at: https://docs.replit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 작업 보드

> 작업 보드를 사용하여 Agent 작업을 각 단계별로 검토, 시작, 모니터링, 적용하세요.

export const TaskReviewDrawer = ({taskNumber = 23, taskTitle = "Story regeneration: redo pages or whole stories", taskDescription = 'Added story and page-level regeneration capabilities:\n\nBackend (API):\n- POST /api/stories/:storyId/regenerate: regenerates the entire story, deletes existing pages, sets status to "generating", and re-runs the full story text and image generation pipeline in the background\n- POST /api/stories/:storyId/pages/:pageId/regenerate: regenerates a single page with body { target: "text" | "image" | "both" }\n\nFrontend (story-preview.tsx):\n- Per-page regeneration menu: subtle three-dot icon overlay on the image area with options for "Regenerate Text", "Regenerate Image", or "Regenerate Both"\n- "Regenerate Story" button in header with confirmation dialog\n- Loading state: regenerating pages show a spinner and skeleton text while other pages remain readable'}) => {
  if (typeof document !== "undefined" && !document.getElementById("task-review-drawer-styles")) {
    const style = document.createElement("style");
    style.id = "task-review-drawer-styles";
    style.textContent = `
      .trd-wrap {
        --trd-surface: var(--replit-docs-bg, #F6F6F4);
        --trd-surface-higher: var(--replit-docs-bg-elevated, #F1F1EE);
        --trd-border: var(--replit-docs-border, #DEDAD5);
        --trd-border-strong: var(--replit-docs-border-strong, #CAC4BE);
        --trd-text: var(--replit-docs-text, #1D1D1D);
        --trd-text-dim: var(--replit-docs-text-muted, #5C5C5C);
        --trd-text-dimmest: var(--replit-docs-text-subtle, #858585);
        --trd-icon: var(--replit-docs-text-subtle, #858585);
        --trd-btn-bg: var(--replit-docs-bg-muted, #F1F1EE);
        --trd-btn-hover: var(--replit-docs-bg-elevated, #F1F1EE);
        --trd-positive-bg: hsla(140, 50%, 38%, 1);
        --trd-positive-text: #FFFFFF;
        --trd-positive-hover: hsla(140, 50%, 33%, 1);
        --trd-shadow: 0 1px 3px rgba(0,0,0,0.06);
        --trd-icon-surface: var(--replit-docs-bg-elevated, #F1F1EE);
        --trd-icon-border: var(--replit-docs-border, #DEDAD5);
      }
      .dark .trd-wrap,
      html.dark .trd-wrap,
      [data-theme="dark"] .trd-wrap {
        --trd-surface: var(--replit-docs-bg, #1E1E1F);
        --trd-surface-higher: var(--replit-docs-bg-elevated, #222223);
        --trd-border: var(--replit-docs-border, #39393D);
        --trd-border-strong: var(--replit-docs-border-strong, #4A4A50);
        --trd-text: var(--replit-docs-text, #F5F5F5);
        --trd-text-dim: var(--replit-docs-text-muted, #B8B8BE);
        --trd-text-dimmest: var(--replit-docs-text-subtle, #8E8F97);
        --trd-icon: var(--replit-docs-text-subtle, #8E8F97);
        --trd-btn-bg: var(--replit-docs-bg-elevated, #222223);
        --trd-btn-hover: var(--replit-docs-bg-muted, #222223);
        --trd-positive-bg: hsla(140, 50%, 33%, 1);
        --trd-positive-text: #FFFFFF;
        --trd-positive-hover: hsla(140, 50%, 28%, 1);
        --trd-shadow: 0 1px 3px rgba(0,0,0,0.2);
        --trd-icon-surface: var(--replit-docs-bg-elevated, #222223);
        --trd-icon-border: var(--replit-docs-border, #39393D);
      }
      .trd-wrap, .trd-wrap * {
        font-family: 'IBM Plex Sans', sans-serif !important;
      }
      @keyframes trd-fade-in {
        from { transform: translateY(8px); opacity: 0; }
        to { transform: translateY(0); opacity: 1; }
      }
      @media (hover: hover) {
        .trd-wrap .trd-dismiss:hover { background: var(--trd-btn-hover) !important; }
        .trd-wrap .trd-apply:hover { background: var(--trd-positive-hover) !important; }
      }
    `;
    document.head.appendChild(style);
  }
  const PlusIcon = <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
      <path d="M12 4.25a.75.75 0 0 1 .75.75v6.25H19a.75.75 0 0 1 0 1.5h-6.25V19a.75.75 0 0 1-1.5 0v-6.25H5a.75.75 0 0 1 0-1.5h6.25V5a.75.75 0 0 1 .75-.75Z" />
    </svg>;
  const ArrowUpIcon = <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
      <path fillRule="evenodd" d="M11.47 4.47a.75.75 0 0 1 1.06 0l7 7a.75.75 0 1 1-1.06 1.06l-5.72-5.72V19a.75.75 0 0 1-1.5 0V6.81l-5.72 5.72a.75.75 0 0 1-1.06-1.06l7-7Z" clipRule="evenodd" />
    </svg>;
  const ApplyIcon = <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
      <path fillRule="evenodd" d="M6 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM2.25 6a3.75 3.75 0 1 1 4.527 3.67 8.25 8.25 0 0 0 7.554 7.553A3.751 3.751 0 0 1 21.75 18a3.75 3.75 0 0 1-7.43.726 9.75 9.75 0 0 1-7.57-4.53V21a.75.75 0 0 1-1.5 0V9.675A3.751 3.751 0 0 1 2.25 6ZM18 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z" clipRule="evenodd" />
    </svg>;
  const AgentModesIcon = <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
      <path d="M1.25 6V4A2.75 2.75 0 0 1 4 1.25h2a.75.75 0 0 1 0 1.5H4A1.25 1.25 0 0 0 2.75 4v2a.75.75 0 0 1-1.5 0ZM21.25 6V4a1.25 1.25 0 0 0-1.126-1.244L20 2.75h-2a.75.75 0 0 1 0-1.5h2A2.75 2.75 0 0 1 22.75 4v2a.75.75 0 0 1-1.5 0ZM21.25 20v-2a.75.75 0 0 1 1.5 0v2A2.75 2.75 0 0 1 20 22.75h-2a.75.75 0 0 1 0-1.5h2A1.25 1.25 0 0 0 21.25 20ZM1.25 20v-2a.75.75 0 0 1 1.5 0v2l.006.124A1.25 1.25 0 0 0 4 21.25h2a.75.75 0 0 1 0 1.5H4A2.75 2.75 0 0 1 1.25 20ZM16 12a4 4 0 1 0-8 0 4 4 0 0 0 8 0Zm1.5 0a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Z" />
    </svg>;
  const ChevronDown = <svg width="12" height="12" viewBox="0 0 24 24" fill="var(--trd-text-dimmest)">
      <path fillRule="evenodd" d="M12.53 15.53a.75.75 0 0 1-1.06 0l-6-6a.75.75 0 0 1 1.06-1.06L12 13.94l5.47-5.47a.75.75 0 1 1 1.06 1.06l-6 6Z" clipRule="evenodd" />
    </svg>;
  const SparkleIcon = <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
      <path fill="#009118" d="M18.662 0a5.332 5.332 0 0 1 .001 10.664l-.412.01a8 8 0 0 0-7.577 7.59l-.01.398-.008.274A5.331 5.331 0 0 1 0 18.662a5.332 5.332 0 0 1 5.332-5.332l.398-.01a8 8 0 0 0 7.59-7.576l.011-.412A5.331 5.331 0 0 1 18.662 0Z" />
      <path fill="#6CD97E" d="m18.663 13.33.273.007a5.332 5.332 0 1 1-5.598 5.6l-.007-.275.006-.265a5.38 5.38 0 0 1 .137-.963c.006-.026.01-.052.017-.078.012-.047.026-.095.04-.142a4.84 4.84 0 0 1 .032-.114l.007-.024.065-.193.02-.054.022-.06c.021-.056.044-.112.067-.167l.026-.059c.024-.056.048-.112.074-.167l.058-.118a4.854 4.854 0 0 1 .145-.265l.024-.043a5.29 5.29 0 0 1 .08-.129c.02-.032.04-.065.062-.097a5.34 5.34 0 0 1 .468-.607l.071-.079a5.562 5.562 0 0 1 .265-.267l.11-.1a5.3 5.3 0 0 1 .308-.252l.058-.045c.046-.034.093-.068.14-.1l.049-.034.073-.049.07-.043.112-.07c.043-.026.089-.05.133-.075.026-.014.051-.03.078-.044l.114-.059c.041-.02.083-.042.126-.062l.089-.04.146-.065.086-.033c.048-.019.095-.038.143-.055l.023-.008.202-.068.024-.007c.062-.019.125-.036.189-.053l.043-.012c.062-.016.125-.03.189-.044l.078-.018a5.34 5.34 0 0 1 .485-.074c.011 0 .023-.003.034-.005.09-.009.18-.014.27-.02l.274-.006ZM5.332 0a5.332 5.332 0 0 1 5.332 5.332l-.008.274a5.332 5.332 0 0 1-5.324 5.058l-.274-.007A5.332 5.332 0 0 1 5.332 0Z" />
    </svg>;
  const descriptionLines = taskDescription.split("\n").filter(l => l.trim());
  return <div className="trd-wrap" style={{
    fontFamily: "'IBM Plex Sans', sans-serif",
    width: "100%",
    maxWidth: "427px",
    minWidth: "320px",
    alignSelf: "center",
    margin: "48px auto",
    opacity: 0,
    animation: "trd-fade-in 300ms ease-out forwards"
  }}>
      {}
      <div style={{
    background: "var(--trd-surface)",
    border: "1px solid var(--trd-border-strong)",
    borderRadius: "6px",
    padding: "8px",
    boxShadow: "var(--trd-shadow)",
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
        {}
        <div>
          <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "8px",
    padding: "4px"
  }}>
            {}
            <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px"
  }}>
              <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    width: "28px",
    height: "28px",
    borderRadius: "8px",
    background: "var(--trd-icon-surface)",
    border: "1px solid var(--trd-icon-border)",
    flexShrink: 0
  }}>
                {SparkleIcon}
              </div>
              <span style={{
    fontSize: "14px",
    lineHeight: "1.4",
    color: "var(--trd-text)"
  }}>
                Ready for review
              </span>
            </div>

            {}
            <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
              <span style={{
    fontSize: "13px",
    lineHeight: "1.4",
    color: "var(--trd-text-dim)"
  }}>
                Task #{taskNumber}: {taskTitle}
              </span>

              {}
              <div style={{
    fontSize: "12px",
    lineHeight: "1.5",
    color: "var(--trd-text-dimmest)",
    maxHeight: "50px",
    overflow: "hidden",
    position: "relative"
  }}>
                {descriptionLines.map((line, i) => <span key={i}>
                    {line}
                    <br />
                  </span>)}
              </div>
            </div>

            {}
            <div style={{
    display: "flex",
    gap: "8px"
  }}>
              <button className="trd-dismiss" type="button" style={{
    width: "122px",
    flexShrink: 0,
    height: "32px",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    border: "none",
    borderRadius: "8px",
    background: "var(--trd-btn-bg)",
    color: "var(--trd-text)",
    fontSize: "14px",
    fontFamily: "inherit",
    cursor: "pointer",
    transition: "background 120ms ease-out"
  }}>
                Dismiss
              </button>
              <button className="trd-apply" type="button" style={{
    flex: 1,
    height: "32px",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "6px",
    border: "none",
    borderRadius: "8px",
    background: "var(--trd-positive-bg)",
    color: "var(--trd-positive-text)",
    fontSize: "14px",
    fontFamily: "inherit",
    cursor: "pointer",
    transition: "background 120ms ease-out"
  }}>
                <span>Apply changes to main version</span>
                {ApplyIcon}
              </button>
            </div>
          </div>
        </div>

        {}
        <div style={{
    padding: 0
  }}>
          <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
            {}
            <div style={{
    padding: "5px 6px",
    minHeight: "21px",
    fontSize: "14px",
    lineHeight: "1.3",
    color: "var(--trd-text-dimmest)"
  }}>
              Message agent…
            </div>

            {}
            <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "flex-end",
    paddingBottom: "2px"
  }}>
              {}
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "4px"
  }}>
                <button type="button" style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    width: "24px",
    height: "24px",
    border: "none",
    borderRadius: "4px",
    background: "transparent",
    cursor: "pointer",
    color: "var(--trd-icon)",
    padding: 0
  }}>
                  {PlusIcon}
                </button>
              </div>

              {}
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    paddingRight: "2px"
  }}>
                <button type="button" style={{
    display: "flex",
    alignItems: "center",
    gap: "4px",
    height: "24px",
    padding: "0 4px",
    border: "none",
    borderRadius: "4px",
    background: "transparent",
    cursor: "pointer",
    color: "var(--trd-icon)"
  }}>
                  {AgentModesIcon}
                  {ChevronDown}
                </button>
                <button type="button" style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    width: "28px",
    height: "28px",
    border: "none",
    borderRadius: "8px",
    background: "hsla(210, 100%, 74%, 1)",
    color: "#FFFFFF",
    padding: 0,
    cursor: "pointer"
  }}>
                  {ArrowUpIcon}
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>;
};

export const KanbanColumns = ({columns = []}) => {
  if (typeof document !== "undefined" && !document.getElementById("kanban-board-styles")) {
    const style = document.createElement("style");
    style.id = "kanban-board-styles";
    style.textContent = `
      .kb-board {
        --kb-bg: var(--replit-docs-bg, #F6F6F4);
        --kb-col-bg: var(--replit-docs-bg-elevated, #F1F1EE);
        --kb-card-bg: var(--replit-docs-bg-task, #EDECE8);
        --kb-border: var(--replit-docs-border, #DEDAD5);
        --kb-text: var(--replit-docs-text, #1D1D1D);
        --kb-text-dim: var(--replit-docs-text-muted, #5C5C5C);
        --kb-text-dimmer: var(--replit-docs-text-muted, #5C5C5C);
        --kb-text-dimmest: var(--replit-docs-text-subtle, #858585);
        --kb-badge-bg: var(--replit-docs-bg-elevated, #F1F1EE);
      }
      .dark .kb-board,
      html.dark .kb-board,
      [data-theme="dark"] .kb-board {
        --kb-bg: var(--replit-docs-bg, #1E1E1F);
        --kb-col-bg: var(--replit-docs-bg-elevated, #222223);
        --kb-card-bg: var(--replit-docs-bg-task, #252527);
        --kb-border: var(--replit-docs-border, #39393D);
        --kb-text: var(--replit-docs-text, #F5F5F5);
        --kb-text-dim: var(--replit-docs-text-muted, #B8B8BE);
        --kb-text-dimmer: var(--replit-docs-text-muted, #B8B8BE);
        --kb-text-dimmest: var(--replit-docs-text-subtle, #8E8F97);
        --kb-badge-bg: var(--replit-docs-bg-elevated, #222223);
      }
      .kb-board { overflow: visible; }
    `;
    document.head.appendChild(style);
  }
  return <div className="kb-board" style={{
    fontFamily: "'IBM Plex Sans', sans-serif",
    display: "flex",
    gap: "12px",
    padding: "8px",
    background: "var(--kb-bg)",
    borderRadius: "12px"
  }}>
      {columns.map((col, ci) => <div key={ci} data-kb-column={col.title.toLowerCase()} style={{
    flex: "1 1 0",
    minWidth: "200px",
    background: "var(--kb-col-bg)",
    borderRadius: "8px",
    display: "flex",
    flexDirection: "column",
    overflow: "visible"
  }}>
          <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "12px 12px 8px 12px"
  }}>
            <span style={{
    fontSize: "14px",
    fontWeight: 500,
    color: "var(--kb-text)"
  }}>
              {col.title}
            </span>
            <span data-kb-count style={{
    fontSize: "12px",
    color: "var(--kb-text-dimmer)",
    background: "var(--kb-badge-bg)",
    padding: "0 6px",
    borderRadius: "4px",
    lineHeight: "20px"
  }}>
              {col.count}
            </span>
          </div>
          <div data-kb-cards style={{
    display: "flex",
    flexDirection: "column",
    gap: "8px",
    padding: "8px",
    flex: "1 1 0"
  }}>
            {col.cards.map((card, i) => <TaskCard key={i} title={card.title} description={card.description} status={card.status} timestamp={card.timestamp} showMenu={card.showMenu} width="100%" />)}
          </div>
        </div>)}
    </div>;
};

export const TaskCard = ({title = "Product Manager next steps discussion", description = "", status = "draft", timestamp = "Created 3 minutes ago", width = "364px", showMenu = false}) => {
  if (typeof document !== "undefined" && !document.getElementById("task-card-styles")) {
    const style = document.createElement("style");
    style.id = "task-card-styles";
    style.textContent = `
      .task-card {
        --tc-bg: var(--replit-docs-bg-task, #EDECE8);
        --tc-border: var(--replit-docs-border, #DEDAD5);
        --tc-text: var(--replit-docs-text, #1D1D1D);
        --tc-text-dim: var(--replit-docs-text-muted, #5C5C5C);
        --tc-text-dimmest: var(--replit-docs-text-subtle, #858585);
        --tc-dot-active: #4A7BF7;
        --tc-dot-draft: #A6A6A6;
        --tc-dot-ready: #F59E0B;
        --tc-dot-done: #22C55E;
      }
      .dark .task-card,
      html.dark .task-card,
      [data-theme="dark"] .task-card {
        --tc-bg: var(--replit-docs-bg-task, #252527);
        --tc-border: var(--replit-docs-border, #39393D);
        --tc-text: var(--replit-docs-text, #F5F5F5);
        --tc-text-dim: var(--replit-docs-text-muted, #B8B8BE);
        --tc-text-dimmest: var(--replit-docs-text-subtle, #8E8F97);
        --tc-dot-active: #6B9EFF;
        --tc-dot-draft: #6B7280;
        --tc-dot-ready: #FBBF24;
        --tc-dot-done: #4ADE80;
      }
      @keyframes task-card-fade-in {
        from { transform: translateY(6px); opacity: 0; }
        to { transform: translateY(0); opacity: 1; }
      }
      @keyframes tc-tl {
        0%{opacity:1;fill:var(--tc-light)} 5%{opacity:1;fill:var(--tc-dark)} 10%{opacity:1;fill:var(--tc-dark)} 15%{opacity:1;fill:var(--tc-light)}
        20%{opacity:0} 25%{opacity:1;fill:var(--tc-dark)} 30%{opacity:1;fill:var(--tc-light)} 35%{opacity:0}
        40%{opacity:1;fill:var(--tc-dark)} 45%{opacity:1;fill:var(--tc-light)} 50%{opacity:0} 55%{opacity:1;fill:var(--tc-dark)}
        60%{opacity:0} 65%{opacity:1;fill:var(--tc-light)} 70%{opacity:1;fill:var(--tc-light)} 75%{opacity:1;fill:var(--tc-light)}
        80%{opacity:1;fill:var(--tc-light)} 85%{opacity:0} 90%{opacity:1;fill:var(--tc-light)} 95%{opacity:1;fill:var(--tc-light)}
      }
      @keyframes tc-tr {
        0%{opacity:1;fill:var(--tc-dark)} 5%{opacity:0} 10%{opacity:0} 15%{opacity:0}
        20%{opacity:1;fill:var(--tc-light)} 25%{opacity:1;fill:var(--tc-light)} 30%{opacity:1;fill:var(--tc-light)} 35%{opacity:1;fill:var(--tc-light)}
        40%{opacity:0} 45%{opacity:1;fill:var(--tc-dark)} 50%{opacity:1;fill:var(--tc-dark)} 55%{opacity:0}
        60%{opacity:1;fill:var(--tc-dark)} 65%{opacity:0} 70%{opacity:0} 75%{opacity:1;fill:var(--tc-light)}
        80%{opacity:0} 85%{opacity:1;fill:var(--tc-dark)} 90%{opacity:0} 95%{opacity:0}
      }
      @keyframes tc-bl {
        0%{opacity:1;fill:var(--tc-light)} 5%{opacity:0} 10%{opacity:0} 15%{opacity:0}
        20%{opacity:1;fill:var(--tc-light)} 25%{opacity:1;fill:var(--tc-light)} 30%{opacity:1;fill:var(--tc-light)} 35%{opacity:1;fill:var(--tc-light)}
        40%{opacity:0} 45%{opacity:1;fill:var(--tc-light)} 50%{opacity:1;fill:var(--tc-light)} 55%{opacity:0}
        60%{opacity:1;fill:var(--tc-light)} 65%{opacity:0} 70%{opacity:0} 75%{opacity:1;fill:var(--tc-light)}
        80%{opacity:0} 85%{opacity:1;fill:var(--tc-light)} 90%{opacity:0} 95%{opacity:0}
      }
      @keyframes tc-br {
        0%{opacity:1;fill:var(--tc-light)} 5%{opacity:1;fill:var(--tc-dark)} 10%{opacity:1;fill:var(--tc-light)} 15%{opacity:1;fill:var(--tc-light)}
        20%{opacity:0} 25%{opacity:1;fill:var(--tc-light)} 30%{opacity:1;fill:var(--tc-dark)} 35%{opacity:0}
        40%{opacity:1;fill:var(--tc-light)} 45%{opacity:1;fill:var(--tc-light)} 50%{opacity:0} 55%{opacity:1;fill:var(--tc-light)}
        60%{opacity:0} 65%{opacity:1;fill:var(--tc-dark)} 70%{opacity:1;fill:var(--tc-light)} 75%{opacity:1;fill:var(--tc-light)}
        80%{opacity:1;fill:var(--tc-light)} 85%{opacity:0} 90%{opacity:1;fill:var(--tc-light)} 95%{opacity:1;fill:var(--tc-dark)}
      }
      @keyframes tc-drop {
        0%{opacity:0} 5%{opacity:1;fill:var(--tc-dark)} 10%{opacity:1;fill:var(--tc-dark)} 15%{opacity:1;fill:var(--tc-dark)}
        20%{opacity:0} 25%{opacity:0} 30%{opacity:0} 35%{opacity:0}
        40%{opacity:1;fill:var(--tc-dark)} 45%{opacity:0} 50%{opacity:0} 55%{opacity:1;fill:var(--tc-dark)}
        60%{opacity:0} 65%{opacity:1;fill:var(--tc-dark)} 70%{opacity:1;fill:var(--tc-dark)} 75%{opacity:0}
        80%{opacity:1;fill:var(--tc-dark)} 85%{opacity:0} 90%{opacity:1;fill:var(--tc-dark)} 95%{opacity:1;fill:var(--tc-dark)}
      }
      @keyframes tc-dropalt {
        0%{opacity:0} 5%{opacity:0} 10%{opacity:0} 15%{opacity:0}
        20%{opacity:1;fill:var(--tc-dark)} 25%{opacity:0} 30%{opacity:0} 35%{opacity:1;fill:var(--tc-dark)}
        40%{opacity:0} 45%{opacity:0} 50%{opacity:1;fill:var(--tc-dark)} 55%{opacity:0}
        60%{opacity:1;fill:var(--tc-dark)} 65%{opacity:0} 70%{opacity:0} 75%{opacity:0}
        80%{opacity:0} 85%{opacity:1;fill:var(--tc-dark)} 90%{opacity:0} 95%{opacity:0}
      }
      .task-card .tc-el-tl { animation: tc-tl 4s step-end infinite; }
      .task-card .tc-el-tr { animation: tc-tr 4s step-end infinite; }
      .task-card .tc-el-bl { animation: tc-bl 4s step-end infinite; }
      .task-card .tc-el-br { animation: tc-br 4s step-end infinite; }
      .task-card .tc-el-drop { animation: tc-drop 4s step-end infinite; }
      .task-card .tc-el-dropalt { animation: tc-dropalt 4s step-end infinite; }
      @media (hover: hover) {
        .task-card:hover {
          border-color: var(--tc-text-dimmest) !important;
        }
        .task-card .tc-menu-btn:hover {
          background: var(--tc-border);
        }
      }
    `;
    document.head.appendChild(style);
  }
  const colorPairs = {
    active: {
      dark: "#57ABFF",
      light: "#A8D4FF"
    },
    "active-queued": {
      dark: "#6BB5FF",
      light: "#6BB5FF"
    },
    draft: {
      dark: "#A6A6A6",
      light: "#D4D4D4"
    },
    "draft-static": {
      dark: "#A6A6A6",
      light: "#D4D4D4"
    },
    "draft-done": {
      dark: "#A6A6A6",
      light: "#D4D4D4"
    },
    ready: {
      dark: "#009118",
      light: "#6CD97E"
    },
    "ready-drop": {
      dark: "#009118",
      light: "#6CD97E"
    },
    applying: {
      dark: "#7C3AED",
      light: "#C4B5FD"
    },
    done: {
      dark: "#22C55E",
      light: "#86EFAC"
    }
  };
  const statusLabels = {
    active: "Active",
    "active-queued": "Queued",
    draft: "Draft",
    "draft-static": "Draft",
    "draft-done": "Done",
    ready: "Ready",
    "ready-drop": "Ready",
    applying: "Applying",
    done: "Done"
  };
  const colors = colorPairs[status] || colorPairs.draft;
  const applyingIconHtml = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="flex-shrink:0;--tc-dark:#009118;--tc-light:#6CD97E"><circle class="tc-el-tl" cx="5.332" cy="5.338" r="5.332" fill="#009118"/><circle class="tc-el-tr" cx="18.663" cy="5.338" r="5.332" fill="#009118"/><circle class="tc-el-bl" cx="5.332" cy="18.668" r="5.332" fill="#009118"/><circle class="tc-el-br" cx="18.663" cy="18.668" r="5.332" fill="#009118"/><path class="tc-el-drop" fill="#009118" d="M18.662 0a5.332 5.332 0 0 1 .001 10.664l-.412.01a8 8 0 0 0-7.577 7.59l-.01.398-.008.274A5.331 5.331 0 0 1 0 18.662a5.332 5.332 0 0 1 5.332-5.332l.398-.01a8 8 0 0 0 7.59-7.576l.011-.412A5.331 5.331 0 0 1 18.662 0Z"/><g class="tc-el-dropalt" transform="rotate(90, 12, 12)"><path fill="#009118" d="M18.662 0a5.332 5.332 0 0 1 .001 10.664l-.412.01a8 8 0 0 0-7.577 7.59l-.01.398-.008.274A5.331 5.331 0 0 1 0 18.662a5.332 5.332 0 0 1 5.332-5.332l.398-.01a8 8 0 0 0 7.59-7.576l.011-.412A5.331 5.331 0 0 1 18.662 0Z"/></g></svg>';
  const doneIconHtml = '<svg width="16" height="16" viewBox="0 0 24 24" style="flex-shrink:0;fill:var(--tc-text-dimmest)"><path d="M12 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z"/><path fill-rule="evenodd" d="M12 1c6.075 0 11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12 5.925 1 12 1Zm0 2a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z" clip-rule="evenodd"/></svg>';
  const handleApplyChanges = e => {
    const tooltip = e.currentTarget.parentElement;
    tooltip.style.display = "none";
    const menuBtn = tooltip.previousElementSibling;
    if (menuBtn) menuBtn.style.background = "none";
    const card = e.currentTarget.closest(".task-card");
    if (!card) return;
    const headerRow = card.firstElementChild;
    const oldIcon = headerRow.firstElementChild;
    const menuWrapper = headerRow.lastElementChild;
    const meta = card.lastElementChild;
    const temp = document.createElement("div");
    temp.innerHTML = applyingIconHtml;
    headerRow.replaceChild(temp.firstElementChild, oldIcon);
    meta.textContent = "Applying \u2022 just now";
    menuWrapper.style.visibility = "hidden";
    setTimeout(() => {
      const currentIcon = headerRow.firstElementChild;
      const doneTemp = document.createElement("div");
      doneTemp.innerHTML = doneIconHtml;
      headerRow.replaceChild(doneTemp.firstElementChild, currentIcon);
      meta.textContent = "Done \u2022 just now";
      const board = card.closest(".kb-board");
      if (!board) return;
      const doneCol = board.querySelector('[data-kb-column="done"]');
      if (!doneCol) return;
      const cardList = doneCol.querySelector("[data-kb-cards]");
      const readyCol = board.querySelector('[data-kb-column="ready"]');
      card.style.transition = "opacity 300ms, transform 300ms";
      card.style.opacity = "0";
      card.style.transform = "translateY(-10px)";
      setTimeout(() => {
        card.parentElement.removeChild(card);
        if (readyCol) {
          const rc = readyCol.querySelector("[data-kb-count]");
          rc.textContent = parseInt(rc.textContent) - 1;
        }
        const dc = doneCol.querySelector("[data-kb-count]");
        dc.textContent = parseInt(dc.textContent) + 1;
        card.style.opacity = "0";
        card.style.transform = "translateY(6px)";
        cardList.insertBefore(card, cardList.firstChild);
        requestAnimationFrame(() => {
          card.style.transition = "opacity 300ms, transform 300ms";
          card.style.opacity = "1";
          card.style.transform = "translateY(0)";
        });
      }, 300);
    }, 3000);
  };
  return <div className="task-card" style={{
    fontFamily: "'IBM Plex Sans', sans-serif",
    width: width,
    background: "var(--tc-bg)",
    border: "1px solid var(--tc-border)",
    borderRadius: "8px",
    padding: "8px 12px",
    margin: 0,
    display: "flex",
    flexDirection: "column",
    gap: "4px",
    cursor: "pointer",
    opacity: 0,
    animation: "task-card-fade-in 300ms ease-out forwards",
    transition: "border-color 120ms ease-out"
  }}>
      {}
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px"
  }}>
        {status === "done" ? <svg width="16" height="16" viewBox="0 0 24 24" fill="var(--tc-text-dimmest)" style={{
    flexShrink: 0
  }}>
            <path d="M12 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" />
            <path fillRule="evenodd" d="M12 1c6.075 0 11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12 5.925 1 12 1Zm0 2a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z" clipRule="evenodd" />
          </svg> : status === "draft-static" ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{
    flexShrink: 0
  }}>
            <circle cx="5.332" cy="5.338" r="5.332" fill={colors.dark} />
            <circle cx="18.663" cy="5.338" r="5.332" fill={colors.dark} />
            <circle cx="5.332" cy="18.668" r="5.332" fill={colors.dark} />
            <circle cx="18.663" cy="18.668" r="5.332" fill={colors.dark} />
          </svg> : status === "draft-done" ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{
    flexShrink: 0
  }}>
            <circle cx="5.332" cy="5.338" r="5.332" fill={colors.light} />
            <circle cx="18.663" cy="5.338" r="5.332" fill={colors.light} />
            <circle cx="5.332" cy="18.668" r="5.332" fill={colors.light} />
            <circle cx="18.663" cy="18.668" r="5.332" fill={colors.light} />
          </svg> : status === "active-queued" ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{
    flexShrink: 0
  }}>
            <circle cx="5.332" cy="5.338" r="5.332" fill={colors.light} />
            <circle cx="18.663" cy="5.338" r="5.332" fill={colors.light} />
            <circle cx="5.332" cy="18.668" r="5.332" fill={colors.light} />
            <circle cx="18.663" cy="18.668" r="5.332" fill={colors.light} />
          </svg> : status === "ready-drop" ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{
    flexShrink: 0
  }}>
            <circle cx="5.332" cy="5.338" r="5.332" fill={colors.light} />
            <circle cx="18.663" cy="18.668" r="5.332" fill={colors.light} />
            <path fill={colors.dark} d="M18.662 0a5.332 5.332 0 0 1 .001 10.664l-.412.01a8 8 0 0 0-7.577 7.59l-.01.398-.008.274A5.331 5.331 0 0 1 0 18.662a5.332 5.332 0 0 1 5.332-5.332l.398-.01a8 8 0 0 0 7.59-7.576l.011-.412A5.331 5.331 0 0 1 18.662 0Z" />
          </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{
    flexShrink: 0,
    "--tc-dark": colors.dark,
    "--tc-light": colors.light
  }}>
            <circle className="tc-el-tl" cx="5.332" cy="5.338" r="5.332" fill={colors.dark} />
            <circle className="tc-el-tr" cx="18.663" cy="5.338" r="5.332" fill={colors.dark} />
            <circle className="tc-el-bl" cx="5.332" cy="18.668" r="5.332" fill={colors.dark} />
            <circle className="tc-el-br" cx="18.663" cy="18.668" r="5.332" fill={colors.dark} />
            <path className="tc-el-drop" fill={colors.dark} d="M18.662 0a5.332 5.332 0 0 1 .001 10.664l-.412.01a8 8 0 0 0-7.577 7.59l-.01.398-.008.274A5.331 5.331 0 0 1 0 18.662a5.332 5.332 0 0 1 5.332-5.332l.398-.01a8 8 0 0 0 7.59-7.576l.011-.412A5.331 5.331 0 0 1 18.662 0Z" />
            <g className="tc-el-dropalt" transform="rotate(90, 12, 12)">
              <path fill={colors.dark} d="M18.662 0a5.332 5.332 0 0 1 .001 10.664l-.412.01a8 8 0 0 0-7.577 7.59l-.01.398-.008.274A5.331 5.331 0 0 1 0 18.662a5.332 5.332 0 0 1 5.332-5.332l.398-.01a8 8 0 0 0 7.59-7.576l.011-.412A5.331 5.331 0 0 1 18.662 0Z" />
            </g>
          </svg>}
        <span style={{
    flex: 1,
    fontSize: "14px",
    lineHeight: "1.4",
    color: "var(--tc-text)",
    minWidth: 0
  }}>
          {title}
        </span>
        <div style={{
    position: "relative",
    flexShrink: 0
  }}>
          <button className="tc-menu-btn" type="button" aria-label="Thread actions" onClick={showMenu ? e => {
    const btn = e.currentTarget;
    const popup = btn.nextElementSibling;
    const card = btn.closest(".task-card");
    const isHidden = popup.style.display === "none";
    popup.style.display = isHidden ? "flex" : "none";
    btn.style.background = isHidden ? "var(--tc-border)" : "none";
    if (card) card.style.zIndex = isHidden ? "9999" : "";
  } : undefined} style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    width: "24px",
    height: "24px",
    border: "none",
    background: "none",
    borderRadius: "4px",
    cursor: "pointer",
    color: "var(--tc-text-dimmest)",
    padding: 0,
    flexShrink: 0,
    transition: "background 120ms ease-out"
  }}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path fillRule="evenodd" d="M10.25 5a1.75 1.75 0 1 1 3.5 0 1.75 1.75 0 0 1-3.5 0Zm0 7a1.75 1.75 0 1 1 3.5 0 1.75 1.75 0 0 1-3.5 0Zm0 7a1.75 1.75 0 1 1 3.5 0 1.75 1.75 0 0 1-3.5 0Z" clipRule="evenodd" />
            </svg>
          </button>
          {showMenu && <div style={{
    display: "none",
    position: "absolute",
    top: 0,
    left: "28px",
    background: "var(--tc-bg)",
    border: "1px solid var(--tc-border)",
    borderRadius: "8px",
    padding: "4px",
    boxShadow: "0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06)",
    zIndex: 9999,
    minWidth: "160px",
    flexDirection: "column",
    gap: "0"
  }}>
              <div onClick={handleApplyChanges} style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "6px 8px",
    borderRadius: "4px",
    cursor: "pointer",
    color: "var(--tc-text)",
    fontSize: "14px"
  }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style={{
    flexShrink: 0
  }}>
                  <path fillRule="evenodd" d="M6 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM2.25 6a3.75 3.75 0 1 1 4.527 3.67 8.25 8.25 0 0 0 7.554 7.553A3.751 3.751 0 0 1 21.75 18a3.75 3.75 0 0 1-7.43.726 9.75 9.75 0 0 1-7.57-4.53V21a.75.75 0 0 1-1.5 0V9.675A3.751 3.751 0 0 1 2.25 6ZM18 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z" clipRule="evenodd" />
                </svg>
                <span>Apply changes</span>
              </div>
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "6px 8px",
    borderRadius: "4px",
    cursor: "pointer",
    color: "var(--tc-text)",
    fontSize: "14px"
  }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style={{
    flexShrink: 0
  }}>
                  <path fillRule="evenodd" d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" clipRule="evenodd" />
                </svg>
                <span>Cancel</span>
              </div>
            </div>}
        </div>
      </div>
      {}
      {description && <div style={{
    fontSize: "12px",
    color: "var(--tc-text-dim)",
    lineHeight: "1.5",
    overflow: "hidden",
    display: "-webkit-box",
    WebkitLineClamp: 3,
    WebkitBoxOrient: "vertical",
    paddingLeft: "0"
  }}>
          {description}
        </div>}
      {}
      <div style={{
    fontSize: "12px",
    color: "var(--tc-text-dimmest)",
    paddingLeft: "0"
  }}>
        {statusLabels[status] || status} • {timestamp}
      </div>
    </div>;
};

## 작업 보드 작동 방식

작업 보드는 Agent 작업을 검토, 시작, 모니터링, 적용할 수 있는 한 곳입니다. Agent가 큰 요청을 여러 조각으로 분할하거나, 백그라운드에서 작업을 실행하고 싶거나, 프로젝트에서 이미 변경된 내용을 이해해야 할 때 사용하세요.

<Frame>
  <img src="https://mintcdn.com/replit/L22mbBMLs80H8_c8/images/replitai/task-board-progress.png?fit=max&auto=format&n=L22mbBMLs80H8_c8&q=85&s=3b4ef2fbede2d057e40c138956ccd74d" alt="초안, 활성, 준비, 완료 열로 구성된 작업들을 보여주는 작업 보드" width="3456" height="1984" data-path="images/replitai/task-board-progress.png" />
</Frame>

작업은 상태에 따라 열로 구성됩니다. 보드에서는 Agent가 계획한 작업, 실행 중인 작업, 검토 준비가 된 작업, 이미 적용된 작업을 확인할 수 있습니다.

각 작업은 진행됨에 따라 왼쪽에서 오른쪽으로 이동합니다:

* **초안**: Agent가 계획했지만 아직 시작하지 않은 작업
* **활성**: Agent가 현재 빌드 중인 작업. 활성 작업 한도를 초과하여 대기 중인 작업 포함
* **준비**: Agent가 완료하여 검토를 기다리는 작업
* **완료**: 완료, 보관, 또는 취소된 작업

## 초안

**초안**은 Agent가 계획했지만 아직 빌드를 시작하지 않은 작업입니다. 각 초안에는 제목, 설명, 단계별 계획이 포함됩니다.

초안을 사용하여 작업이 시작되기 전에 Agent의 제안된 접근 방식을 검토하세요. 계획을 검사하고, Agent에게 조정을 요청하거나, 준비가 되면 작업을 시작할 수 있습니다.

초안은 더 큰 기능을 커밋하기 전에 계획을 세우고 싶을 때 유용합니다. 예를 들어, Agent는 온보딩 프로젝트를 인증, 프로필 설정, 대시보드 UI, 배포 마무리 등의 별도 초안으로 분할할 수 있습니다.

## 활성

작업을 수락하면 **활성** 열로 이동하고 Agent가 빌드를 시작합니다. 각 작업은 프로젝트의 격리된 복사본에서 실행되므로, 작업을 적용하기 전까지 메인 버전에는 아무것도 변경되지 않습니다.

일부 작업은 다른 작업에 의존합니다. 예를 들어, 대시보드를 빌드하는 작업은 데이터베이스 스키마 작업이 먼저 완료되어야 할 수 있습니다. 종속 작업은 전제 조건이 완료될 때까지 **큐에 추가됨** 상태로 대기합니다. 플랜의 활성 백그라운드 작업 한도에 도달하면 작업도 큐에 추가됩니다.

<KanbanColumns
  columns={[
{ title: 'Drafts', count: 2, cards: [
{ title: '가족과 친구를 위한 선물 흐름 추가', description: '부모가 다른 아이를 위한 이야기를 만들어 선물로 보낼 수 있게 합니다. 새로운 고객 확보 채널을 열어줍니다.', status: 'draft-static', timestamp: '2m' },
{ title: '어두운 테마가 있는 취침 독서 모드', description: '따뜻한 톤의 어두운 테마와 주변 소리가 있는 전용 취침 모드로 앱이 완전한 취침 의식이 됩니다.', status: 'draft-static', timestamp: '2m' },
] },
{ title: 'Active', count: 3, cards: [
{ title: '이야기 연속: 속편', description: '"모험 계속하기" 버튼을 추가하면 이전 이야기가 끝난 곳에서 시작되는 속편이 생성됩니다.', status: 'active', timestamp: '12m' },
{ title: '이야기 재생성: 페이지 다시 작성', description: '개별 페이지를 다시 생성하는 기능을 추가하면 부모에게 제어권을 주고 불만을 줄여줍니다.', status: 'active', timestamp: '8m' },
{ title: '가족과 친구를 위한 선물 흐름 추가', description: '부모가 다른 아이를 위한 이야기를 만들어 선물로 보낼 수 있게 합니다.', status: 'active-queued', timestamp: '1m' },
] }
]}
/>

## 준비

작업이 빌드를 완료하면 **준비** 상태로 이동합니다. 이는 작업이 완료되었지만 메인 버전에는 아직 아무것도 변경되지 않았음을 의미합니다.

두 가지 방법으로 변경 사항을 적용할 수 있습니다:

* **스레드에서**: 작업의 스레드를 열고 **메인 버전에 변경 사항 적용**을 선택하여 작업을 프로젝트에 반영하거나, **해제**를 선택하여 취소합니다
* **보드에서**: 준비 작업 카드의 점 세 개 메뉴를 선택한 다음 **변경 사항 적용**을 선택합니다

<TaskReviewDrawer />

준비 작업을 적용하기 전에 검토하세요. 작업 로그, 테스트 결과, 미리보기를 확인하여 Agent가 변경한 내용을 이해하세요.

## 완료

적용되면 작업 카드가 **완료** 열로 이동합니다.

완료 열에는 보관된 계획 세션과 취소된 작업도 포함됩니다. 이를 통해 완료된 작업을 초안이나 활성으로 혼용하지 않고 무슨 일이 있었는지 기록을 볼 수 있습니다.

<KanbanColumns
  columns={[
{ title: 'Ready', count: 3, cards: [
{ title: '어두운 테마가 있는 취침 독서 모드', description: '따뜻한 톤의 어두운 테마와 주변 소리가 있는 전용 취침 모드.', status: 'ready-drop', timestamp: '35m', showMenu: true },
{ title: 'OG 미리보기가 있는 공유 가능한 이야기 링크', description: '공개 공유 가능한 링크 추가가 가장 큰 성장 레버입니다.', status: 'ready-drop', timestamp: '31m', showMenu: true },
{ title: '향상된 캐릭터 커스터마이징', description: '외모 옵션을 추가하면 일러스트레이션이 진정으로 개인화된 느낌이 납니다.', status: 'ready-drop', timestamp: '28m', showMenu: true },
] },
{ title: 'Done', count: 4, cards: [
{ title: '단어 강조 표시가 있는 소리 내어 읽기 모드', description: '텍스트 음성 변환을 통해 아이들이 독립적으로 이야기를 즐길 수 있습니다.', status: 'done', timestamp: '4h' },
{ title: 'OG 미리보기가 있는 공유 가능한 이야기 링크', description: '가장 큰 성장 레버인 공개 공유 가능한 링크.', status: 'done', timestamp: '1h' },
{ title: '향상된 캐릭터 커스터마이징', description: '진정으로 개인화된 일러스트레이션을 위한 외모 옵션.', status: 'done', timestamp: '4h' },
{ title: '모바일 반응성 및 프로덕션 마무리', description: '개선된 오류 상태, 모바일 레이아웃, 로딩 애니메이션.', status: 'done', timestamp: '4h' },
] }
]}
/>

여러 작업의 변경 사항을 적용할 때 Agent가 충돌을 자동으로 처리합니다. 뭔가 이상해 보이면 Agent에게 수정을 요청하세요.

## 작업 찾기 및 필터링

프로젝트에 진행 중인 작업이 많을 때 작업 보드를 사용하세요. 작업 제목이나 작업 번호로 검색하고, 초안을 필터링하고, 편집 모드를 사용하여 작업 그룹을 한 번에 관리할 수 있습니다.

예를 들어, 스레드, 리뷰, 또는 Slack 대화에서 누군가가 작업을 언급할 때 작업 번호로 검색하세요. 활성 또는 완료된 작업을 스캔하지 않고 계획된 작업에 집중하고 싶을 때 초안을 필터링하세요.

제안된 후속 작업은 [후속 작업](/references/agent/follow-up-tasks)을 참조하세요.
