import { For, createSignal, createMemo } from "solid-js";
import { useNavigate, useParams } from "@tanstack/solid-router";
import { getEmailsForMailbox } from "~/data/emails";
import { MailListItem } from "~/components/MailListItem";
export function MailList() {
const params = useParams({ strict: false }) as {
mailbox?: string;
id?: string;
};
const navigate = useNavigate();
const [query, setQuery] = createSignal("");
const mailbox = () => params.mailbox || "inbox";
const list = createMemo(() => {
const q = query().toLowerCase();
return getEmailsForMailbox(mailbox()).filter(
(e) =>
!q ||
e.subject.toLowerCase().includes(q) ||
e.snippet.toLowerCase().includes(q)
);
});
function open(id: string) {
navigate({
to: "/mail/$mailbox/$id",
params: { mailbox: mailbox(), id },
search: (prev) => prev,
});
}
return (
<For each={list()}>
{(e) => (
<div
role="listitem"
tabindex={0}
onClick={() => open(e.id)}
onKeyDown={(ev) => ev.key === "Enter" && open(e.id)}
class="mail-item"
data-selected={params.id === e.id ? "true" : undefined}
aria-selected={params.id === e.id ? "true" : undefined}
>
<div>
<div class="mail-item-subject truncate">{e.subject}</div>
<div class="mail-item-snippet truncate">{e.snippet}</div>
</div>
<time
class="text-xs muted"
datetime={e.date}
title={new Date(e.date).toLocaleString()}
>
{new Date(e.date).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})}
</time>
</div>
<MailListItem
email={e}
isSelected={params.id === e.id}
onOpen={open}
/>
)}
</For>
);
}
import type { Email } from "~/types";
interface MailListItemProps {
email: Email;
isSelected: boolean;
onOpen: (id: string) => void;
}
export function MailListItem(props: MailListItemProps) {
return (
<div
role="listitem"
tabindex={0}
onClick={() => props.onOpen(props.email.id)}
onKeyDown={(ev) => ev.key === "Enter" && props.onOpen(props.email.id)}
class="mail-item"
data-selected={props.isSelected ? "true" : undefined}
aria-selected={props.isSelected ? "true" : undefined}
>
<div>
<div class="mail-item-subject truncate">{props.email.subject}</div>
<div class="mail-item-snippet truncate">{props.email.snippet}</div>
</div>
<time
class="text-xs muted"
datetime={props.email.date}
title={new Date(props.email.date).toLocaleString()}
>
{new Date(props.email.date).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})}
</time>
</div>
);
- 새로 생성
MailListItem 개별 메일 목록 항목 렌더링을 처리하는 컴포넌트 - 다음에서 메일 항목 JSX와 로직 추출
MailList 새MailListItem 컴포넌트로 - 업데이트
MailList 새 컴포넌트 사용을 위해MailListItem 컴포넌트로 - 프로젝트를 빌드하여 변경 사항이 올바르게 컴파일되는지 확인
- 애플리케이션을 테스트하여 기능이 그대로 유지되는지 확인
- 개별 메일 목록 항목을 렌더링하기 위한 재사용 가능한 컴포넌트 생성
- 수락
email ,isSelected , 및onSelect props - 모든 접근성 기능 유지 (ARIA 역할, 키보드 탐색)
- 스타일 및 상호작용 동작 보존
- 인라인 메일 항목 렌더링 로직을 제거하여 간소화
- 이제
MailListItem 컴포넌트를 깔끔하게 사용하여 prop 전달 - 컴포넌트 복잡성 감소 및 가독성 향상
- ✅ 빌드 성공
- ✅ 모든 기능 정상 작동 (필터링, 선택, 탐색)
- ✅ 키보드 접근성 보존 (Enter 키 탐색)
- ✅ 시각적 선택 상태 정상 작동
당신을 대신해 구축하는 에이전트
작업을 AI 에이전트에게 맡기세요. 에이전트는 자율적으로 계획을 세우고, 코드를 변경하고, 명령어를 실행하며 작업이 완료될 때까지 반복합니다.
예를 들어, 백그라운드에서 버그를 분류하고 수정하도록 CLI 기반 에이전트를 할당하거나, 통합 브라우저에서 실시간 검증을 사용하여 기능을 구현하도록 다른 에이전트와 상호 작용하세요. 또한 홈페이지 리디자인 작업을 팀이 검토할 수 있도록 풀 리퀘스트를 생성하는 클라우드 에이전트에게 위임할 수 있습니다.
에이전트 시작하기package http
import (
"io"
"log/slog"
"mime/multipart"
"net/http"
"strings"
)
type BatchItemResult struct {
Name string `json:"name"`
Metadata *struct {
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Bytes int `json:"bytes"`
} `json:"metadata,omitempty"`
Error string `json:"error,omitempty"`
}
type BatchResponse struct {
Results []*BatchItemResult `json:"results"`
Count int `json:"count"`
Success int `json:"success"`
Failed int `json:"failed"`
}
// handleProcessBatch processes multiple uploaded images (multipart/form-data) under the field name "files".
// It returns metadata for each image or an error per item without failing the whole batch unless the request is malformed.
func (s *Server) handleProcessBatch(w http.ResponseWriter, r *http.Request) {
// Enforce max body size overall.
r.Body = http.MaxBytesReader(w, r.Body, s.cfg.MaxUploadBytes)
if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(ct, "multipart/form-data") {
s.writeJSON(w, http.StatusBadRequest, map[string]string{"error": "content type must be multipart/form-data"})
return
}
if err := r.ParseMultipartForm(s.cfg.MaxUploadBytes); err != nil {
status := http.StatusBadRequest
if strings.Contains(err.Error(), "request body too large") {
status = http.StatusRequestEntityTooLarge
}
s.writeJSON(w, status, map[string]string{"error": "invalid multipart form: " + err.Error()})
return
}
// Accept files under the key "files". If absent, attempt to fallback to any file parts.
var fileHeaders []*multipart.FileHeader
if r.MultipartForm != nil && len(r.MultipartForm.File["files"]) > 0 {
fileHeaders = r.MultipartForm.File["files"]
} else if r.MultipartForm != nil {
// Fallback: gather all files across keys.
for _, fhs := range r.MultipartForm.File {
fileHeaders = append(fileHeaders, fhs...)
}
}
if len(fileHeaders) == 0 {
s.writeJSON(w, http.StatusBadRequest, map[string]string{"error": "no files provided (expect key 'files')"})
return
}
resp := &BatchResponse{Results: make([]*BatchItemResult, 0, len(fileHeaders))}
for _, fh := range fileHeaders {
item := &BatchItemResult{Name: fh.Filename}
f, err := fh.Open()
if err != nil {
item.Error = "open file: " + err.Error()
resp.Results = append(resp.Results, item)
resp.Failed++
continue
}
data, err := io.ReadAll(f)
_ = f.Close()
if err != nil {
item.Error = "read file: " + err.Error()
resp.Results = append(resp.Results, item)
resp.Failed++
continue
}
meta, err := s.proc.ExtractMetadata(data)
if err != nil {
item.Error = err.Error()
resp.Results = append(resp.Results, item)
resp.Failed++
continue
}
// Copy into anonymous struct to decouple from internal type if it changes.
item.Metadata = &struct {
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Bytes int `json:"bytes"`
}{Format: meta.Format, Width: meta.Width, Height: meta.Height, Bytes: meta.Bytes}
resp.Results = append(resp.Results, item)
resp.Success++
}
resp.Count = len(resp.Results)
s.log.LogAttrs(r.Context(), slog.LevelInfo, "batch processed", slog.Int("count", resp.Count), slog.Int("success", resp.Success), slog.Int("failed", resp.Failed))
s.writeJSON(w, http.StatusOK, resp)
}
할 일 목록 작성 중
기존 서버 및 프로세서 코드 읽기, 배치 엔드포인트 계약 설계, 핸들러가 포함된 새 파일 구현 등의 작업을 관리하며 할 일 목록 작성을 시작합니다. 핸들러를 서버 경로에 연결하고 배치 엔드포인트에 대한 최소한의 테스트를 추가할 계획입니다(선택 사항). 이후 모든 것을 테스트하고 빌드할 예정입니다. 실행합시다!파일 처리 개선 중...
server.go를 수정할 수도 있지만, 새로운 파일을 요청했기 때문에 /process/batch라는 새 경로를 생성해야 합니다. 최대 본문 크기를 제한하고 콘텐츠 유형이 multipart/form-data인지 확인하겠습니다. 다음으로 폼을 파싱하고 form.File["files"]를 반복할 것입니다. 각 FileHeader에 대해 파일을 열고 바이트를 읽은 다음 ExtractMetadata를 호출하여 결과를 수집하겠습니다. 마지막으로 결과가 포함된 구조화된 JSON 응답을 반환하고 파일이 없거나 최대 배치 제한 초과 등의 예외 상황을 처리하겠습니다. 절대적으로 필요한 것은 아니지만, 동시성을 제한하면서 각 파일을 순차적으로 처리하기 위해 sync.WaitGroup을 사용하는 작은 개선을 고려 중입니다. 이렇게 하면 초기 커밋이 더 간단해집니다. 코드 파일을 추가하고 internal/image 참조 별칭을 가져와야 합니다. 다음으로 tests/image_processor_test.go에 있는 기존 테스트를 확인하여 스타일을 파악하고 배치 핸들러를 위한 새 테스트 파일을 생성할지 결정하겠습니다. 내용을 읽은 후, 첫 번째와 두 번째 작업 완료를 표시하며 할 일 상태를 업데이트하겠습니다.HTTP 테스트 준비 중
HTTP 테스트를 실행해야 하는데, 현재 테스트 패키지가 메인 모듈 내부가 아닌 'tests'에 있는 것을 확인했습니다. HTTP 테스트를 수행하려면 설정, 프로세서, 로거, 서버를 설정하고 멀티파트 요청을 보내야 합니다. 다음 단계는 포함해야 할 필수 필드를 이해하기 위해 config.go의 설정을 확인하는 것입니다. 시작해 봅시다!업데이트 및 패치 관리
모든 작업을 조직적으로 관리하기 위해 manage_todo_list를 사용해야 합니다. 완료되면 패치 적용을 진행할 수 있습니다. 여기에는 새 파일 추가와 server.go 편집이 포함됩니다. 이 과정에서 실수를 방지하기 위해 모든 것이 명확하고 정확한지 확인하고 싶습니다. 계속 진행합시다!업데이트 및 테스트 구현 중
계약 설계를 완료로 표시하고 업데이트를 구현해야 합니다. 할 일 목록을 관리하고 패치를 적용하여 파일을 추가하고 server.go를 업데이트하겠습니다. server.go에 /process/batch 경로를 추가하고 internal/http/batch.go라는 새 파일을 구현해야 할 것 같습니다. 코드를 작성한 후 테스트 파일을 추가하고 go test로 테스트를 실행하며 run_in_terminal 명령을 사용하겠습니다. 시작합시다!-
server.go : 새 경로 POST /process/batch 등록. batch.go : 새 핸들러 handleProcessBatch 추가-
- multipart/form-data 허용.
- files 키 아래 여러 파일 읽기 (파일 누락 시 모든 파일 파트 활용).
- 전체 배치 실패 없이 파일별 성공/오류 반환.
- 카운트 집계: 총 개수, 성공, 실패.
-
batch_endpoint_test.go : 해피 패스(유효 파일 1개 + 무효 파일 1개) 및 파일 누락 오류 케이스 테스트.
- 배치당 최대 파일 수 설정 추가 (예: config를 통해).
- 매우 큰 이미지를 위한 제한된 메모리 스트리밍 처리 (처리 후 폐기).
- 작업자 풀(worker pool) 및 컨텍스트 취소 기능을 사용한 선택적 병렬 처리.
- 디코딩 전 파일별 크기 검증 추가.
- 멀티파트가 아닌 클라이언트를 위한 JSON 기반 배치(URL 또는 base64) 지원.
어떤 에이전트든, 어떤 모델이든
워크플로에 맞는 에이전트 하네스를 사용하세요. Copilot이나 Claude, OpenAI와 같은 타사 제공업체와 함께 로컬 또는 클라우드에서 에이전트를 실행하세요.
빠른 완료 모델부터 고급 추론 모델까지, 제공업체 전반에 걸친 수십 가지 모델 중에서 선택하세요. 또는 자체 키를 사용하여 모든 제공업체의 모든 모델을 사용할 수 있습니다.
모든 세션을 한눈에
여러 에이전트가 작업을 병렬로 수행하는 동안 생산성을 유지하세요. 에이전트가 어디서 실행되든 상관없이 단일 뷰에서 모든 에이전트 세션을 추적하세요.
다른 도구나 터미널로 전환할 필요 없이 세션을 빠르게 필터링하고 모니터링하거나 개별 에이전트 상호 작용을 자세히 살펴보세요.
당신의 규칙, 당신의 에이전트
에이전트가 귀하의 관행과 팀 워크플로를 따르도록 하세요. 사용자 지정 지침을 정의하고, 에이전트 기술을 추가하거나, 프로젝트에 맞춘 사용자 지정 에이전트를 구축하세요.
MCP 서버를 사용하여 외부 도구 및 서비스에 연결하거나, 에이전트 플러그인 또는 확장 프로그램을 설치하여 에이전트의 기능을 확장하세요.
핵심은 세계 최고 수준의 코드 편집기
VS Code는 10년 넘게 수백만 명의 개발자가 선택한 편집기입니다. AI 기반 인라인 제안, 지능형 완성 기능, 풍부한 편집 경험은 직접 코드를 작성할 때도 동일하게 강력한 성능을 제공합니다.
동일한 편집기 내에서 에이전트 작업과 직접 코딩 작업을 원활하게 전환하세요.
VS Code 시작하기import numpy as np
import pandas as pd
iris_data = pd.read_csv("iris_dataset.csv")
def describe(species: str) -> pd.Series:
7
subset = data[data["species"] == species]
subset = iris_data[iris_data["species"] == species]
if subset.empty:
raise ValueError(f"{species} missing from sample")
return subset[["petal", "sepal"]].agg(["mean", "std"]).loc["mean"]
def summary():
13
for species in np.sort(data["species"].unique()):
for species in np.sort(iris_data["species"].unique()):
try:
stats = describe(species)
except ValueError:
print(f"{species}: no records")
continue
print(f"{species}: petal={stats['petal']:.2f} sepal={stats['sepal']:.2f}")
if __name__ == "__main__":
summary()
확장 프로그램과 함께 코딩
확장 프로그램 및 모델 컨텍스트 프로토콜(MCP) 서버의 도구로 에이전트를 확장하세요. 또는 직접 확장 프로그램을 빌드하여 팀의 고유한 시나리오를 지원하세요.
확장 프로그램 마켓플레이스에서 8만 개 이상의 확장 프로그램 보기
어떤 언어로든 코딩
VS Code는 거의 모든 주요 프로그래밍 언어를 지원합니다. JavaScript, TypeScript, CSS, HTML과 같은 언어는 기본적으로 제공되며, 기타 언어를 위한 확장 프로그램은 VS Code 마켓플레이스에서 찾을 수 있습니다.

JavaScript
TypeScript
Python
C#
C++
HTML
Java
JSON
PHP
Markdown
Powershell
YAML완벽한 사용자 지정
코딩 스타일에 맞게 VS Code UI와 레이아웃을 사용자 지정하세요.
색상 테마를 사용하면 선호도와 작업 환경에 맞게 VS Code 사용자 인터페이스의 색상을 변경할 수 있습니다.
설정 동기화(Settings Sync) 기능을 사용하면 VS Code 인스턴스 간에 사용자 설정을 공유할 수 있습니다.
프로필을 사용하면 사용자 지정 세트를 만들고 빠르게 전환하거나 다른 사람들과 공유할 수 있습니다.
어디서나 코딩
클라우드 연결, 원격 저장소, 웹용 VS Code(vscode.dev)를 통한 브라우저 환경 등 어디서든 가장 생산적인 환경에서 코딩하세요.
내장 소스 제어는 Git 지원을 즉시 제공합니다. 기타 많은 소스 제어 공급자는 확장 프로그램을 통해 사용할 수 있습니다.
GitHub Codespaces는 장기 프로젝트든 풀 리퀘스트 검토와 같은 단기 작업이든 관계없이 모든 활동에 클라우드 기반 개발 환경을 제공합니다.
풍부한 기능으로 코딩
편집기에는 더 많은 기능이 있습니다. 내장 기능을 사용하든 풍부한 확장 프로그램을 사용하든, 모두를 위한 무언가가 준비되어 있습니다.
통합 터미널
zsh, pwsh, git bash 등 원하는 셸을 모두 편집기 내부에서 사용하세요.
코드 실행
편집기를 떠나지 않고 코드를 실행하고 디버깅하세요.
버전 제어
Git 및 기타 여러 소스 제어 공급자에 대한 기본 지원.
빌드 작업
VS Code 내부에서 도구를 실행하고 결과를 분석하세요.
로컬 기록
자동으로 추적되는 로컬 기록으로 변경 사항을 절대 잃어버리지 마세요.
테마
테마는 개성의 연장선입니다. 편집기에 감각을 더하고 자신만의 느낌을 추가하세요.
접근성
스크린 리더, 고대비 테마, 키보드 전용 탐색에 최적화된 경험.
웹 지원
휴대폰, 태블릿, 데스크톱 등 어디서나 코드에 액세스할 수 있습니다.