@conscience-technology/ctds 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/assets/components/ChatInput/ChatInput.module.css +67 -14
- package/assets/components/ChatInput/ChatInput.tsx +17 -1
- package/assets/components/ChatInput/SendButton.tsx +18 -1
- package/assets/components/ChatInput/index.ts +3 -0
- package/assets/components/Onboarding/OnboardingPage.tsx +1 -0
- package/assets/components/TabStrip/TabStrip.module.css +358 -0
- package/assets/components/TabStrip/TabStrip.tsx +396 -0
- package/assets/components/TabStrip/index.ts +7 -0
- package/assets/components/Toolbar/Toolbar.module.css +34 -7
- package/assets/components/Toolbar/Toolbar.tsx +9 -3
- package/assets/components/Toolbar/index.ts +1 -1
- package/assets/components/_data.ts +5 -3
- package/assets/components/index.ts +1 -0
- package/assets/prompts/ASSEMBLY.md +11 -0
- package/assets/prompts/axes/icon.md +3 -0
- package/assets/prompts/axes/voice.md +7 -4
- package/assets/prompts/components/callout.md +17 -11
- package/assets/prompts/components/chat-input.md +17 -3
- package/assets/prompts/components/list-item.md +22 -0
- package/assets/prompts/components/tab-strip.md +144 -0
- package/assets/prompts/components/toolbar.md +5 -2
- package/assets/prompts/components/topbar.md +1 -0
- package/assets/prompts/primitives/scroll.md +8 -1
- package/assets/prompts/recipes/page-header.md +29 -15
- package/assets/skills/ctds-wireframe/SKILL.md +5 -0
- package/package.json +4 -4
- package/src/commands/init.js +63 -8
- package/src/utils/detect.js +14 -2
- package/src/utils/patch.js +134 -24
- package/assets/prompts/themes/focus.md +0 -72
- /package/assets/prompts/{axes/cascade.md → CASCADE.md} +0 -0
package/src/utils/patch.js
CHANGED
|
@@ -145,41 +145,151 @@ export function patchLayoutTsx(
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
|
-
* next.config.(ts|mjs|js)
|
|
149
|
-
* turbopack.rules
|
|
150
|
-
* `*.module.css` 제외 규칙이 없으면 CSS Modules import가 undefined로 깨짐.
|
|
151
|
-
* (Next create 시 Tailwind 옵션으로 생성된 설정에서 자주 발생)
|
|
148
|
+
* next.config.(ts|mjs|js) 자동 패치:
|
|
149
|
+
* `turbopack.rules["*.css"]` 블록을 **삭제**.
|
|
152
150
|
*
|
|
153
|
-
*
|
|
151
|
+
* 왜: Turbopack glob 매칭은 `.module.css`도 `.css`에 포함시킴.
|
|
152
|
+
* 사용자 룰이 등록되면 built-in Lightning CSS CSS Modules 경로가 bypass되고
|
|
153
|
+
* 결과가 global CSS로 처리돼 `.module.css`의 class export가 전부 undefined.
|
|
154
|
+
* create-next-app이 `@tailwindcss/turbopack` 옵션으로 뽑은 설정이 이 함정에 걸림.
|
|
155
|
+
* Next 16은 PostCSS를 네이티브 지원하므로 Tailwind는 postcss.config로 옮기면 됨.
|
|
154
156
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
157
|
+
* 전략:
|
|
158
|
+
* 1) `"*.css":` 룰 블록을 짝맞춤 `}`로 추적해 통째 삭제 (앞뒤 콤마·공백 포함)
|
|
159
|
+
* 2) `rules: {}` 가 비어지면 `rules` 자체 제거
|
|
160
|
+
* 3) `turbopack: {}` 가 비어지면 `turbopack` 자체 제거
|
|
161
|
+
* 4) 매칭 실패하면 fail → 수동 fallback
|
|
158
162
|
*/
|
|
159
|
-
export function
|
|
163
|
+
export function patchNextConfig(cwd, { dry = false } = {}) {
|
|
164
|
+
const filePath = findNextConfig(cwd);
|
|
165
|
+
if (!filePath) return { status: "skipped", reason: "next.config.(ts|mjs|js) 없음." };
|
|
166
|
+
|
|
167
|
+
const before = readFileSync(filePath, "utf8");
|
|
168
|
+
const cssRuleMatch = before.match(/(["'`])(\*\*\/)?\*\.css\1\s*:\s*\{/);
|
|
169
|
+
if (!cssRuleMatch) {
|
|
170
|
+
return { status: "skipped", reason: "turbopack.rules의 *.css 룰이 없음." };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// "*.css": { ... }, ← 블록 전체 범위 추적
|
|
174
|
+
const keyStart = cssRuleMatch.index;
|
|
175
|
+
const openBrace = cssRuleMatch.index + cssRuleMatch[0].length - 1;
|
|
176
|
+
let depth = 1;
|
|
177
|
+
let closeBrace = -1;
|
|
178
|
+
for (let i = openBrace + 1; i < before.length; i++) {
|
|
179
|
+
const ch = before[i];
|
|
180
|
+
if (ch === "{") depth += 1;
|
|
181
|
+
else if (ch === "}") {
|
|
182
|
+
depth -= 1;
|
|
183
|
+
if (depth === 0) {
|
|
184
|
+
closeBrace = i;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (closeBrace === -1) {
|
|
190
|
+
return { status: "failed", reason: "`*.css` 룰 블록 짝 `}`를 찾지 못함 — 수동 설정 필요." };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 블록 뒤의 선택적 콤마까지 삭제 범위에 포함
|
|
194
|
+
let endExclusive = closeBrace + 1;
|
|
195
|
+
if (before[endExclusive] === ",") endExclusive += 1;
|
|
196
|
+
|
|
197
|
+
// 블록 앞의 라인 선두 공백도 함께 삭제해서 빈 라인이 남지 않게
|
|
198
|
+
let startInclusive = keyStart;
|
|
199
|
+
while (startInclusive > 0 && /[ \t]/.test(before[startInclusive - 1])) {
|
|
200
|
+
startInclusive -= 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let next =
|
|
204
|
+
before.slice(0, startInclusive) +
|
|
205
|
+
before.slice(endExclusive);
|
|
206
|
+
|
|
207
|
+
// 룰 삭제 결과 생긴 공백 라인 하나 정리 (연속 \n\n\n → \n\n)
|
|
208
|
+
next = next.replace(/\n[ \t]*\n[ \t]*\n/g, "\n\n");
|
|
209
|
+
|
|
210
|
+
// `rules: {}` 가 비어졌으면 `rules` 자체 제거
|
|
211
|
+
next = next.replace(/\s*rules\s*:\s*\{\s*\}\s*,?/g, "");
|
|
212
|
+
|
|
213
|
+
// `turbopack: {}` 가 비어졌으면 `turbopack` 자체 제거
|
|
214
|
+
next = next.replace(/\s*turbopack\s*:\s*\{\s*\}\s*,?/g, "");
|
|
215
|
+
|
|
216
|
+
if (next === before) {
|
|
217
|
+
return { status: "skipped", reason: "변경 사항 없음." };
|
|
218
|
+
}
|
|
219
|
+
if (dry) {
|
|
220
|
+
return { status: "applied", reason: "(dry) turbopack.rules *.css 삭제", before, after: next };
|
|
221
|
+
}
|
|
222
|
+
copyFileSync(filePath, filePath + ".bak");
|
|
223
|
+
writeFileSync(filePath, next, "utf8");
|
|
224
|
+
return {
|
|
225
|
+
status: "applied",
|
|
226
|
+
reason: "turbopack.rules의 *.css 룰 삭제 (CSS Modules 복구)",
|
|
227
|
+
filePath,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function findNextConfig(cwd) {
|
|
160
232
|
const candidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
|
|
161
|
-
|
|
233
|
+
for (const name of candidates) {
|
|
234
|
+
const abs = `${cwd}/${name}`;
|
|
235
|
+
if (existsSync(abs)) return abs;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* postcss.config.{mjs,js,cjs,json} 자동 패치/생성:
|
|
242
|
+
* `@tailwindcss/postcss` 플러그인을 추가해 Turbopack native PostCSS 경로로 Tailwind 처리.
|
|
243
|
+
* `@tailwindcss/turbopack`을 쓰던 자리를 대체. 멱등.
|
|
244
|
+
*
|
|
245
|
+
* 1) 설정 파일이 하나도 없고 Tailwind가 설치돼 있으면 postcss.config.mjs 신규 생성
|
|
246
|
+
* 2) 설정 파일이 있고 `@tailwindcss/postcss` 플러그인이 이미 있으면 skip
|
|
247
|
+
* 3) 그 외 (설정 파일 있는데 플러그인 없음) → fail (사용자 수정이 안전)
|
|
248
|
+
*
|
|
249
|
+
* Tailwind 미사용 플젝에서는 skip.
|
|
250
|
+
*/
|
|
251
|
+
export function patchPostCssConfig(cwd, { dry = false, hasTailwind = false } = {}) {
|
|
252
|
+
if (!hasTailwind) {
|
|
253
|
+
return { status: "skipped", reason: "Tailwind 미사용 — PostCSS 설정 불필요." };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const candidates = ["postcss.config.mjs", "postcss.config.js", "postcss.config.cjs", "postcss.config.json"];
|
|
162
257
|
for (const name of candidates) {
|
|
163
258
|
const abs = `${cwd}/${name}`;
|
|
164
259
|
if (existsSync(abs)) {
|
|
165
|
-
|
|
166
|
-
|
|
260
|
+
const src = readFileSync(abs, "utf8");
|
|
261
|
+
if (/@tailwindcss\/postcss/.test(src)) {
|
|
262
|
+
return { status: "skipped", reason: `${name}에 @tailwindcss/postcss 이미 등록됨.` };
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
status: "failed",
|
|
266
|
+
reason: `${name}이 있는데 @tailwindcss/postcss 없음 — 수동 추가 필요.`,
|
|
267
|
+
filePath: abs,
|
|
268
|
+
};
|
|
167
269
|
}
|
|
168
270
|
}
|
|
169
|
-
if (!filePath) return { status: "ok" };
|
|
170
|
-
|
|
171
|
-
const src = readFileSync(filePath, "utf8");
|
|
172
|
-
// turbopack 블록 안에 .css 전역 룰이 있는지 (loaders 지정)
|
|
173
|
-
const hasTurbopackCssRule =
|
|
174
|
-
/turbopack\s*:\s*\{[\s\S]*?rules\s*:\s*\{[\s\S]*?["'`](?:\*\*\/)?\*\.css["'`]\s*:/.test(
|
|
175
|
-
src,
|
|
176
|
-
);
|
|
177
|
-
if (!hasTurbopackCssRule) return { status: "ok" };
|
|
178
271
|
|
|
179
|
-
|
|
180
|
-
|
|
272
|
+
// 설정 없음 → 신규 생성
|
|
273
|
+
const newPath = `${cwd}/postcss.config.mjs`;
|
|
274
|
+
const content = `export default {\n plugins: { "@tailwindcss/postcss": {} },\n};\n`;
|
|
275
|
+
if (dry) {
|
|
276
|
+
return { status: "applied", reason: "(dry) postcss.config.mjs 신규 생성 예정", filePath: newPath };
|
|
277
|
+
}
|
|
278
|
+
writeFileSync(newPath, content, "utf8");
|
|
279
|
+
return { status: "applied", reason: "postcss.config.mjs 신규 생성", filePath: newPath };
|
|
280
|
+
}
|
|
181
281
|
|
|
182
|
-
|
|
282
|
+
/** `pkg.dependencies`·`pkg.devDependencies`에서 특정 이름 체크. */
|
|
283
|
+
export function hasPkgDep(cwd, name) {
|
|
284
|
+
try {
|
|
285
|
+
const pkg = JSON.parse(readFileSync(`${cwd}/package.json`, "utf8"));
|
|
286
|
+
return Boolean(
|
|
287
|
+
(pkg.dependencies && pkg.dependencies[name]) ||
|
|
288
|
+
(pkg.devDependencies && pkg.devDependencies[name]),
|
|
289
|
+
);
|
|
290
|
+
} catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
183
293
|
}
|
|
184
294
|
|
|
185
295
|
/** 파일 상단의 마지막 import 바로 아래에 line 삽입. import가 없으면 파일 최상단. */
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Focus
|
|
2
|
-
|
|
3
|
-
### 원칙
|
|
4
|
-
데이터 밀도 중심 — 관리자 대시보드, 분석 도구, 로그 뷰어, 트레이딩 콘솔처럼 **한 화면에 많은 정보를 올려야 하고 사용자가 매일 몇 시간씩 들여다보는** 자리의 Theme. 꾸밈을 걷어내고 밀도를 올려 "읽기 좋음"에 전부를 투자한다. `spare`가 "매일 쓰는 도구의 중앙값"이라면 `focus`는 그 극단 — 밀도를 한 단계 더 올리고 브랜드 액센트를 완전히 중성화한다.
|
|
5
|
-
|
|
6
|
-
### Do
|
|
7
|
-
- 테이블·리스트·사이드바에 가능한 많은 행을 노출. 섹션 간 호흡은 Density가 결정하되 추가 margin은 금지.
|
|
8
|
-
- 상태는 색이 아니라 **배지·형태·weight**로 먼저 전달. 색은 status(warning/danger/success/info)에만 남긴다.
|
|
9
|
-
- 액션은 Toolbar·IconButton·DropdownMenu 중심. 큰 primary 버튼은 폼 제출·CTA 자리에만.
|
|
10
|
-
- 모든 숫자·금액 셀은 좌측 정렬 유지 — [[feedback_no_right_align_numbers]] 그대로.
|
|
11
|
-
- 사이드바·탑바는 작은 size(sm)로 밀도를 더 쪼은다.
|
|
12
|
-
|
|
13
|
-
### Don't
|
|
14
|
-
- 마케팅·랜딩·온보딩에 쓰지 않는다. 액센트가 없어 전환이 안 터진다.
|
|
15
|
-
- "비어 보여서" 간격이나 장식을 추가하지 않는다. compact density는 의도된 밀도.
|
|
16
|
-
- `vivid`/`pastel` palette로 override하지 않는다 — 밀도가 높을수록 색 노이즈가 피로를 가속.
|
|
17
|
-
- 브랜드 identity를 색으로 보여야 하는 자리엔 이 Theme이 맞지 않음. [[themes/spare]]나 [[themes/expressive]]로.
|
|
18
|
-
|
|
19
|
-
### Fallback
|
|
20
|
-
축 preset은 아래 표 값으로 고정. palette만 브랜드 선호에 따라 `mono` ↔ `standard` 사이 선택 가능.
|
|
21
|
-
|
|
22
|
-
### 관련
|
|
23
|
-
- [[themes/_SCHEMA]] — Theme 스키마
|
|
24
|
-
- [[themes/spare]] — "매일 쓰는 도구의 중앙값". 밀도를 한 단계 낮춘 버전.
|
|
25
|
-
- [[axes/palette]] — `mono` 값과의 상호작용(아이콘·포커스·nav-active 전부 중성 fg 톤).
|
|
26
|
-
- [[axes/density]] — `compact` 값 전용 프리셋.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
### Signature
|
|
31
|
-
이 Theme IS "매일 몇 시간씩 머무는 작업실" UI. 데이터 밀도가 디자인의 1원칙이고, 꾸밈은 피로 가속기라는 전제 위에 있다. 터미널의 자제력을 웹 앱의 레이어에 끌어들인 결과물 — not minimalism, but engineered legibility.
|
|
32
|
-
|
|
33
|
-
### Preset
|
|
34
|
-
|
|
35
|
-
| Axis | Value | Why |
|
|
36
|
-
|---|---|---|
|
|
37
|
-
| [[axes/mode]] | `dark` | 장시간 사용 환경에서 피로 최소화. light override는 소비자 선호. |
|
|
38
|
-
| [[axes/palette]] | `mono` | 브랜드 액센트 완전 제거. 아이콘·포커스·nav-active 전부 fg 톤. status만 유색 유지. |
|
|
39
|
-
| [[axes/shape]] | `crisp` | 격자 자체가 신호. soft radius는 밀도 환경에서 "뭉개짐"으로 읽힌다. |
|
|
40
|
-
| [[axes/density]] | `compact` | 핵심 축. 행 간격·컨트롤 높이가 한 단계 좁아진다. |
|
|
41
|
-
| [[axes/font]] | `lang=ko` | 한국어 기본. 폰트 스택은 Font 축이 담당. |
|
|
42
|
-
| [[axes/icon]] | `phosphor` | regular/fill 매트릭 완전 매칭. 밀도 환경에선 아이콘 식별이 중요. |
|
|
43
|
-
| [[axes/motion]] | `calm` | 애니메이션 최소화 — 눈이 머무는 시간이 길어 bounce/overshoot가 피로. none은 접근성 승인된 사용자용. |
|
|
44
|
-
|
|
45
|
-
### Layout
|
|
46
|
-
2-column 또는 3-pane이 디폴트 — 좌측 사이드바(sm size) + 중앙 작업 영역 + 우측 디테일 패널. 한 화면에 가능한 많은 정보를 수평으로 분할. 1-column 중심 레이아웃(블로그·마케팅)엔 맞지 않음.
|
|
47
|
-
|
|
48
|
-
### Imagery
|
|
49
|
-
없음. 데이터와 아이콘만. 사진·일러스트는 이 Theme의 성격과 어긋남 — 작업 중 시선을 분산시킴. 예외: 사용자 아바타·조직 로고 같은 identity 식별자.
|
|
50
|
-
|
|
51
|
-
### Decoration
|
|
52
|
-
없음. hairline과 bg-subtle의 톤 차이만으로 그룹 구분. gradient·noise·pattern 금지. 이 Theme에서 "장식은 피로"의 공리.
|
|
53
|
-
|
|
54
|
-
### Negative
|
|
55
|
-
- no primary accent color — status 외 모든 색은 중성 fg 톤
|
|
56
|
-
- no large hero section — "눈에 띄는 한 자리"가 없는 게 디폴트
|
|
57
|
-
- no illustrations · no photography — 작업 영역에 아트는 노이즈
|
|
58
|
-
- no soft radius — crisp 격자가 밀도의 전제
|
|
59
|
-
- no bouncy motion — overshoot는 매일 보기 피곤
|
|
60
|
-
|
|
61
|
-
### Override
|
|
62
|
-
전부 자유. 사용자가 "focus에서 shape만 soft로"처럼 지시하면 그대로 적용. 책임은 사용자. Theme 선언을 벗어난 조합의 완성도는 보증하지 않는다 ([[PRINCIPLES]] §7).
|
|
63
|
-
|
|
64
|
-
### Known Gaps
|
|
65
|
-
- 랜딩·마케팅 자리는 커버 안 함 — [[themes/expressive]]로.
|
|
66
|
-
- 모바일 터치 중심 UX는 커버 안 함 — compact density는 데스크톱·태블릿 가로 전제.
|
|
67
|
-
- 색으로 identity를 전달해야 하는 브랜드엔 맞지 않음 — mono가 디폴트이므로.
|
|
68
|
-
|
|
69
|
-
### Iteration Guide
|
|
70
|
-
1. 밀도를 더 올리고 싶으면 size prop(xs/sm)으로 가되, density 축 자체는 compact에서 더 내릴 수 없다 — compact이 최종 밀도.
|
|
71
|
-
2. palette를 `mono`에서 `standard` 또는 `calm`으로 올리는 override는 허용. 그 외(vivid/pastel)는 Theme과 상충.
|
|
72
|
-
3. motion을 bouncy로 올리는 override는 **금지** — focus의 핵심 자제가 깨짐.
|
|
File without changes
|