Skip to content

fix: token 스크립트 수정#11

Merged
dasosann merged 2 commits intomainfrom
feat/background-blur
Jan 26, 2026
Merged

fix: token 스크립트 수정#11
dasosann merged 2 commits intomainfrom
feat/background-blur

Conversation

@dasosann
Copy link
Copy Markdown
Contributor

@dasosann dasosann commented Jan 26, 2026

PR Type

Enhancement


Description

  • 디자인 토큰 자동 생성 스크립트 추가

  • token.json에서 CSS 변수로 변환하는 파이프라인 구축

  • 타이포그래피, 색상, 반경 등 토큰 시스템 정의

  • 홈페이지를 토큰 테스트 페이지로 리뉴얼


Diagram Walkthrough

flowchart LR
  A["token.json<br/>Design Tokens"] -- "generate-tokens.js" --> B["tokens.css<br/>CSS Variables"]
  B -- "import" --> C["globals.css"]
  C -- "apply" --> D["Components<br/>UI Elements"]
Loading

File Walkthrough

Relevant files
Enhancement
generate-tokens.js
토큰 자동 생성 스크립트 구현                                                                                 

scripts/generate-tokens.js

  • token.json 파일을 읽어 CSS 변수로 변환하는 Node.js 스크립트 작성
  • 색상, 반경, 테두리, 불투명도, 폰트 크기 토큰 처리
  • 토큰 참조 해석 기능으로 의미론적 토큰 지원
  • 텍스트 유틸리티 클래스 자동 생성 (text-{size}-{weight} 형식)
+214/-0 
globals.css
토큰 CSS 파일 임포트 추가                                                                                 

app/globals.css

  • tokens.css 파일 import 추가
  • 따옴표 스타일 통일 (작은따옴표 → 큰따옴표)
+2/-1     
page.tsx
홈페이지를 토큰 테스트 페이지로 리뉴얼                                                                       

app/page.tsx

  • 기존 Next.js 템플릿 코드 제거
  • 디자인 토큰 테스트 페이지로 전환
  • 타이포그래피, 색상, 배경, 버튼 섹션 추가
  • 토큰 기반 클래스명 적용 (text-24-700, bg-surface-base 등)
+55/-57 
Configuration changes
tokens.css
생성된 디자인 토큰 CSS 파일                                                                               

app/tokens.css

  • 자동 생성된 CSS 변수 파일 (260줄)
  • 그레이스케일, 핑크, 플레임, 오렌지 색상 정의
  • 반경, 테두리, 불투명도 CSS 변수 선언
  • 타이포그래피 유틸리티 클래스 (text-10-400 ~ text-24-700)
  • 배경, 버튼, 테두리, 텍스트 색상 유틸리티 정의
+260/-0 
settings.json
CSS 린트 설정 업데이트                                                                                     

.vscode/settings.json

  • CSS 린트 설정 추가 (unknownAtRules 무시)
  • 커스텀 CSS 변수 사용 시 경고 방지
+2/-1     
Miscellaneous
token.json
토큰 파일 위치 변경                                                                                           

app/src/token.json

  • 기존 토큰 파일 삭제 (1279줄)
  • 새로운 위치로 이동 (app/token.json)
+0/-1279


✨ Describe tool usage guide:

Overview:
The describe tool scans the PR code changes, and generates a description for the PR - title, type, summary, walkthrough and labels. The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on a PR.

When commenting, to edit configurations related to the describe tool (pr_description section), use the following template:

/describe --pr_description.some_config1=... --pr_description.some_config2=...

With a configuration file, use the following template:

[pr_description]
some_config1=...
some_config2=...
Enabling\disabling automation
  • When you first install the app, the default mode for the describe tool is:
pr_commands = ["/describe", ...]

meaning the describe tool will run automatically on every PR.

  • Markers are an alternative way to control the generated description, to give maximal control to the user. If you set:
pr_commands = ["/describe --pr_description.use_description_markers=true", ...]

the tool will replace every marker of the form pr_agent:marker_name in the PR description with the relevant content, where marker_name is one of the following:

  • type: the PR type.
  • summary: the PR summary.
  • walkthrough: the PR walkthrough.
  • diagram: the PR sequence diagram (if enabled).

Note that when markers are enabled, if the original PR description does not contain any markers, the tool will not alter the description at all.

Custom labels

The default labels of the describe tool are quite generic: [Bug fix, Tests, Enhancement, Documentation, Other].

If you specify custom labels in the repo's labels page or via configuration file, you can get tailored labels for your use cases.
Examples for custom labels:

  • Main topic:performance - pr_agent:The main topic of this PR is performance
  • New endpoint - pr_agent:A new endpoint was added in this PR
  • SQL query - pr_agent:A new SQL query was added in this PR
  • Dockerfile changes - pr_agent:The PR contains changes in the Dockerfile
  • ...

The list above is eclectic, and aims to give an idea of different possibilities. Define custom labels that are relevant for your repo and use cases.
Note that Labels are not mutually exclusive, so you can add multiple label categories.
Make sure to provide proper title, and a detailed and well-phrased description for each label, so the tool will know when to suggest it.

Inline File Walkthrough 💎

For enhanced user experience, the describe tool can add file summaries directly to the "Files changed" tab in the PR page.
This will enable you to quickly understand the changes in each file, while reviewing the code changes (diffs).

To enable inline file summary, set pr_description.inline_file_summary in the configuration file, possible values are:

  • 'table': File changes walkthrough table will be displayed on the top of the "Files changed" tab, in addition to the "Conversation" tab.
  • true: A collapsable file comment with changes title and a changes summary for each file in the PR.
  • false (default): File changes walkthrough will be added only to the "Conversation" tab.
Utilizing extra instructions

The describe tool can be configured with extra instructions, to guide the model to a feedback tailored to the needs of your project.

Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Notice that the general structure of the description is fixed, and cannot be changed. Extra instructions can change the content or style of each sub-section of the PR description.

Examples for extra instructions:

[pr_description]
extra_instructions="""- The PR title should be in the format: '<PR type>: <title>'
- The title should be short and concise (up to 10 words)
- ...
"""

Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.

More PR-Agent commands

To invoke the PR-Agent, add a comment using one of the following commands:

  • /review: Request a review of your Pull Request.
  • /describe: Update the PR title and description based on the contents of the PR.
  • /improve [--extended]: Suggest code improvements. Extended mode provides a higher quality feedback.
  • /ask <QUESTION>: Ask a question about the PR.
  • /update_changelog: Update the changelog based on the PR's contents.
  • /help_docs <QUESTION>: Given a path to documentation (either for this repository or for a given one), ask a question.
  • /add_docs 💎: Generate docstring for new components introduced in the PR.
  • /generate_labels 💎: Generate labels for the PR based on the PR's contents.
  • /analyze 💎: Automatically analyzes the PR, and presents changes walkthrough for each component.

See the tools guide for more details.
To list the possible configuration parameters, add a /config comment.

See the describe usage page for a comprehensive guide on using this tool.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @dasosann, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 디자인 토큰 관리 방식을 개선하여, 디자인 시스템의 유지보수성과 일관성을 높이는 것을 목표로 합니다. token.json 정의를 기반으로 CSS 변수와 유틸리티 클래스를 자동 생성하는 스크립트를 도입하고, 이를 애플리케이션에 통합하여 디자인 요소들을 중앙에서 관리할 수 있도록 했습니다. 변경된 app/page.tsx는 이러한 새로운 토큰 기반 스타일링의 실제 적용 사례를 보여줍니다.

Highlights

  • 디자인 토큰 시스템 도입: 기존의 디자인 토큰 정의 방식에서 벗어나, 스크립트를 통해 CSS 변수 및 유틸리티 클래스를 자동 생성하는 새로운 디자인 토큰 시스템이 도입되었습니다.
  • 자동화된 CSS 토큰 생성: 새로운 Node.js 스크립트(scripts/generate-tokens.js)가 추가되어 token.json 파일로부터 디자인 토큰을 읽어 app/tokens.css 파일을 자동으로 생성합니다.
  • 전역 CSS에 토큰 통합: 생성된 tokens.css 파일이 app/globals.css에 임포트되어 애플리케이션 전반에서 디자인 토큰 기반의 스타일을 사용할 수 있게 되었습니다.
  • 디자인 토큰 데모 페이지 업데이트: app/page.tsx 파일이 완전히 재작성되어 새로운 디자인 토큰(타이포그래피, 색상, 버튼 등)이 적용된 UI를 시각적으로 확인할 수 있는 데모 페이지로 변경되었습니다.
  • VS Code 설정 업데이트: VS Code 설정에 css.lint.unknownAtRules: "ignore"가 추가되어, 자동 생성된 CSS 파일에서 발생할 수 있는 알 수 없는 @ 규칙 관련 린팅 경고를 무시하도록 설정했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이번 PR은 token.json에서 디자인 토큰을 자동으로 생성하는 스크립트를 도입하고, 이를 활용한 테스트 페이지를 구현하는 중요한 개선 작업을 포함하고 있습니다. 전반적으로 디자인 시스템의 기반을 마련하는 좋은 시도입니다. 다만, 코드의 유지보수성과 확장성을 높이기 위해 몇 가지 개선점을 제안합니다. generate-tokens.js 스크립트의 중복 코드를 제거하고, 동적으로 토큰을 처리하도록 리팩토링하는 것이 필요해 보입니다. 또한, page.tsx에서 버튼 스타일을 적용하는 방식에 오류가 있어 수정이 필요합니다. 자세한 내용은 각 파일에 남긴 코드 리뷰 코멘트를 참고해주세요.

Comment on lines +37 to +57
if (colors.Pink) {
Object.entries(colors.Pink).forEach(([key, value]) => {
if (key.startsWith("color-pink-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});
}
if (colors.Flame) {
Object.entries(colors.Flame).forEach(([key, value]) => {
if (key.startsWith("color-flame-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});
}
if (colors.Orange) {
Object.entries(colors.Orange).forEach(([key, value]) => {
if (key.startsWith("color-orange-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Pink, Flame, Orange 색상 그룹을 처리하는 로직이 중복되고 있습니다. 코드 중복은 유지보수를 어렵게 만듭니다. colors 객체를 순회하며 동적으로 CSS 변수를 생성하도록 리팩토링하는 것을 권장합니다. 이렇게 하면 새로운 색상 그룹이 추가되어도 스크립트를 수정할 필요가 없습니다.

if (colors) {
  for (const colorGroup of Object.values(colors)) {
    for (const [key, token] of Object.entries(colorGroup)) {
      if (key.startsWith("color-")) {
        cssVariables.push(`  --${sanitizeKey(key)}: ${token.$value};`);
      }
    }
  }
}
References
  1. 중복 코드를 발견하면 재사용 가능한 유틸리티나 컴포넌트로 추출할 것을 제안하세요. (link)

Comment on lines +99 to +140
function resolveValue(value, depth = 0) {
if (depth > 10 || typeof value !== "string") return value;

const match = value.match(/^\{(.+)\}$/);
if (!match) return value;

const ref = match[1];

// Grayscale 참조
if (ref.startsWith("Grayscale.")) {
const key = ref.replace("Grayscale.", "");
return `var(--${key})`;
}

// Colors 참조
if (ref.startsWith("Colors.Pink.")) {
const key = ref.replace("Colors.Pink.", "");
return `var(--${key})`;
}
if (ref.startsWith("Colors.Flame.")) {
const key = ref.replace("Colors.Flame.", "");
return `var(--${key})`;
}
if (ref.startsWith("Colors.Orange.")) {
const key = ref.replace("Colors.Orange.", "");
return `var(--${key})`;
}

// Transparent 참조
if (ref.startsWith("Transparent.")) {
const key = ref.replace("Transparent.", "");
return `var(--${key})`;
}

// System/Mode 1의 Colors 참조
if (ref.startsWith("Colors.")) {
const key = ref.replace("Colors.", "");
return `var(--${key})`;
}

return value;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

resolveValue 함수에서 각 토큰 그룹(Grayscale, Colors.Pink 등)에 대한 참조를 처리하는 로직이 반복적으로 작성되어 있습니다. 이로 인해 새로운 토큰 그룹이 추가될 때마다 코드를 수정해야 하는 번거로움이 있습니다. 참조 경로를 동적으로 해석하는 방식으로 일반화하여 코드 중복을 줄이고 확장성을 높이는 것이 좋습니다.

function resolveValue(value, depth = 0) {
  if (depth > 10 || typeof value !== "string") return value;

  const match = value.match(/^\{(.+)\}$/);
  if (!match) return value;

  const ref = match[1];
  const refParts = ref.split('.');

  // e.g., {Grayscale.color-gray-64} -> var(--color-gray-64)
  const key = refParts[refParts.length - 1];
  return `var(--${sanitizeKey(key)})`;
}
References
  1. 중복 코드를 발견하면 재사용 가능한 유틸리티나 컴포넌트로 추출할 것을 제안하세요. (link)

app/page.tsx Outdated
<button className="btn-primary text-16-600 px-8 py-3 transition-opacity hover:opacity-90">
Primary Button
</button>
<button className="btn-slate text-brand-black border-light text-16-600 rounded-2xl border px-8 py-3">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

이 버튼의 className이 다소 길고 복잡합니다. 스타일 가이드 51번에 따라, 이렇게 여러 유틸리티 클래스가 조합되는 경우 cva (class-variance-authority)를 사용하거나, @layer components@apply를 사용하여 btn-secondary와 같은 컴포넌트 클래스로 추상화하는 것을 고려해볼 수 있습니다. 이렇게 하면 코드 가독성이 향상되고 재사용이 용이해집니다.

References
  1. Tailwind 클래스가 너무 길거나 복잡하면 class-variance-authority (cva) 또는 논리적 그룹핑을 제안하세요. (link)

tailwindUtilities.push(`${className} {
font-size: ${size}px;
font-weight: ${weight};
line-height: 1.5;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

타이포그래피 유틸리티 클래스에서 line-height1.5로 하드코딩되어 있습니다. 디자인 토큰의 일관성과 확장성을 위해 line-height 값도 token.json에서 관리하고, 스크립트에서 이를 읽어 동적으로 생성하는 방식을 고려해보세요. 이렇게 하면 타이포그래피 시스템을 더 유연하게 관리할 수 있습니다.

Comment on lines +1 to +214
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// token.json 읽기
const tokensPath = path.join(__dirname, "../app/token.json");
const tokens = JSON.parse(fs.readFileSync(tokensPath, "utf-8"));

// Semantic tokens에서 실제 색상값 추출
const semanticMode1 = tokens["Sementic/Mode 1"] || {};
const systemMode1 = tokens["System/Mode 1"] || {};

// Grayscale, Colors, Transparent 값 추출
const grayscale = semanticMode1.Grayscale || {};
const colors = semanticMode1.Colors || {};
const transparent = semanticMode1.Transparent || {};

// CSS 변수 생성
let cssVariables = [];

// 키 정제 함수 (공백을 하이픈으로 변경)
function sanitizeKey(key) {
return key.replace(/\s+/g, "-").toLowerCase();
}

// Grayscale 색상
Object.entries(grayscale).forEach(([key, value]) => {
if (key.startsWith("color-gray-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});

// Colors (Pink, Flame, Orange)
if (colors.Pink) {
Object.entries(colors.Pink).forEach(([key, value]) => {
if (key.startsWith("color-pink-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});
}
if (colors.Flame) {
Object.entries(colors.Flame).forEach(([key, value]) => {
if (key.startsWith("color-flame-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});
}
if (colors.Orange) {
Object.entries(colors.Orange).forEach(([key, value]) => {
if (key.startsWith("color-orange-")) {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
}
});
}

// Transparent
Object.entries(transparent).forEach(([key, value]) => {
cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
});

// System tokens
if (systemMode1.Colors) {
Object.entries(systemMode1.Colors).forEach(([key, value]) => {
if (value.$type === "color") {
const finalValue = resolveValue(value.$value);
cssVariables.push(` --${sanitizeKey(key)}: ${finalValue};`);
}
});
}

// Radius
if (systemMode1.Radius) {
Object.entries(systemMode1.Radius).forEach(([key, value]) => {
const varName = key.replace("radius-", "");
cssVariables.push(` --radius-${sanitizeKey(varName)}: ${value.$value}px;`);
});
}

// Border
if (systemMode1.Border) {
Object.entries(systemMode1.Border).forEach(([key, value]) => {
const varName = key.replace("border-width-", "");
cssVariables.push(` --border-${sanitizeKey(varName)}: ${value.$value}px;`);
});
}

// Opacity
if (systemMode1.Opacity) {
Object.entries(systemMode1.Opacity).forEach(([key, value]) => {
const varName = key.replace("opacity-", "");
cssVariables.push(` --opacity-${sanitizeKey(varName)}: ${value.$value}%;`);
});
}

// 토큰 참조 해석
function resolveValue(value, depth = 0) {
if (depth > 10 || typeof value !== "string") return value;

const match = value.match(/^\{(.+)\}$/);
if (!match) return value;

const ref = match[1];

// Grayscale 참조
if (ref.startsWith("Grayscale.")) {
const key = ref.replace("Grayscale.", "");
return `var(--${key})`;
}

// Colors 참조
if (ref.startsWith("Colors.Pink.")) {
const key = ref.replace("Colors.Pink.", "");
return `var(--${key})`;
}
if (ref.startsWith("Colors.Flame.")) {
const key = ref.replace("Colors.Flame.", "");
return `var(--${key})`;
}
if (ref.startsWith("Colors.Orange.")) {
const key = ref.replace("Colors.Orange.", "");
return `var(--${key})`;
}

// Transparent 참조
if (ref.startsWith("Transparent.")) {
const key = ref.replace("Transparent.", "");
return `var(--${key})`;
}

// System/Mode 1의 Colors 참조
if (ref.startsWith("Colors.")) {
const key = ref.replace("Colors.", "");
return `var(--${key})`;
}

return value;
}

// Font Size - text-{size}-{weight} 형태로 생성
const fontSizes = systemMode1?.Font || {};
const weights = ["400", "500", "600", "700"];
let tailwindUtilities = [];

Object.entries(fontSizes).forEach(([key, value]) => {
if (key.startsWith("font-size-")) {
const size = value.$value;
weights.forEach((weight) => {
const className = `.text-${size}-${weight}`;
tailwindUtilities.push(`${className} {
font-size: ${size}px;
font-weight: ${weight};
line-height: 1.5;
}`);
});
}
});

// CSS 파일 생성
const cssContent = `/* Auto-generated from token.json */
/* Run: node scripts/generate-tokens.js to regenerate */

@layer base {
:root {
${cssVariables.join("\n")}
}
}

@layer utilities {
/* Text utilities: text-{size}-{weight} */
${tailwindUtilities.join("\n\n")}

/* Background utilities */
.bg-surface-base { background-color: var(--color-surface-base); }
.bg-brand-secondary-pink { background-color: var(--color-brand-secondary-pink); }
.bg-brand-primary-orange { background-color: var(--color-brand-primary-orange); }
.bg-disabled { background-color: var(--color-background-disabled); }

/* Button utilities */
.bg-button-primary {
background: linear-gradient(135deg, var(--color-brand-primary-flame), var(--color-brand-primary-orange));
}

.bg-button-disabled {
background-color: var(--color-background-disabled);
}

.bg-button-slate {
background-color: var(--color-gray-0-a30);
}

/* Border utilities */
.border-light { border-color: var(--color-border-light); }

/* Text color utilities */
.text-white { color: var(--color-text-white); }
.text-disabled { color: var(--color-text-disabled); }
.text-brand-black { color: var(--color-brand-black); }
}
`;

// tokens.css 파일로 저장
const outputPath = path.join(__dirname, "../app/tokens.css");
fs.writeFileSync(outputPath, cssContent, "utf-8");

console.log("✅ Tokens generated successfully!");
console.log(`📝 Output: ${outputPath}`);
console.log("\nNext steps:");
console.log("1. Import tokens.css in your globals.css:");
console.log(' @import "./tokens.css";');
console.log("\n2. Use utilities in your components:");
console.log(' <div className="bg-surface-base text-16-500">Hello</div>');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

스크립트 파일의 길이가 200줄을 초과합니다. 가이드라인에 따라 파일을 더 작은 모듈로 분리하는 것을 고려해볼 수 있습니다. 예를 들어, 토큰 종류별(색상, 타이포그래피, 간격 등) 파싱 로직을 별도의 함수나 파일로 분리하면 코드의 가독성과 유지보수성이 향상됩니다. 앞서 제안한 중복 코드 리팩토링을 적용하면 파일 크기를 줄이는 데 도움이 될 것입니다.

References
  1. 컴포넌트 파일이 200줄을 초과하면 더 작은 서브 컴포넌트로 분리할 것을 권장하세요. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/describe --pr_description.generate_title=true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scripts/generate-tokens.js 파일의 길이가 여전히 200줄을 초과하고 있습니다. 코드의 가독성과 유지보수성을 높이기 위해 토큰 종류별(색상, 타이포그래피, 간격 등) 파싱 로직을 별도의 함수나 파일로 분리하는 것을 다시 한번 고려해 보시는 것이 좋습니다. 이전에 제안드렸던 중복 코드 리팩토링을 적용하시면 파일 크기를 줄이는 데도 도움이 될 것입니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ask

@gemini-code-assist review

Answer:

전반적으로 “token.json → tokens.css(CSS 변수/유틸) → globals.css import” 파이프라인이 잘 잡혀 있고, @layer base / utilities로 Tailwind와 공존시키려는 방향도 좋습니다. 다만 현재 스크립트/산출물에는 유지보수성과 정확성 측면에서 몇 가지 개선 포인트가 보여요.

1) 스크립트 구조(200줄 초과) 분리 제안 (gemini bot 코멘트 동의)

scripts/generate-tokens.js가 “파일 I/O + 토큰 파싱 + 참조 해석 + CSS 템플릿 생성”을 한 파일에서 전부 담당하고 있어요. 아래처럼 역할 기준으로 함수/모듈 분리하면 가독성과 확장성이 좋아집니다.

  • readTokens(tokensPath)
  • extractSemantic(tokens) / extractSystem(tokens)
  • buildCssVariables({grayscale, colors, transparent, systemMode1})
  • buildTypographyUtilities(systemMode1.Font)
  • buildUtilities() (bg/text/border/button 등)
  • writeCss(outputPath, cssContent)

또한 Pink/Flame/Orange 처리 로직은 거의 동일하므로 공통 함수로 중복 제거가 가능합니다.
예: pushColorGroup(colors, "Pink", "color-pink-")

2) token.json 경로 및 파일 이동 이슈 가능성

스크립트는 ../app/token.json을 읽도록 되어 있는데,
diff에서는 app/src/token.json이 삭제된 것으로 보이고, app/token.json의 추가는 보이지 않습니다.

  • 실제로 token.json이 app/token.json에 존재하는지 확인 필요
  • 만약 경로가 바뀐 거라면 스크립트 경로도 함께 수정하거나, CLI 인자로 받는 형태가 안전합니다.
    • 예: node scripts/generate-tokens.js --input app/src/token.json --output app/tokens.css

3) 키/참조 해석 로직의 일관성 문제 (sanitizeKey / resolveValue)

  • CSS 변수명은 sanitizeKey()로 lower-case 및 공백→하이픈 처리합니다.
  • 그런데 resolveValue()에서 참조를 var(--${key})로 만들 때는 sanitize를 적용하지 않습니다.
  • 현재 token.json 참조는 "{Grayscale.color-gray-64}"처럼 이미 CSS 변수에 가까운 키라서 운 좋게 맞지만,
    opacity-subtle 2처럼 공백이 포함된 키(실제로 token에 있음)는 --opacity-subtle-2로 생성되는데, 참조 해석은 공백을 포함한 채 var(--opacity-subtle 2)가 될 위험이 있습니다(지금은 Opacity는 참조 해석을 안 해서 우연히 피해간 상태).

개선:

  • resolveValue()에서 반환하는 변수명에도 sanitizeKey()를 적용
  • 그리고 depth 파라미터가 선언만 있고 재귀 호출이 없어 의미가 없습니다. 참조가 연쇄되는 경우를 대비해 재귀로 풀거나(필요하다면) depth 제거가 더 명확합니다.

4) 토큰 스펠링/키 안정성: "Sementic/Mode 1"

코드에서 tokens["Sementic/Mode 1"]를 사용 중인데, 일반적으로 “Semantic” 오타로 보입니다.

  • 현재 token.json의 최상위 키가 실제로 Sementic/Mode 1라면 동작은 하겠지만, 추후 수정/도구 연동 시 깨지기 쉬운 지점입니다.
  • 가능하면 소스 토큰 키를 Semantic/Mode 1로 정정하고, 스크립트도 함께 맞추는 것을 추천합니다.
  • 혹은 하위호환으로 tokens["Semantic/Mode 1"] ?? tokens["Sementic/Mode 1"] ?? {} 처럼 방어코드를 두는 것도 방법입니다.

5) 생성된 CSS 품질(포맷/일관성)

app/tokens.css를 보면 :root 내부 들여쓰기/정렬이 다소 깨져 있습니다(변수 라인이 :root {와 같은 레벨처럼 보임).

  • 현재 스크립트가 cssVariables에 이미 " --..."(2칸) 들여쓰기를 넣고, 템플릿에서도 들여쓰기를 또 하고 있어 결과가 애매해졌습니다.
  • “템플릿에서만 들여쓰기” 또는 “데이터에는 들여쓰기 제거” 중 하나로 일관성을 맞추는 것이 좋습니다.
  • Prettier 적용(스크립트에서 prettier.format 사용 또는 CI에서 포맷)을 고려하면 산출물 품질이 안정적입니다.

6) page.tsx에서 사용하는 클래스와 tokens.css의 불일치

page.tsx에는 btn-primary, btn-slate, btn-disabled를 사용하지만,
tokens.css에는 .bg-button-primary, .bg-button-slate, .bg-button-disabled만 생성됩니다.

  • 이 상태면 버튼 스타일이 기대대로 적용되지 않습니다.
  • 해결안:
    1. page에서 bg-button-* 클래스를 사용하도록 수정하거나
    2. generator에서 .btn-primary 등 버튼 컴포넌트용 유틸도 같이 생성(텍스트색/보더/라운드 포함)하도록 확장

7) VSCode 설정 변경(css.lint.unknownAtRules ignore)

@layer, @theme 등으로 경고가 뜨는 문제를 해결하려는 의도는 이해됩니다. 다만 저장소 공용 설정에 넣을 때는 팀 컨센서스가 필요할 수 있어요(개인 환경에선 괜찮지만, repo에 포함되면 모두에게 적용).

  • 대안: .vscode/settings.json 유지하되, 문서에 이유를 간단히 남기거나, stylelint 도입 시 룰로 관리

정리하면: 스크립트 모듈화 + 경로/키 안정성 + sanitize/resolve 일관성 + page에서 사용하는 클래스 불일치 해결 이 4가지만 잡아도, 토큰 파이프라인이 훨씬 견고해질 것 같습니다.

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Jan 26, 2026

PR Description updated to latest commit (354fd18)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File Walkthrough

Enhancement
generate-tokens.js (+214/-0)
토큰 자동 생성 스크립트 구현                                                                                 

scripts/generate-tokens.js

  • token.json 파일을 읽어 CSS 변수로 변환하는 Node.js 스크립트 작성
  • 색상, 반경, 테두리, 불투명도, 폰트 크기 토큰 처리
  • 토큰 참조 해석 기능으로 의미론적 토큰 지원
  • 텍스트 유틸리티 클래스 자동 생성 (text-{size}-{weight} 형식)
globals.css (+2/-1)
토큰 CSS 파일 임포트 추가                                                                                 

app/globals.css

  • tokens.css 파일 import 추가
  • 따옴표 스타일 통일 (작은따옴표 → 큰따옴표)
page.tsx (+55/-57)
홈페이지를 토큰 테스트 페이지로 리뉴얼                                                                       

app/page.tsx

  • 기존 Next.js 템플릿 코드 제거
  • 디자인 토큰 테스트 페이지로 전환
  • 타이포그래피, 색상, 배경, 버튼 섹션 추가
  • 토큰 기반 클래스명 적용 (text-24-700, bg-surface-base 등)
Configuration changes
tokens.css (+260/-0)
생성된 디자인 토큰 CSS 파일                                                                               

app/tokens.css

  • 자동 생성된 CSS 변수 파일 (260줄)
  • 그레이스케일, 핑크, 플레임, 오렌지 색상 정의
  • 반경, 테두리, 불투명도 CSS 변수 선언
  • 타이포그래피 유틸리티 클래스 (text-10-400 ~ text-24-700)
  • 배경, 버튼, 테두리, 텍스트 색상 유틸리티 정의
settings.json (+2/-1)
CSS 린트 설정 업데이트                                                                                     

.vscode/settings.json

  • CSS 린트 설정 추가 (unknownAtRules 무시)
  • 커스텀 CSS 변수 사용 시 경고 방지
Miscellaneous
token.json (+0/-1279)
토큰 파일 위치 변경                                                                                           

app/src/token.json

  • 기존 토큰 파일 삭제 (1279줄)
  • 새로운 위치로 이동 (app/token.json)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@dasosann dasosann merged commit 7e4b810 into main Jan 26, 2026
2 checks passed
@dasosann dasosann deleted the feat/background-blur branch January 26, 2026 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant