diff --git a/docs/design.md b/docs/design.md index 34bd040..335f1a9 100644 --- a/docs/design.md +++ b/docs/design.md @@ -146,9 +146,15 @@ The wordmark should use the aqua brand color. Everything below the wordmark should stay concise. -The welcome brand signal is framed in a single aqua panel. Wide terminals show -the DevMap symbol above the large wordmark. Narrow terminals keep the symbol -but use a compact `DEVMAP` title so the panel does not wrap or clip. +The welcome brand signal uses a large outlined block DevMap wordmark +without a surrounding panel or separate symbol. A small CLI label, concise +capability line, and solid aqua separator make it feel like a professional +developer tool without adding decorative terminal noise. + +Narrow terminals use a compact `DEVMAP` title and shorter capability label so +the welcome header does not wrap or clip. The wide wordmark relies on Unicode +block and box-drawing glyphs; terminals without compatible fonts may use the +compact fallback or require a font configuration adjustment. Example: diff --git a/docs/for-me-personal/PROGRESS.md b/docs/for-me-personal/PROGRESS.md index 6d7e28d..6d70005 100644 --- a/docs/for-me-personal/PROGRESS.md +++ b/docs/for-me-personal/PROGRESS.md @@ -21,10 +21,13 @@ Terakhir diperbarui: 2026-06-14 ### Welcome Brand Panel -- Welcome screen sekarang menampilkan simbol logo DevMap di atas ASCII - wordmark. -- Logo, wordmark, dan border memakai warna aqua brand yang sama. -- Terminal lebar memakai wordmark penuh dalam panel maksimal 76 kolom. +- Simbol logo terpisah dan border luar dihapus agar welcome screen lebih bersih. +- Terminal lebar memakai wordmark blok berpinggiran seperti identitas awal. +- Label `DEVMAP CLI`, capability line, dan separator aqua solid memperjelas + identitas produk sebagai developer tool. +- Perbedaan render glyph Unicode pada sebagian font terminal dicatat sebagai + known issue untuk compatibility pass berikutnya. +- Terminal lebar memakai wordmark penuh dalam area maksimal 76 kolom. - Terminal sempit memakai judul `DEVMAP` ringkas agar panel tidak wrap atau terpotong. - Renderer panel dipisahkan agar layout lebar dan sempit dapat diuji langsung. diff --git a/packages/cli/src/utils/welcome.ts b/packages/cli/src/utils/welcome.ts index 94a354d..b9a63aa 100644 --- a/packages/cli/src/utils/welcome.ts +++ b/packages/cli/src/utils/welcome.ts @@ -2,18 +2,6 @@ import { existsSync } from "node:fs"; import { getSnapshotPath } from "../cache/snapshot.js"; import { theme } from "./output.js"; -const SYMBOL_LOGO = [ - " ╷ ╱╲", - " ╭──────┼──────╱ ╲", - " ╭─╯ ●─────╯ ╱", - " ╱ ╱│ ╱", - "│ ╱ │ ╭────╯", - "│ ╱ │╱╱", - " ╲__╱ ╱│", - " ╲____╱ │", - " ╵" -]; - const WIDE_WORDMARK = [ "██████╗ ███████╗██╗ ██╗███╗ ███╗ █████╗ ██████╗", "██╔══██╗██╔════╝██║ ██║████╗ ████║██╔══██╗██╔══██╗", @@ -26,6 +14,8 @@ const WIDE_WORDMARK = [ const WIDE_PANEL_WIDTH = 76; const COMPACT_PANEL_WIDTH = 48; const WIDE_TERMINAL_MINIMUM = 72; +const PRODUCT_LABEL = "DEVMAP CLI"; +const PRODUCT_CAPABILITIES = "CODEBASE MAP / STATIC ANALYSIS / AI CONTEXT"; export function printWelcome(projectRoot: string): void { const hasSnapshot = existsSync(getSnapshotPath(projectRoot)); @@ -49,32 +39,39 @@ export function printWelcome(projectRoot: string): void { export function renderWelcomeBrandPanel(terminalWidth: number): string { const isWide = terminalWidth >= WIDE_TERMINAL_MINIMUM; - const panelWidth = Math.max( - 28, - Math.min( - terminalWidth, - isWide ? WIDE_PANEL_WIDTH : COMPACT_PANEL_WIDTH - ) + const contentWidth = Math.max( + 24, + Math.min(terminalWidth, isWide ? WIDE_PANEL_WIDTH : COMPACT_PANEL_WIDTH) ); - const contentWidth = panelWidth - 4; const wordmark = isWide ? WIDE_WORDMARK : ["DEVMAP"]; - const content = [ - ...SYMBOL_LOGO, + const label = centerLine(`[ ${PRODUCT_LABEL} ]`, contentWidth); + const wordmarkRows = centerBlock(wordmark, contentWidth); + const capabilities = centerLine( + isWide ? PRODUCT_CAPABILITIES : "CODEBASE INTELLIGENCE", + contentWidth + ); + const separator = "━".repeat(Math.min(contentWidth, 64)); + + return [ + `${theme.gray}${label}${theme.reset}`, "", - ...wordmark - ]; - const top = `╭${"─".repeat(panelWidth - 2)}╮`; - const bottom = `╰${"─".repeat(panelWidth - 2)}╯`; - const rows = content.map((line) => { - const visibleLine = line.slice(0, contentWidth); - const leftPadding = Math.floor((contentWidth - visibleLine.length) / 2); - const rightPadding = contentWidth - visibleLine.length - leftPadding; - return `│ ${" ".repeat(leftPadding)}${visibleLine}${" ".repeat(rightPadding)} │`; - }); + ...wordmarkRows.map((line) => `${theme.aqua}${line}${theme.reset}`), + "", + `${theme.gray}${capabilities}${theme.reset}`, + `${theme.aqua}${separator}${theme.reset}` + ].join("\n"); +} + +function centerLine(line: string, width: number): string { + const visibleLine = line.slice(0, width); + const leftPadding = Math.floor((width - visibleLine.length) / 2); + return `${" ".repeat(leftPadding)}${visibleLine}`; +} - return [top, ...rows, bottom] - .map((line) => `${theme.aqua}${line}${theme.reset}`) - .join("\n"); +function centerBlock(lines: string[], width: number): string[] { + const blockWidth = Math.min(width, Math.max(...lines.map((line) => line.length))); + const leftPadding = Math.floor((width - blockWidth) / 2); + return lines.map((line) => `${" ".repeat(leftPadding)}${line.slice(0, width)}`); } function printCommand(command: string, description?: string): void { diff --git a/packages/cli/test/welcome.test.ts b/packages/cli/test/welcome.test.ts index 588660c..4696d7f 100644 --- a/packages/cli/test/welcome.test.ts +++ b/packages/cli/test/welcome.test.ts @@ -2,26 +2,29 @@ import assert from "node:assert/strict"; import test from "node:test"; import { renderWelcomeBrandPanel } from "../src/utils/welcome.js"; -test("welcome brand panel frames the logo and wordmark on wide terminals", () => { +test("welcome brand panel renders an outlined block wordmark and tool identity", () => { const panel = stripAnsi(renderWelcomeBrandPanel(100)); const lines = panel.split("\n"); + assert.match(panel, /\[ DEVMAP CLI \]/); assert.match(panel, /██████╗ ███████╗/); - assert.match(panel, /╱╲/); - assert.ok(lines.every((line) => line.length === lines[0]?.length)); - assert.ok((lines[0]?.length ?? 0) <= 76); - assert.match(lines[0] ?? "", /^╭─+╮$/); - assert.match(lines.at(-1) ?? "", /^╰─+╯$/); + assert.match(panel, /██╔══██╗██╔════╝/); + assert.match(panel, /CODEBASE MAP \/ STATIC ANALYSIS \/ AI CONTEXT/); + assert.ok(lines.every((line) => line.length <= 76)); + assert.match(lines.at(-1) ?? "", /^━+$/); + assert.equal(lines[2]?.indexOf("█"), lines[3]?.indexOf("█")); }); -test("welcome brand panel uses a compact wordmark on narrow terminals", () => { +test("welcome brand panel uses a compact tool identity on narrow terminals", () => { const panel = stripAnsi(renderWelcomeBrandPanel(48)); const lines = panel.split("\n"); + assert.match(panel, /\[ DEVMAP CLI \]/); assert.match(panel, /DEVMAP/); - assert.ok(lines.every((line) => line.length === lines[0]?.length)); - assert.ok((lines[0]?.length ?? 0) <= 48); - assert.doesNotMatch(panel, /██████/); + assert.match(panel, /CODEBASE INTELLIGENCE/); + assert.ok(lines.every((line) => line.length <= 48)); + assert.doesNotMatch(panel, /██████╗/); + assert.match(lines.at(-1) ?? "", /^━+$/); }); function stripAnsi(value: string): string {